Wednesday, November 7, 2018

Oracle row cache objects Event: 10222, Dtrace Scripts (I)

You don’t understand anything until you learn it more than one way.
Marvin Minsky


(I)-Tracing Methods       (II)-Object Type(Part-1)      (III)-Object Type(Part-2)       (IV)-Sql Executions (IV)      (V)-Contentions and Scalability


This small Oracle Row Cache Cookbook is made of a series of 5 Blogs:
Oracle row cache objects Event: 10222, Dtrace Script (I)
Row Cache Objects, Row Cache Latch on Object Type: Plsql vs Java Call (Part-1) (II)
Row Cache Objects, Row Cache Latch on Object Type: Plsql vs Java Call (Part-2) (III)
Row Cache and Sql Executions (IV)
Latch: row cache objects Contentions and Scalability (V)


This Blog will present two tracing methods of row cache objects Gets, which provide more deep insights than ordinary Oracle dynamic performance views (v$rowcache, v$latch, etc.).

At first we explore Event: 10222 to trace row cache objects Gets, and then create a PL/SQL script to import and analyse the generated trace files.

As a second approach, three Dtrace scripts are composed and tested:
   -. rco_dtrace_cache: trace each Row Cache Object Get with its arguments.
   -. rco_dtrace_latch: printout each Row Cache Latch Get call stack.
   -. rco_dtrace_std:   draw a State Transition Diagram for Object and Latch Gets.
The difference of three methods are their precision of perceiving granularity.
   (1). dynamic performance views: statement
   (2). 10222 trace:               row cache
   (3). dtrace:                    latch (atomic locking mechanism)
Note: all tests are done in Oracle 12cR1 (12.1.0.2.0), and 12cR2 (12.2.0.1.0).


1. Trace Event: 10222


Perform the tracing on the same query as Blog: nls_database_parameters, dc_props, latch: row cache objects, and compare with the output of dynamic performance views in that Blog.

More details are revealed by increasing the tracing Level.


1.1. Level 4


alter session set max_dump_file_size = UNLIMITED;

alter session set tracefile_identifier = 'row_cache_10222_4';
alter session set events '10222 trace name context forever, level 4';  
select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
alter session set events '10222 trace name context off';

---------------------- Output ----------------------

kqrpre1 : found po=18f058b68 flg=2 hash=5b42c02e .
kqrpre1 : done po=18f058b68 cid=15 flg=2 hash=5b42c02e 0 eq=13feab2b0 .

kqrpre1 : found po=142020320 flg=2 hash=2155f5c0 .
kqrpre1 : done po=142020320 cid=15 flg=2 hash=2155f5c0 0 eq=13feab2b0 .

1.2. Level 255


alter session set tracefile_identifier = 'row_cache_10222_255';
alter session set events '10222 trace name context forever, level 255';  
select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
alter session set events '10222 trace name context off';

---------------------- Output ----------------------

kqrpre1 : found po=18f058b68 flg=2 hash=5b42c02e time=2235042744
kqrmpin : kqrLockAndPinPo Pin po 18f058b68 cid=15 flg=2 hash=5b42c02e time=2235042857
kqrpre1 : done po=18f058b68 cid=15 flg=2 hash=5b42c02e 0 eq=13fea3cd0 time=2235042872
kqrmupin : kqrpspr2 Unpin po 18f058b68 cid=15 flg=2 hash=5b42c02e time=2235042890

kqrpre1 : found po=142020320 flg=2 hash=2155f5c0 time=2235042919
kqrmpin : kqrLockAndPinPo Pin po 142020320 cid=15 flg=2 hash=2155f5c0 time=2235042945
kqrpre1 : done po=142020320 cid=15 flg=2 hash=2155f5c0 0 eq=13fea3cd0 time=2235043046
kqrmupin : kqrpspr2 Unpin po 142020320 cid=15 flg=2 hash=2155f5c0 time=2235043061

1.3. Level 4294967295


alter session set tracefile_identifier = 'row_cache_10222_4294967295';
alter session set events '10222 trace name context forever, level 4294967295';  
select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
alter session set events '10222 trace name context off';

---------------------- Output ----------------------

kqrpre1 : found po=18f058b68 flg=2 hash=5b42c02e time=2271264535
kqrmpin : kqrLockAndPinPo Pin po 18f058b68 cid=15 flg=2 hash=5b42c02e
time=2271264669
kqrpre1 : done po=18f058b68 cid=15 flg=2 hash=5b42c02e 0 eq=13fea57f8 SQL=select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET' time=2271264687
kqrmupin : kqrpspr2 Unpin po 18f058b68 cid=15 flg=2 hash=5b42c02e time=2271264704
kqrmupin : free po=18f058b68 flg=2
kqrfrpo: Free po 18f058b68
 KQR Parent Cache Object:
 pob = 18f058b68 flg = 00000002 cid = 15 xcb = 0 hbk = 18f05ac38
 hbk=[f84f96e8,151186580] 
 own=18f058c38[18f058c38,18f058c38] wat=18f058c48[18f058c48,18f058c48] mod=0
kqrfrpo : freed to heap po=18f058b68 time=2271264814

kqrpre1 : found po=142020320 flg=2 hash=2155f5c0 time=2271264844
kqrmpin : kqrLockAndPinPo Pin po 142020320 cid=15 flg=2 hash=2155f5c0
time=2271264871
kqrpre1 : done po=142020320 cid=15 flg=2 hash=2155f5c0 0 eq=13fea57f8 SQL=select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET' time=2271264885
kqrmupin : kqrpspr2 Unpin po 142020320 cid=15 flg=2 hash=2155f5c0 time=2271264904
kqrmupin : free po=142020320 flg=2
kqrfrpo: Free po 142020320
 KQR Parent Cache Object:
 pob = 142020320 flg = 00000002 cid = 15 xcb = 0 hbk = 1420223f0
 hbk=[1511862a0,18f077dd0] 
 own=1420203f0[1420203f0,1420203f0] wat=142020400[142020400,142020400] mod=0
kqrfrpo : freed to heap po=142020320 time=2271264992

1.4. Trace Level Discussion


Tests showed:
  Level 4 (5, 7, 15 are same) prints only kqrpre1 subroutine call. 
  Level 255 prints kqrpre1, kqrmpin, kqrmupin.
  Level 4294967295 prints kqrpre1, kqrmpin, kqrpre1, kqrmupin, kqrmupin, kqrfrpo, kqrfrpo.
where 4294967295 (2^32-1) seems the maximum tracing level with most details, including SQL statements.

"cid" denotes Cache ID; "kqrpre1" is for row cache object Read; "po" is for Parent Object.

There is also Event: 10205 which says:

  10205, 00000, "row cache debugging"
   // *Cause:
   // *Action:
   /  At level 1, KQR will dump out latch clean-up debugging traces.
   /  At level 2, KQR will dump out bootstrap debugging traces.
   /  At level 3, KQR will invalidate, remove from the cache and put 0xff's
   /              in row cache objects that are not currently locked.
But it is not able to generate trace file with 10205.


1.5. Are trace levels always bitmap additive ?


A common belief of trace levels is bitmap additive (all binary numbers are positive, different, power of 2).

If we run following Plsql, Level 516 output includes SQL statements. Since 516 is bitmap additive of 4 and 512:
    516 = 4 + 512 = 2^2 + 2^9 
Level 4 or Level 512 should contain SQL statements. But the test shows that Level 4 contains only "kqrpre1" in 12cR1 (and "kqreqd" in 12cR2); Level 512 does not generate any trace file. So Level 516 is not bitmap additive in this case, probably some hard-coded logic
(Level 513, 514 and 515 also not generate any trace file).

create or replace procedure test10222_trace_level as
  type         t_level_tab is table of pls_integer;
  l_level_tab  t_level_tab := new t_level_tab(4, 512, 513, 514, 515, 516);
  l_trc_prefix varchar2(256) := 'row_cache_10222_Level_';
  l_val        varchar2(256);
begin
  for i in 1..l_level_tab.count loop
    execute immediate q'[alter session set tracefile_identifier = ']' ||l_trc_prefix||l_level_tab(i)||q'[']';
    execute immediate q'[alter session set events '10222 trace name context forever, level ]'||l_level_tab(i)||q'[']';
    select value into l_val from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
    execute immediate q'[alter session set events '10222 trace name context off']';
  end loop;
end;
/

exec test10222_trace_level;


2. 10222 Trace Import and Analyse


2.1. Import


Import the trace files into DB via external files (see appended PL/SQL Script)

set serveroutput on

exec import_10222_trc_proc(1, 'testdb_ora_7920_row_cache_10222_4.trc');

exec import_10222_trc_proc(2, 'testdb_ora_7920_row_cache_10222_255.trc');

exec import_10222_trc_proc(3, 'testdb_ora_7920_row_cache_10222_4294967295.trc');

2.2. Analyze


Here a list of queries to analyze Trace Level 4294967295 (RUN=3).

select rownum line_nbr, text1 from raw_10222_trc_ext;

select * from import_10222_trc 
where run=3 
order by run, line_nbr;

select cid, cid_type, cid_subordinate#, cid_parameter, count(*) cnt 
from import_10222_trc 
where run=3 
group by cid, cid_type, cid_subordinate#, cid_parameter 
order by cnt desc;

CID  CID_TYPE     CID_SUBORDINATE#  CID_PARAMETER   CNT
---  -----------  ----------------  -------------   ---
15   PARENT                         dc_props        63
7    SUBORDINATE  0                 dc_users        14
17   PARENT                         dc_global_oids  4
11                                                  4
8    PARENT                         dc_objects      1
                                                    1

select ts_us - lag(ts_us) over(partition by run order by line_nbr) elapsed_us, t.*
from import_10222_trc t 
where run=3 
order by run, line_nbr;

select cid, cid_type, cid_subordinate#, cid_parameter, count(*) cnt, sum(elapsed_us) elapsed_us from (
  select ts_us - lag(ts_us) over(partition by run order by line_nbr) elapsed_us, t.*
  from import_10222_trc t
  where run=3 order by run, line_nbr)
group by cid, cid_type, cid_subordinate#, cid_parameter order by cnt desc;

-- only tested on Solaris (x86-64)
select * from import_10222_trc t, v$rowcache_parent r
where t.run=3 and '0000000'||t.po_address = r.address(+)
order by run, line_nbr;

3. Dtrace Script


3.1 Row Cache Object Gets


Run the same query in one Sqlplus Session (SID: 123, SPID: 7890), and at the same time, trace it by following trace Script (see appended Dtrace Script: rco_dtrace_cache).

SQL (123, 456) > select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';

sudo dtrace -n \
'pid$target::kqrLockAndPinPo:entry /execname == "oracle"/ {
  @CID_ADDR_CNT[arg0, arg1] = count(); 
  @CID_CNT[arg0] = count();
  printf("\nCID = %-4d ADDR = %-16X\n", arg0, arg1);}
END {
  printf("\n----- CID_ADDR CNT -----\n"); printa("CID = %-4d ADDR = %-16X CNT = %-10@d\n", @CID_ADDR_CNT);
  printf("\n----- CID CNT -----\n"); printa("CID = %-4d CNT = %-10@d\n", @CID_CNT);
  printf("\n*** all except 1st counting doubled ***\n");}
' -p 7890

 CPU     ID                    FUNCTION:NAME
...
   5  80618            kqrLockAndPinPo:entry
CID = 15   ADDR = 18F058B68
...
   5  80618            kqrLockAndPinPo:entry
CID = 15   ADDR = 142020320
...

----- CID_ADDR CNT -----
CID = 15   ADDR = 18F058B68        CNT = 1
CID = 15   ADDR = 142020320        CNT = 1
...

----- CID CNT -----
CID = 11   CNT = 3
CID = 17   CNT = 3
CID = 15   CNT = 60
We can see that ADDR matches 10222 Trace Event output, for example, po=18f058b68.


3.2 State Transition Diagram


Run Dtrace Script: rco_dtrace_std to draw a State Transition Diagram of latch Get (kslgetl) and Latch Free (kslfre) for each Row Cache Object GET. (Note: kslgetl is exClusive latch get, whereas ksl_get_shared_latch is Shared latch get(in S or X Mode))

Here the output for one Row Cache Object GET. It shows 3 kslgetl Calls at 3 different Locations ("where"), all are prefixed with "Transition", followed by one kslfre
(note the indented function entry and return).

Those are probably the 3 unclear "latch: row cache objects" GETs, discussed in Oracle Core: Essential Internals for DBAs and Developers Page 167. (see Blog: nls_database_parameters, dc_props, latch: row cache objects )

-> kqrpre1
---------Step 1 at 2018 Oct 18 08:50:25 (Elapsed-ns=1491587737)---------
  -> kslgetl
---------Step 2 at 2018 Oct 18 08:50:25 (Elapsed-ns=104489)---------
            Entry State(nproc_pid=>0x0, flag=>0x0, gets=>0x4A04F, latch=>0x19B, level=>0x4, where=>0x1171)
            Transition > kslgetl:entry(Addr=>0x182D98958, Immed_Get=>1, Why=>0, Where=>4441(0x1159))
  <- kslgetl
---------Step 3 at 2018 Oct 18 08:50:25 (Elapsed-ns=32606)---------
            Return State(nproc_pid=>0x1B, flag=>0x0, gets=>0x4A050, latch=>0x19B, level=>0x4, where=>0x1159)
  -> kqrLockAndPinPo
---------Step 4 at 2018 Oct 18 08:50:25 (Elapsed-ns=36536)---------
            CID = 15   ADDR = 171999DD8
  <- kqrLockAndPinPo
---------Step 5 at 2018 Oct 18 08:50:25 (Elapsed-ns=24430)---------
  -> kslfre
---------Step 6 at 2018 Oct 18 08:50:25 (Elapsed-ns=8813)---------
            Transition > kslfre:entry(Addr=>0x182D98958) ===Latch freed===
  <- kslfre
---------Step 7 at 2018 Oct 18 08:50:25 (Elapsed-ns=14267)---------
<- kqrpre1
---------Step 8 at 2018 Oct 18 08:50:25 (Elapsed-ns=11490)---------
-> kslgetl
---------Step 9 at 2018 Oct 18 08:50:25 (Elapsed-ns=6415)---------
            Entry State(nproc_pid=>0x0, flag=>0x0, gets=>0x4A050, latch=>0x19B, level=>0x4, where=>0x1159)
            Transition > kslgetl:entry(Addr=>0x182D98958, Immed_Get=>1, Why=>0, Where=>4464(0x1170))
<- kslgetl
---------Step 10 at 2018 Oct 18 08:50:25 (Elapsed-ns=6092)---------
            Return State(nproc_pid=>0x1B, flag=>0x0, gets=>0x4A051, latch=>0x19B, level=>0x4, where=>0x1170)
-> kslfre
---------Step 11 at 2018 Oct 18 08:50:25 (Elapsed-ns=10545)---------
            Transition > kslfre:entry(Addr=>0x60164110) ===Latch freed===
<- kslfre
---------Step 12 at 2018 Oct 18 08:50:25 (Elapsed-ns=4733)---------
-> kslgetl
---------Step 13 at 2018 Oct 18 08:50:25 (Elapsed-ns=4412)---------
            Entry State(nproc_pid=>0x0, flag=>0x0, gets=>0x4A051, latch=>0x19B, level=>0x4, where=>0x1170)
            Transition > kslgetl:entry(Addr=>0x182D98958, Immed_Get=>1, Why=>0, Where=>4465(0x1171))
<- kslgetl
---------Step 14 at 2018 Oct 18 08:50:25 (Elapsed-ns=5110)---------
            Return State(nproc_pid=>0x1B, flag=>0x0, gets=>0x4A052, latch=>0x19B, level=>0x4, where=>0x1171)
-> kslfre
---------Step 15 at 2018 Oct 18 08:50:25 (Elapsed-ns=4445)---------
            Transition > kslfre:entry(Addr=>0x182D98958) ===Latch freed===
<- kslfre
The 3 consecutive latch Gets for one Row Cache Object GET are at 3 different Locations ("Where"):
  Where=>4441(0x1159): kqrpre: find obj   -- latch Get at 1st Location
  Where=>4464(0x1170): kqreqd             -- latch Get at 2nd Location
  Where=>4465(0x1171): kqreqd: reget      -- latch Get at 3rd Location
which can be found by sql below:

CREATE OR REPLACE FORCE VIEW X_GV$LATCH_MISSES as
SELECT t1.indx              INDX,
       t1.inst_id           INST_ID,       
       t1.ksllasnam         PARENT_NAME,   
       t2.ksllwnam          "WHERE",       
       t1.kslnowtf          NWFAIL_COUNT,  
       t1.kslsleep          SLEEP_COUNT,   
       t1.kslwscwsl         WTR_SLP_COUNT, 
       t1.kslwsclthg        LONGHOLD_COUNT,
       t2.ksllwnam          LOCATION,      
       t1.con_id            CON_ID         
  FROM x$ksllw t2, x$kslwsc t1
 WHERE t2.indx = t1.indx;
 
select * from sys.X_GV$LATCH_MISSES 
where parent_name in ('row cache objects') 
  and indx in (4441, 4464, 4465); 
  
INDX  PARENT_NAME        WHERE             NWFAIL_COUNT  SLEEP_COUNT  WTR_SLP_COUNT  LONGHOLD_COUNT  LOCATION        
----- ----------------------  -----------------------------  ------------  -----------  -------------  --------------
4441  row cache objects  kqrpre: find obj  0             2            8              0               kqrpre: find obj
4464  row cache objects  kqreqd            0             0            0              0               kqreqd          
4465  row cache objects  kqreqd: reget     0             6            0              0               kqreqd: reget 


3.3 Call Stack and Call Sequence (Update 2020-Aug-03)


There are 3 latch gets, the first call is from kqrpre1 with Call Stack:

  kslgetl ()
  kqrpre1 ()
  kkdlpftld ()
  qerfxFetch ()
  opifch2 ()
the second and third calls are from kqreqd with Call Stack:

  kslgetl ()
  kqreqd ()
  kqrprl ()
  kkdlpftld ()
  qerfxFetch ()
kqrpre1 calls latch get/free (kslgetl/kslfre) with Call Sequence (latch location 0x1159):

  <kgghash>
  <kslgetl>      -- Where=>4441(0x1159)
  <kqrCacheHit>
  <kqrLockAndPinPo>
  <kslfre>
kqreqd calls latch get/free (kslgetl/kslfre) twice, with Call Sequence (latch location 0x1170 and 0x1171):

  <kslgetl>     -- Where=>4464(0x1170)
  <KGHISPIR>    -- "pin recreatable" 
  <kghupr>      -- "unpin recreatable"
  <kslfre>
  
  <kslgetl>     -- Where=>4465(0x1171)
  <kglFreeSO>   -- "free state object"
  <kslfre>
To find RowCache object, kqrpre1 first calls <kgghash> to compute a hash value and map to po (Rowcache Parent Object) value as shown in above 10222 trace:

  kqrpre1 : found po=0x9675c390 flg=2 hash=819e131 pso=0x9511def8 dur=CALL


4. 12cR2 10222 Trace Event


Make the same trace in Oracle 12cR2 for 3 different Levels, here the output:


4.1. Level 4


kqrpre1 : found po=0x9675c390 flg=2 hash=819e131 pso=0x9511def8 dur=CALL .
kqrpre1 : done po=0x9675c390 cid=15 flg=2 hash=819e131 0 eq=0x63dfbd98 pso=0x9511def8 dur=CALL .
kqreqd: eq: 0x63dfbd98
kqreqd freeing enqeue: eq: 0x63dfbd98

4.2. Level 255


kqrmpin : kqrpre: found po Pin po 0x9675c390 cid=15 flg=2 hash=819e131
time=1980227738
kqrpre1 : found po=0x9675c390 flg=2 hash=819e131 pso=0x9511def8 dur=CALL time=1980227746
kqrpre1 : done po=0x9675c390 cid=15 flg=2 hash=819e131 0 eq=0x63dfc580 pso=0x9511def8 dur=CALL time=1980227754
kqrprl: eq=0x63dfc580 fpInUse=FALSE
ksedsts()+346<-kqrprl()+1221<-kkdlpExecSqlCbk()+570<-kkdlpExecSql()+114<-kkdlpGet()+317<-kkdlpftld()+229<-qerfxFetch()+6519<-opifch2()+3267<-kpoal8()+3436<-opiodr()+1229<-ttcpip()+1257<-opitsk()+1940<-opiino()+941<-opiodr()+1229<-opidrv()+1021<-sou2o()+145
<-opimai_real()+455<-ssthrdmain()+417<-main()+262<-__libc_start_main()+256.
kqreqd: eq: 0x63dfc580
kqrmupin : kqrReleaseLock Unpin po 0x9675c390 cid=15 flg=2 hash=819e131 time=1980227875
kqreqd freeing enqeue: eq: 0x63dfc580

4.3. Level 4294967295


kqrmpin : kqrpre: found po Pin po 0x9675c390 cid=15 flg=2 hash=819e131
time=1845554855
kqrpre1 : found po=0x9675c390 flg=2 hash=819e131 pso=0x9511def8 dur=CALL time=1845554916
found stack: ksedsts()+346<-kqrpre1()+4784<-kkdlpExecSqlCbk()+418<-kkdlpExecSql()+114<-kkdlpGet()+317<-kkdlpftld()+229<-qerfxFetch()+6519<-opifch2()+3267<-kpoal8()+3436<-opiodr()+1229<-ttcpip()+1257<-opitsk()+1940<-opiino()+941<-opiodr()+1229<-opidrv()+1021
<-sou2o()+145<-opimai_real()+455<-ssthrdmain()+417<-main()+262<-__libc_start_main()+256.
kqrpre1 : done po=0x9675c390 cid=15 flg=2 hash=819e131 0 eq=0x63dfc0e0 pso=0x9511def8 dur=CALL SQL=select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET' time=1845564279
done stack: ksedsts()+346<-kqrpre1()+2566<-kkdlpExecSqlCbk()+418<-kkdlpExecSql()+114<-kkdlpGet()+317<-kkdlpftld()+229<-qerfxFetch()+6519<-opifch2()+3267<-kpoal8()+3436<-opiodr()+1229<-ttcpip()+1257<-opitsk()+1940<-opiino()+941<-opiodr()+1229<-opidrv()+1021
<-sou2o()+145<-opimai_real()+455<-ssthrdmain()+417<-main()+262<-__libc_start_main()+256.
kqrprl: eq=0x63dfc0e0 fpInUse=FALSE
ksedsts()+346<-kqrprl()+1221<-kkdlpExecSqlCbk()+570<-kkdlpExecSql()+114<-kkdlpGet()+317<-kkdlpftld()+229<-qerfxFetch()+6519<-opifch2()+3267<-kpoal8()+3436<-opiodr()+1229<-ttcpip()+1257<-opitsk()+1940<-opiino()+941<-opiodr()+1229<-opidrv()+1021<-sou2o()+145
<-opimai_real()+455<-ssthrdmain()+417<-main()+262<-__libc_start_main()+256.
kqreqd: eq: 0x63dfc0e0
kqrmupin : kqrReleaseLock Unpin po 0x9675c390 cid=15 flg=2 hash=819e131 time=1845566287
kqreqd freeing enqeue: eq: 0x63dfc0e0

4.4. 12cR2 vs. 12cR1


Note that in Oracle 12.2.0.1.0 (12cR2), "row cache mutex" replaced 12.1.0.2.0 (12cR1) "latch: row cache objects". (See Blog: row cache mutex in Oracle 12.2.0.1.0 )

It seems that 10222 Trace event still generates similar output with more information, for example, call stacks.

Comparing the function calling sequence of Level 255 between 12cR1 and 12cR2:

  12cR1: kqrpre1(found) -> kqrmpin        -> kqrpre1(done) -> kqrmupin
  12cR2: kqrmpin        -> kqrpre1(found) -> kqrpre1(done) -> kqrmupin
12cR1: kqrpre1 and kqrmpin (kqrmupin) are interleaved, whereas 12cR2 shows that kqrpre1 is enclosed by kqrmpin (kqrmupin). So 12cR2 improved trace output.


5. Row Cache Internals: kqrpre1 and kqrfrpo


Pick one Row Cache Object GET from the output of above 10222 Level 4294967295, it shows that Row Cache is not able to cache it. Each time it needs to invoke "kqrpre1" (Row Cache Parent Read) to create a new DC row, use it, and then release it by "kqrfrpo : freed to heap". It took 279 us (2271264814-2271264535) for one such Read.

kqrpre1 : found po=18f058b68 flg=2 hash=5b42c02e time=2271264535
kqrmpin : kqrLockAndPinPo Pin po 18f058b68 cid=15 flg=2 hash=5b42c02e
time=2271264669
kqrpre1 : done po=18f058b68 cid=15 flg=2 hash=5b42c02e 0 eq=13fea57f8 SQL=select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET' time=2271264687
kqrmupin : kqrpspr2 Unpin po 18f058b68 cid=15 flg=2 hash=5b42c02e time=2271264704
kqrmupin : free po=18f058b68 flg=2
kqrfrpo: Free po 18f058b68
 KQR Parent Cache Object:
 pob = 18f058b68 flg = 00000002 cid = 15 xcb = 0 hbk = 18f05ac38
 hbk=[f84f96e8,151186580] 
 own=18f058c38[18f058c38,18f058c38] wat=18f058c48[18f058c48,18f058c48] mod=0
kqrfrpo : freed to heap po=18f058b68 time=2271264814
We will look further in next Blog: Row Cache Objects, Row Cache Latch on Object Type: Plsql vs Java Call (Part-1) (II)


6. Discussions


Row Cache is a mechanisms analogue to CPU Cache:
  "CPU Cache" transfer "cache lines" between "Main Memory"  and "L1 Cache".

  "Row Cache" transfer "DC rows"     between "Buffer Cache" and "DC Cache".
Row Cache is more like a "software" cache because "Row Cache" and its hidden "Buffer Cache" are in SGA memory,
whereas "CPU Cache (L1)" and its underlying "Main Memory" are located in separate hardware.

Any cache miss results in a "Main Memory/Buffer" with much longer latency. The cache hit rate plays an important role in determining the performance.

Section 3.2 State Transition Diagram showed that each Row Cache Object GET is a cache miss.


7. 10222 Trace Import and Analyse Script


drop table raw_10222_trc_ext;

create table raw_10222_trc_ext
( text1 varchar2(1000)
)
organization external
(type oracle_loader
 default directory PLSHPROF_DIR
 access parameters
 (
   records delimited by newline CHARACTERSET AL32UTF8
   badfile aaa_db_io:'trc_ext.bad'
   logfile aaa_db_io:'trc_ext.log'
   discardfile aaa_db_io:'trc_ext.dsc'
   fields terminated by ','  OPTIONALLY ENCLOSED BY '"'
   missing field values are null
   ( text1 position(1:1000)
   )
 )
 location ('testdb_ksun_trc_row_cache_10222_15.trc')
) reject limit unlimited;

select rownum line_nbr, text1 from raw_10222_trc_ext;

drop table import_10222_trc;

create table import_10222_trc (run number, line_nbr number, cid number, ts_us number, po_address varchar2(32), 
  cid_type varchar2(12), cid_subordinate# number, cid_parameter varchar2(40), 
  obobn number, obname varchar2(100), sql varchar2(100), raw_lines varchar2(4000));

create or replace procedure import_10222_trc_proc (p_run number, p_trc_file varchar2, p_delete_old boolean := true) as 
  l_line_sep    varchar2(3) := '###';
  l_pos_1       number;
  l_pos_2       number;
  l_raw_lines   varchar2(32000);
  l_line_nbr    number           := 1;
  l_cid         number;
  l_ts_us       number;
  l_po_address  varchar2(32000);
  l_obobn       number;
  l_obname      varchar2(32000);
  l_sql         varchar2(32000);
begin
  -- setup trace file location
  execute immediate 'alter table raw_10222_trc_ext location (''' || p_trc_file || ''')';
  
  -- delete old RUN
  if p_delete_old then
    delete from import_10222_trc where run = p_run;
    commit;
  end if;
  
  -- concatenate same PO lines into one line
  for c in (select rownum line_nbr, text1 from raw_10222_trc_ext) loop
    dbms_output.put_line('Raw line_nbr: '||c.line_nbr ||'--text1: '||c.text1);
    if l_raw_lines is null then
      l_raw_lines := c.text1;
    else
      l_raw_lines := l_raw_lines ||l_line_sep||c.text1;
    end if;
    
    if c.text1 like 'kqrpre1 : found po=%' or c.text1 like 'kqrpad: new po%' then
      l_raw_lines := substr(l_raw_lines, 1, 4000);
      insert into import_10222_trc(run, line_nbr, raw_lines) values(p_run, l_line_nbr, l_raw_lines);
      commit;
      l_line_nbr  := c.line_nbr;
      l_raw_lines := c.text1;
    end if;
  end loop;
  
  -- insert last line
  if length(l_raw_lines) > 0 then
   l_raw_lines := substr(l_raw_lines, 1, 4000);
    insert into import_10222_trc(run, line_nbr, raw_lines) values(p_run, l_line_nbr, l_raw_lines);
    commit;
  end if;

 -- parse raw_lines
  for c in (select line_nbr, raw_lines from import_10222_trc
             where raw_lines like 'kqrpre1 : found po=%' or raw_lines like 'kqrpad: new po%') loop
    dbms_output.put_line('Parsing line_nbr: '||c.line_nbr||'--text1: '||c.raw_lines);
    l_cid        := regexp_substr(c.raw_lines, 'cid=(\d*)', 1, 1, 'i', 1);
    l_pos_1      := instr(c.raw_lines, 'time=', -1);
    if l_pos_1 > 0 then
      l_ts_us    := regexp_substr(c.raw_lines, 'time=(\d*)', l_pos_1, 1, 'i', 1);
    end if;
    l_po_address := substr(upper(regexp_substr(c.raw_lines, 'po=(\S*)', 1, 1, 'i', 1)), 1, 32);
    l_obobn      := regexp_substr(c.raw_lines, 'obobn=(\d*)', 1, 1, 'i', 1);
    l_obname     := substr(regexp_substr(c.raw_lines, 'obname=(\S*)', 1, 1, 'i', 1), 1, 100);
    l_pos_2      := instr(c.raw_lines, 'SQL=', 1) + 4;
    
    if (l_pos_2 > 4) then
     l_pos_1      := instr(c.raw_lines, 'time=', l_pos_2);
     if l_pos_1 = 0 then l_pos_1 := l_pos_2 + 100; end if;
      l_sql        := substr(substr(c.raw_lines, l_pos_2, l_pos_1 - l_pos_2), 1, 100);
    else
     l_sql := null;
    end if;
    
    update import_10222_trc 
       set cid        = l_cid
          ,ts_us      = l_ts_us
          ,po_address = l_po_address
          ,obobn      = l_obobn
          ,obname     = l_obname
          ,sql        = l_sql
     where run = p_run and line_nbr = c.line_nbr; 
    commit;
  end loop;

  -- add v$rowcache info
  update import_10222_trc t 
     set (cid_type, cid_subordinate#, cid_parameter) = 
         (select type, subordinate#, parameter from v$rowcache r where r.cache# = t.cid and rownum=1)
   where run = p_run;
  commit;
end;
/


8. Dtrace Scripts


8.1. rco_dtrace_cache


sudo dtrace -n \
'pid$target::kqrLockAndPinPo:entry /execname == "oracle"/ {
  @cid_addr_cnt[arg0, arg1] = count();
  @cid_cnt[arg0] = count();
  @cid_cnt_total = count();
  ustack(5, 0);
  printf("\nCID = %-4d ADDR = %-16X\n", arg0, arg1);}
END { 
   /** Only first call in new opened Sqlplus has not double counting **/
   printf("\n\n*************** CID Stats with Address (double counting) ****************");
   printa("\nCID = %-4d ADDR = %-16X CNT = %-10@d", @cid_addr_cnt);
   printf("\n*************** CID Stats ****************");
   printa("\nCID = %-4d CNT = %-10@d", @cid_cnt);
   printf("\n*************** CID Stats Summary ****************");
   printa("\nCNT = %-10@d", @cid_cnt_total); }
' -p $1

Update (30-May-2021)

Nenad (All-round Database Topics) found that double counting user calls can be avoided by specifying the module name, like

   pid$target:oracle:kqrLockAndPinPo:entry
instead of

   pid$target::kqrLockAndPinPo:entry
Here some tests in Oracle 19c
(Note: Oracle 12.2.0.1.0 (12cR2), "row cache mutex" replaced 12.1.0.2.0 (12cR1) "latch: row cache objects". See Blog: row cache mutex in Oracle 12.2.0.1.0 )

At first we list all kqr* functions:

SQL > select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';

$ sudo dtrace -n 'pid$target::kqr*:entry { @[probefunc] = count(); }' -p 25986

	dtrace: description 'pid$target::kqr*:entry ' matched 310 probes
		  kqrAllocateEnqueue        194
		  kqrCacheHit               194
		  kqrFreeEnqueue            194
		  kqrGetPOMutexInt          194
		  kqrLockPo                 194
		  kqrReleaseLock            194
		  kqreqd                    194
		  kqrget                    194
		  kqrhsh                    194
		  kqrmpin                   194
		  kqrmupin                  194
		  kqrpre1                   194
		  kqrpre2                   194
		  kqrprl                    194
		  kqrpspr                   194
		  
	*Note: kqrLockAndPinPo not listed, probably it is an Oracle 12.1 function
Then we list probes for above probe functions. There are two probe modules for each function (that is why we have the double counting), for example,

$ sudo dtrace -l -f kqrLockPo 
         ID   PROVIDER          MODULE           FUNCTION NAME
      90031   pid25986           a.out          kqrLockPo entry
      90032   pid25986          oracle          kqrLockPo entry
         
$ sudo dtrace -l -f kqrpre1    
         ID   PROVIDER          MODULE           FUNCTION NAME
      90029   pid25986           a.out            kqrpre1 entry
      90030   pid25986          oracle            kqrpre1 entry
Now we can use one of following probes to specify module and hence avoid double counting:

$ sudo dtrace -n 'pid$target:oracle:kqrpre1:entry {@[probefunc] = count();}' -p 25986
$ sudo dtrace -n 'pid$target:a.out:kqrpre1:entry  {@[probefunc] = count();}' -p 25986
$ sudo dtrace -i '90029 {@[probefunc] = count();}'
$ sudo dtrace -i '90030 {@ = count(); }'
We can also directly list each function with its probe module as follows:

SQL >select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';

$ sudo dtrace -n 'pid$target::kqr*:entry {@[probefunc, probemod] = count();}' -p 25986
	dtrace: description 'pid$target::kqr*:entry ' matched 310 probes
	    kqrAllocateEnqueue          a.out          97
	    kqrAllocateEnqueue          oracle         97
	    ......                      
	    kqrpre1                     a.out          97
	    kqrpre1                     oracle         97
	    ......                      
	    kqrpspr                     a.out          97
	    kqrpspr                     oracle         97

8.2. rco_dtrace_latch


sudo dtrace -F -n \
'
BEGIN {roread = 0; step = 1; self->func = "ne"; self->name = "na"; prev_ts = timestamp;}
pid$target::kqrpre1:entry /execname == "oracle" && self->func != probefunc / {roread = 1;
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt[probefunc] = count();}
pid$target::kqrpre1:return /roread ==1/ {roread = 0;
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;}
pid$target::kslgetl:entry /step > 1 && self->func != probefunc / {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt[probefunc] = count();}
pid$target::kslgetl:return /self->func == probefunc && self->name != probename/{
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1;  prev_ts = timestamp;}
pid$target::kslfre:entry /step > 1 && self->func != probefunc / {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt[probefunc] = count();}
pid$target::kslfre:return /self->func == probefunc && self->name != probename/{
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1;  prev_ts = timestamp;}
pid$target::kqrLockAndPinPo:entry /step > 1 && self->func != probefunc / {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt[probefunc] = count();}
pid$target::kqrLockAndPinPo:return /self->func == probefunc && self->name != probename/{
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;}
' -p $1

8.3. rco_dtrace_std (@TODO improve it)


sudo dtrace -F -n \
'
typedef unsigned int ub4;

typedef struct latchstate /* latch state */ {
    ub4 nproc_pid;  /* Nproc or pid   */
    ub4 flag;       /* blocking flag  */
    ub4 gets;       /* number of gets */
    ub4 latch;      /* latch number   */
    ub4 level;      /* latch level    */
    ub4 where; } latchstate;

BEGIN {step = 1; self->func = "na"; self->name = "na"; prev_ts = timestamp;}
pid$target::kqrpre1:entry /execname == "oracle" && self->func != probefunc / {self->roread = 1;
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt_func[probefunc] = count();}
pid$target::kqrpre1:return /self->roread ==1/ {self->roread = 0;
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = "na"; self->name = probename; step = step + 1; prev_ts = timestamp;}
pid$target::kslgetl:entry /step > 1 && self->func != probefunc / {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt_func[probefunc] = count();
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   printf("\n            Entry State(nproc_pid=>0x%X, flag=>0x%X, gets=>0x%X, latch=>0x%X, level=>0x%X, where=>0x%X)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   printf("\n            Transition > %s:%s(Addr=>0x%X, Immed_Get=>%d, Why=>%d, Where=>%d(0x%X))",
          probefunc, probename, arg0, arg1, arg2, arg3, arg3);
   self->addr = arg0; self->loc = arg3;}
pid$target::kslgetl:return /self->func == probefunc && self->name != probename/{
   @sum[self->addr, self->loc] = sum(timestamp - prev_ts);
   @avg[self->addr, self->loc] = avg(timestamp - prev_ts);
   @max[self->addr, self->loc] = max(timestamp - prev_ts);
   @cnt[self->addr, self->loc] = count();
   @sum_total = sum(timestamp - prev_ts);
   @cnt_total = count();
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1;  prev_ts = timestamp;
   ls = ((latchstate *) copyin(self->addr, sizeof(latchstate)));
   printf("\n            Return State(nproc_pid=>0x%X, flag=>0x%X, gets=>0x%X, latch=>0x%X, level=>0x%X, where=>0x%X)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);}
pid$target::kslfre:entry /step > 1 && self->func != probefunc / {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt_func[probefunc] = count();
   printf("\n            Transition > %s:%s(Addr=>0x%-X) ===Latch freed===", probefunc, probename, arg0);}
pid$target::kslfre:return /self->func == probefunc && self->name != probename/{
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1;  prev_ts = timestamp;}
pid$target::kqrLockAndPinPo:entry /step > 1 && self->func != probefunc / {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;
   @cnt_func[probefunc] = count();
   @cid_addr_cnt[arg0, arg1] = count();
   @cid_cnt[arg0] = count();
   @cid_cnt_total = count();
   printf("\n            CID = %-4d ADDR = %-16X", arg0, arg1);}
pid$target::kqrLockAndPinPo:return /self->func == probefunc && self->name != probename/{
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   self->func = probefunc; self->name = probename; step = step + 1; prev_ts = timestamp;}
END {
   trunc(@sum, 10); trunc(@avg, 10); trunc(@max, 10); trunc(@cnt, 10);
   printf("\n\n*************** Latch Stats (time in ns) ****************");
   printf("\nAddr           Where");
   printa("\n0x%-10X %6d sum = %10@d", @sum); printf("\n----------------------");
   printa("\n0x%-10X %6d avg = %10@d", @avg); printf("\n----------------------");
   printa("\n0x%-10X %6d max = %10@d", @max); printf("\n----------------------");
   printa("\n0x%-10X %6d cnt = %10@d", @cnt); printf("\n----------------------");
   printa("\nsum_total = %10@d", @sum_total);
   printa("\ncnt_total = %10@d", @cnt_total);

   /** @TODO not exact **/
   printf("\n\n*************** CID Stats with Address ****************");
   printa("\nCID = %-4d ADDR = %-16X CNT = %-10@d", @cid_addr_cnt);
   printf("\n*************** CID Stats ****************");
   printa("\nCID = %-4d CNT = %-10@d", @cid_cnt);
   printf("\n*************** CID Stats Summary ****************");
   printa("\nCNT = %-10@d", @cid_cnt_total);

   printf("\n\n*************** probefunc Stats ****************");
   printa("\n%-20s cnt = %10@d", @cnt_func);}
' -p $1

Thursday, October 18, 2018

Oracle ROWCACHE Views and Contents


1. ROWCACHE Views


Oralce pvovided 3 Views on Row Cache Objects.
V$ROWCACHE (X$KQRST): 
    a summary view of DC statistics. The suffix "ST" in underlying X$ Table probably means STatistics.
    One special Column: FASTGETS is marked as Reserved for internal use,
    which could indicate that some on-going work of new development.
    
    V$ROWCACHE is a stats view having 70 fixed rows.
    An appropriate name would be V$ROWCACHE_Stats.

V$ROWCACHE_PARENT (X$KQRFS): 
    an instantaneous view of DC activity. Its 4 real-time Columns:
      LOCK_MODE, LOCK_REQUEST, TXN, SADDR                        
    indicate that this view is reflecting each lock owner and each row waiter instantaneously.
    
    It is a run-time view having dynamic number of rows (eg. 61057).
    An appropriate name would be V$ROWCACHE_objects.

V$ROWCACHE_SUBORDINATE (X$KQRFP): 
    list 4 existed Subcache(Subordinate): 
    
    select cache#, subcache_name, count(*) cnt from V$ROWCACHE_SUBORDINATE 
     group by cache#, subcache_name order by subcache_name desc;
    
        CACHE#  SUBCACHE_NAME             CNT
        ------ ------------------------- ----
        7       dc_user_grants            32
        8       dc_object_grants          492
        16      dc_histogram_data_values  142
        16      dc_histogram_data         26
        
    It has a dynamic number of rows (eg. 2225). It looks like a residue of Row Cache development,
    and it is not clear about its practical usage.


2. ROWCACHE Contents


Learned from Case study on Oracle rowcache internals, cached non-existent objects and a describe bug, we can compose queries to list all Dictionary Caches and their contents. Each query looks like how Oracle makes the recursive queries to acces Row Caches.
(Note that Solaris x86 is little-endian)

We can compose queries to list all Dictionary Caches and their contents. Each query looks like how Oracle makes the recursive queries to acces Row Caches.
(Note that Test DB on Solaris x86 is little-endian)

At first, create a conversion function.

----helper function----
create or replace function dump_hex2str (dump_hex varchar2) return varchar2 is
  l_str varchar2(100);
begin
  with sq_pos as (select level pos from dual connect by level <= 1000)
      ,sq_chr as (select pos, chr(to_number(substr(dump_hex, (pos-1)*2+1, 2), 'XX')) ch
                  from sq_pos where pos <= length(dump_hex)/2)
  select listagg(ch, '') within group (order by pos) word
    into l_str
  from sq_chr;
  return l_str;
end;
/
Here the queries to read Row Caches and some example outputs.


2.1. dc_props


select dump_hex2str(rtrim(key, '0')) dc_prop_name, v.* 
from v$rowcache_parent v 
where cache_name in ('dc_props') 
order by key; 

  DC_PROP_NAME                  INDX  HASH  ADDRESS           CACHE#  CACHE_NAME  EXISTENT
  ----------------------------- ----- ----- ----------------- ------- ----------- --------
  BACK_END_DB                   37420  48    000000017AC05ED8  15      dc_props    N
  DBTIMEZONE                    37377  7     000000017C1D2538  15      dc_props    Y
  DEFAULT_EDITION               37416  46    000000017C3D9668  15      dc_props    Y
  DEFAULT_PERMANENT_TABLESPACE  37378  7     00000001783FDF00  15      dc_props    Y
  DEFAULT_TBS_TYPE              37384  12    000000017C1DAB98  15      dc_props    Y
  DEFAULT_TEMP_TABLESPACE       37371  0     000000017C1B0A78  15      dc_props    Y
  ...
  NLS_CALENDAR                  37374  3     000000017C18CF18  15      dc_props    Y
  NLS_CHARACTERSET              37402  29    000000017C18F038  15      dc_props    Y
  ... 
  60 rows selected.


2.2. dc_global_oids


with ro as (select /*+ materialize */ substr(key, 9, 32) key_oid$, ro.* 
              from v$rowcache_parent ro where cache_name in ('dc_global_oids') 
               and rownum <= 100    -- select 100 for test
              )
    ,so as (select /*+ materialize */ obj#, ro.* from sys.oid$, ro where oid$ = ro.key_oid$)
select owner||'.'||object_name object, so.*
  from dba_objects do, so
 where object_id = so.obj#;

  OBJECT         OBJ#     KEY_OID$                          INDX  HASH  ADDRESS           CACHE#  CACHE_NAME      EXISTENT
  -------------- -------- --------------------------------- ----- ----- ----------------- ------- --------------- --------
  SYS.PRVT_ILM   1581930  2CE6B7D8955B3AEBE054D48564CDEC20  1799  508   00000001714A1DD0  17      dc_global_oids  Y
  SYS.PRVT_ILM   1581930  2CE6B7D895B03AEBE054D48564CDEC20  1788  158   00000001714A3BF0  17      dc_global_oids  Y
  K.T_OBJ_RET    2360168  795ECCD5A45D48DDE054005056984D97  1793  410   00000001714DE8C0  17      dc_global_oids  Y
  K.T_OBJ_IN     2360169  7807E9A682B5082FE054005056984D97  1786  126   00000001714F2078  17      dc_global_oids  Y
  K.T_OBJ_OUT    2360170  7807E9A682BA082FE054005056984D97  1794  464   00000001714FB2B0  17      dc_global_oids  Y
  K.T_OBJ_INOUT  2360171  77DDFDFCEB484AF6E054005056984D97  1785  105   00000001714FB4E0  17      dc_global_oids  Y 


2.3. dc_objects


select to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX') Schema_User_ID,
       (select username from dba_users where user_id = 
          to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX')) username,
       dump_hex2str(rtrim(substr(key, 13), '0')) dc_object_name, 
       to_number(trim(both '0' from substr(key, 11, 2)||substr(key, 9, 2)), 'XXXX') key_str_len,
       v.* 
from v$rowcache_parent v 
where cache_name in ('dc_objects') 
  and rownum<= 100                  -- select 100 for test
order by key; 

  SCHEMA_USER_ID  USERNAME           DC_OBJECT_NAME               KEY_STR_LEN  INDX  HASH   ADDRESS           CACHE#  CACHE_NAME  EXISTENT
  --------------- ------------------ ---------------------------- ------------ ----- ------ ----------------- ------- ----------- --------
                                     FILE$                        5            546   11880  000000017BF87310  8       dc_objects  Y
                                     UNDO$                        5            505   4027   000000017BFAA5E0  8       dc_objects  Y
                                     VIEW$                        5            534   10244  0000000164B8D4B8  8       dc_objects  Y
                                     PROPS$                       6            585   17055  000000017B72E3C8  8       dc_objects  Y
  1                                  DUAL                         4            607   19722  0000000177B824B8  8       dc_objects  Y
  1                                  V$ROWCACHE_PARENT            17           610   20351  00000001662CEAB0  8       dc_objects  Y
  1                                  SYS_TEMP_0FD9D664D_E88F5145  27           512   5516   0000000164D16BF0  8       dc_objects  N
  49              K                  FOO                          3            532   10104  0000000177BD3AB8  8       dc_objects  Y
  49              K                  T_OBJ_RET                    9            676   30902  00000001662AD9B8  8       dc_objects  Y
  49              K                  DBA_OBJECTS                  11           519   6726   000000016630D270  8       dc_objects  N
  49              K                  DBMS_OUTPUT                  11           594   18151  00000001662796D0  8       dc_objects  N
  49              K                  DUMP_HEX2STR                 12           517   6336   0000000179577980  8       dc_objects  Y
  49              K                  V$ROWCACHE_PARENT            17           639   24261  00000001662D0EA0  8       dc_objects  N
  1779            GSMADMIN_INTERNAL  CLOUD                        5            646   26011  0000000164E42DF0  8       dc_objects  Y


2.4. dc_tablespaces


select to_number(substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2), 'XXXXXX') ts#, 
      (select name from v$tablespace 
        where ts# = to_number(substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2), 'XXXXXX')) ts_name,
       v.* 
from v$rowcache_parent v 
where cache_name in ('dc_tablespaces') 
  and rownum <= 100       -- select 100 for test
order by 1;
 
  TS#   TS_NAME   INDX  HASH  ADDRESS           CACHE#  CACHE_NAME      EXISTENT
  ----- --------- ----- ----- ----------------- ------- --------------- --------
  0     SYSTEM    56    4     0000000164DB14D0  0       dc_tablespaces  Y
  3     TEMP      23    2     0000000170F58628  0       dc_tablespaces  Y
  1999  USER1     37    2     0000000182F74750  0       dc_tablespaces  Y
  2000  USER2     68    5     0000000166167F68  0       dc_tablespaces  Y


2.5. dc_users


The output shows that SYS is in CACHE#=7, whereas SYSTEM in CACHE#=10.

select to_number(substr(key, 3, 2)||substr(key, 1, 2), 'XXXX') key_len,
       dump_hex2str(rtrim(substr(key, 5, 2*to_number(substr(key, 3, 2)||substr(key, 1, 2), 'XXXX')), '0')) user_or_role_name, 
       v.* 
from v$rowcache_parent v 
where cache_name in ('dc_users') 
  and rownum <= 100       -- select 100 for test
order by key;

  KEY_LEN  USER_OR_ROLE_NAME  INDX  HASH   ADDRESS           CACHE#  CACHE_NAME  EXISTENT
  -------- ------------------ ----- ------ ----------------- ------- ----------- --------
  3        SYS                1406  10788  0000000165B77CA0  7       dc_users    Y
  3        SYS                2342  51836  0000000165B77CA0  7       dc_users    Y
  5        OUTLN              1419  41803  0000000165E54C78  10      dc_users    Y
  5        OUTLN              2331  21366  0000000165E54C78  10      dc_users    Y
  6        DBSNM              1408  14676  00000001672EDCD0  10      dc_users    Y
  6        DBSNM              2338  45053  00000001672EDCD0  10      dc_users    Y
  6        SYSTEM             1422  50615  000000017C2F6468  10      dc_users    Y
  6        SYSTEM             2330  17882  000000017C2F6468  10      dc_users    Y


2.6. dc_sequences


create sequence test_seq start with 1 cache 1000;
 
with sq as (                                                                                      
  select to_number(substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2), 'XXXXXX') seq_object_id
        ,v.*                                                                                       
   from v$rowcache_parent v                                                                       
  where cache_name in ('dc_sequences'))                                                           
select o.object_name, seq_object_id, cache#, cache_name 
    --,sq.*                                   
  from dba_objects o, sq                                                                          
 where o.object_id   = sq.seq_object_id                                                            
   and o.object_name = 'TEST_SEQ';                                                                
                                                                                                   
  OBJECT_NAM  SEQ_OBJECT_ID  CACHE#  CACHE_NAME
  ----------  -------------  ------  ------------
  TEST_SEQ          4153811      13  dc_sequences 


2.7 dc_rollback_segments


with sq as (
  select to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX') 
            rollback_segment_id
         ,indx, hash, address, cache#, cache_name    -- v.*
    from v$rowcache_parent v
   where cache_name in ('dc_rollback_segments'))
select s.sid, s.serial#, t.xidusn, t.xidslot, t.xidsqn, t.ubafil, t.ubablk
      ,(select segment_name from dba_rollback_segs where segment_id = sq.rollback_segment_id) segment_name
      ,sq.*
from  sq, v$session s, v$transaction t 
where sq.rollback_segment_id = t.xidusn and s.taddr=t.addr;

  SID SERIAL# XIDUSN XIDSLOT XIDSQN UBAFIL UBABLK SEGMENT_NAME           ROLLBACK_SEGMENT_ID INDX HASH ADDRESS          CACHE# CACHE_NAME
  --- ------- ------ ------- ------ ------ ------ ---------------------- ------------------- ---- ---- ---------------- ------ --------------------
  558   53837    264      23    152      3  15926 _SYSSMU264_2251784465$                 264 2731 3975 00000000A22DBD00      3 dc_rollback_segments
  722   27322    280      16    142      3  12193 _SYSSMU280_2691079894$                 280 2819 7188 00000000A22D8A00      3 dc_rollback_segments
       
-- Only those rollback_segments have 'ACTIVE' undo_extents
select * from dba_undo_extents where status = 'ACTIVE';

  OWN SEGMENT_NAME           TABLESPACE_NAME EXTENT_ID FILE_ID BLOCK_ID   BYTES BLOCKS RELATIVE_FNO COMMIT_JTIME COMMIT_WTIME STATUS
  --- ---------------------- --------------- --------- ------- -------- ------- ------ ------------ ------------ ------------ ------
  SYS _SYSSMU264_2251784465$ UNDO                    2       3    15872 1048576    128            3                           ACTIVE
  SYS _SYSSMU280_2691079894$ UNDO                    2       3    12160 1048576    128            3                           ACTIVE


-- Lock held to perform DDL on the undo segment
select * from v$enqueue_statistics where eq_type = 'US';  

-- 'US' Lock
select * from v$lock where type = 'US';


3. ROWCACHE Hash Bucket (Update 2020-Aug-03)


With following query, we can see that 60 rowcache objects in dc_props are distributed into 37 hash buckets:

select (hash+1) Hash_Bucket, count(*) Hash_Bucket_Size from v$rowcache_parent where cache_name = 'dc_props' 
group by hash order by 1;

  HASH_BUCKET HASH_BUCKET_SIZE
  ----------- ----------------
            1                3
            4                1
            6                2
            8                2
            9                1
           11                2
          ...             ...
           62                3
           63                1
           64                1
  
  37 rows selected.
The bucket size is varied from 1 to 3. Any size bigger than 1 will incur hash collision.

select Hash_Bucket_Size, count(*) Number_of_Buckets, (Hash_Bucket_Size * count(*)) Number_of_RowCache_Objects from 
  (select (hash+1) Hash_Bucket, count(*) Hash_Bucket_Size from v$rowcache_parent where cache_name = 'dc_props' group by hash)
group by Hash_Bucket_Size order by 2 desc;

  HASH_BUCKET_SIZE NUMBER_OF_BUCKETS NUMBER_OF_ROWCACHE_OBJECTS
  ---------------- ----------------- --------------------------
                 1                20                         20
                 2                11                         22
                 3                 6                         18

Wednesday, October 10, 2018

Result Cache: RC Enqueue and Contention (V)

This Blog will discuss 'enq: RC - Result Cache: Contention' when two (or multi) sessions adding the same Result Cache item at about the same time.

In the previous 4 Blogs, we showed 4 different cases of Result Cache Invalidation and the subsequent Result Cache: RC Latch Contention.
1. PL/SQL Function Result Cache Invalidation (I) (DML and Select for Update)
2. Result Cache: RC Latch Contention and RESULT_CACHE_MAX_SIZE (II))
3. Result Cache: RC Latch Contention and PL/SQL Exception Handler (III)
4. Result Cache:0 rows updated Invalidations (IV) 
Note: All tests are done in Oracle 12.1.0.2.


1. RC Latch vs. RC Enqueue


Adding a new RC item requires 4 RC Latch Gets (1 S Mode and successive 3 X Mode) and 4 subsequent Releases (kslfre), so that the single RC Latch is held solely in the necessary critical code path for a short time during whole RC item building time. (see Blog: PL/SQL Function Result Cache Invalidation (I))

Prepare Test:

--============ Test Setup ============--
create or replace function test_build_time (p_id number) return number result_cache as
begin
 dbms_lock.sleep(p_id);
  return p_id;
end;
/

SQL > exec dbms_result_cache.flush;
Open two Sessions to add two different new RC items at about same time.

--============ Session_1 @T1 (started at 05:06:24) ============--
05:06:08 SQL > var ret number;
05:06:15 SQL > exec :ret := test_build_time(9.1);
Elapsed: 00:00:09.12
05:06:33 SQL >

--============ Session_2 @T2 (started at 05:06:25) ============--
05:06:09 SQL > var ret number;
05:06:16 SQL > exec :ret := test_build_time(9.2);
Elapsed: 00:00:09.22
05:06:34 SQL >
Above test demonstrates that each adding takes the specified build_time (09.12, 09.22), hence no RC Latch contentions. Both seemed running independently alone even though they are running concurrently.

Now it arises the question, what happens when two different sessions adding identical RC item at about same time ?

RC Latch (ksl_get_shared_latch) does not have the knowledge of RC item content, and two sessions do not hold it during whole RC item building time. Therefore RC Latch is not able to protect the uniqueness of this new RC item.

The Oracle answer is RC Enqueue to coordinate such access, analogue to TX Enqueue.


2. Building Phases


Run test:

--============ Test Run ============--
SQL > exec :ret := test_build_time(15.8);

--============ Test Output ============--
06:53:10 SQL > var ret number;
06:53:20 SQL > exec :ret := test_build_time(15.8);
Elapsed: 00:00:15.80
06:53:36 SQL >
If during the Test Run, we frequently run query (for example, with SQLcl repeat command):

select id, status, name, creation_timestamp, (sysdate-creation_timestamp)*86400 elapsed_seconds, 
       scan_count, lru_number, build_time --, cache_id, cache_key
from v$result_cache_objects 
where type = 'Result' and name like '%TEST_BUILD_TIME%'
order by id desc;
We can observe that adding a new RC item experiences 3 phases, with status changed at different timestamp:
  New (0 - 10 second)
      -> Bypass (10 - 15.8 second)  
            -> Published (after 15.8 second)
10 seconds is a timeout and will be looked later.

At the end, we have the output:

 ID STATUS    NAME                      CREATION_TIMESTAMP  ELAPSED_SECONDS SCAN_COUNT LRU_NUMBER BUILD_TIME
--- --------- ------------------------- ------------------- --------------- ---------- ---------- ----------
  1 Published "K"."TEST_BUILD_TIME"::8. 2018-10-09 06:53:20              17          0        886       1580
The two columns in v$result_cache_objects denote:
  CREATION_TIMESTAMP: Starting TimeStamp of Build
  BUILD_TIME:         Elapsed Time of Build (in centisecond)
The intermediate status "Bypass" (10 - 15.8 second) seems related to "Bypass Object (BO)" mentioned in:
Bug 23738186 : HIGH RESULT CACHE LATCHES AFTER MIGRATION TO 12.1.0.2
(superseded by Bug 26436717 : RESULT CACHE: "BYPASS OBJECTS" INCORRECTLY USED BY WAITING PROCESSES.
This bug fixes a flaw in the original fix for bug# 23738186)

See Blog: Result Cache: RC Latch Contention and PL/SQL Exception Handler (III)


3. Result Cache: RC Enqueue


Run following test, we can see that adding a new Row requires 2 enq REQs (311-309=2).

select eq_name, total_req#, succ_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
exec :ret := test_build_time(16.8);
select eq_name, total_req#, succ_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');

--============ Test Output ============--
11:53:24 SQL > select eq_name, total_req#, succ_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
  EQ_NAME                        TOTAL_REQ#  SUCC_REQ# CUM_WAIT_TIME
  ------------------------------ ---------- ---------- -------------
  Result Cache: Enqueue                 309        309             0

11:54:02 SQL > exec :ret := test_build_time(16.8);
Elapsed: 00:00:16.82

11:54:19 SQL > select eq_name, total_req#, succ_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
  EQ_NAME                        TOTAL_REQ#  SUCC_REQ# CUM_WAIT_TIME
  ------------------------------ ---------- ---------- -------------
  Result Cache: Enqueue                 311        311             0

4. Two Sessions adding the same Result Cache item at about the same time


Add the same Result Cache item at about the same time in two sessions, and trace both session with Event 10704 & 10046.

During test, run query below to watch 'enq: RC - Result Cache: Contention':

  select * from v$wait_chains;

4.1. Session_1 adding item started at 18:55:06


--============ Session_1 @T1 (18:55:06) ============--
exec dbms_result_cache.flush;

set time on timing on
column eq_name format a25
var ret number;

select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
alter session set events='10046 trace name context forever, level 12 : 10704 trace name context forever, level 3' tracefile_identifier='RC_Enq_1';
exec :ret := test_build_time(18.8);
alter session set events='10046 trace name context off : 10704 trace name context off ';
select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');

--============ Session_1 Output (started at 18:55:06)============--
18:55:04 SQL> select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
  EQ_NAME                   TOTAL_REQ# TOTAL_WAIT#  SUCC_REQ# FAILED_REQ# CUM_WAIT_TIME
  ------------------------- ---------- ----------- ---------- ----------- -------------
  Result Cache: Enqueue            670           8        666           4        215480

18:55:06 SQL> exec :ret := test_build_time(18.8);
Elapsed: 00:00:18.83

18:55:25 SQL> select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
  EQ_NAME                   TOTAL_REQ# TOTAL_WAIT#  SUCC_REQ# FAILED_REQ# CUM_WAIT_TIME
  ------------------------- ---------- ----------- ---------- ----------- -------------
  Result Cache: Enqueue            674           9        669           5        225480

--============ Session_1 Trace (irelevant lines removed) ============--
*** SESSION ID:(189.37589) 2018-10-09 18:55:06.768
PARSING IN CURSOR #139879855170464 len=43 dep=0 uid=49 oct=47 lid=49 tim=12989538062736 hv=3533163271 ad='1513d9108' sqlid='4ffwn7z99gks7'
BEGIN :ret := test_build_time(18.8); END;
ksqgtl *** RC-00000002-FFFFFFFF-00000000-00000000 mode=4 flags=0x10400 timeout=21474836 ***
ksqgtl: RETURNS 0
ksqgtl *** RC-00000002-0024034D-00000000-00000000 mode=3 flags=0x10000 timeout=0 ***
ksqgtl: RETURNS 0
ksqgtl *** RC-00000001-00000001-00000000-00000000 mode=6 flags=0x10000 timeout=0 ***
ksqgtl: RETURNS 0

*** 2018-10-09 18:55:25.592
WAIT #139879855170464: nam='PL/SQL lock timer' ela= 18805802 duration=0 p2=0 p3=0 obj#=-1 tim=12989556871029
EXEC #139879855170464:c=859,e=18808207,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=1,plh=0,tim=12989556871192

4.2. Session_2 adding item started at 18:55:10 (4 seconds after Session_1)


--============ Session_2 @T2 (18:55:10) ============--
set time on timing on
column eq_name format a25
var ret number;

select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
alter session set events='10046 trace name context forever, level 12 : 10704 trace name context forever, level 3' tracefile_identifier='RC_Enq_2';
exec :ret := test_build_time(18.8);
alter session set events='10046 trace name context off : 10704 trace name context off ';
select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');

--============ Session_2 Output (started at 18:55:10, 4 seconds after Session_1) ============--
17:54:08 SQL> select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
  EQ_NAME                   TOTAL_REQ# TOTAL_WAIT#  SUCC_REQ# FAILED_REQ# CUM_WAIT_TIME
  ------------------------- ---------- ----------- ---------- ----------- -------------
  Result Cache: Enqueue            673           8        669           4        215480

18:55:10 SQL> exec :ret := test_build_time(18.8);
Elapsed: 00:00:28.84

18:55:39 SQL> select eq_name, total_req#, total_wait#, succ_req#, failed_req#, cum_wait_time from v$enqueue_statistics where eq_type in ('RC');
  EQ_NAME                   TOTAL_REQ# TOTAL_WAIT#  SUCC_REQ# FAILED_REQ# CUM_WAIT_TIME
  ------------------------- ---------- ----------- ---------- ----------- -------------
  Result Cache: Enqueue            674           9        669           5        225480

--============ Session_2 Trace (irelevant lines removed) ============--
*** SESSION ID:(363.50996) 2018-10-09 18:55:11.039
PARSING IN CURSOR #139824425361968 len=43 dep=0 uid=49 oct=47 lid=49 tim=12989542327336 hv=3533163271 ad='1513d9108' sqlid='4ffwn7z99gks7'
BEGIN :ret := test_build_time(18.8); END;
ksqgtl *** RC-00000001-00000001-00000000-00000000 mode=4 flags=0x10000 timeout=10 ***
ksqgtl: no transaction
ksqgtl: use existing ksusetxn DID
ksqgtl:
 ksqlkdid: 0001-001A-00000B01
*** ksudidTrace: ksqgtl
 ksusesdi:   0000-0000-00000000
 ksusetxn:   0001-001A-00000B01
ksqcmi: RC-00000001-00000001-00000000-00000000 mode=4 timeout=10

*** 2018-10-09 18:55:21.052
WAIT #139824425361968: nam='enq: RC - Result Cache: Contention' ela= 10003442 name|mode=1380122628 chunkNo=1 blockNo=1 obj#=1591122 tim=12989552331028
ksqcmi: returns 51
ksqgtl: RETURNS 51

*** 2018-10-09 18:55:39.858
WAIT #139824425361968: nam='PL/SQL lock timer' ela= 18805872 duration=0 p2=0 p3=0 obj#=-1 tim=12989571137038

4.3. Discussions


Sqlplus output shows:
  Session_1 started at 18:55:06, and took 18.83 seconds.
  
  Session_2 started at 18:55:10 (4 seconds later), and took 28.84 seconds.
            28.84 is (10 + 18.8). We will look it later.
Above trace shows:
  Session_1 made 3 "ksqgtl *** RC" in mode=4, 3, 6, all succeded "ksqgtl: RETURNS 0",
                    with ela= 18805802 us on 'PL/SQL lock timer'.
           
  Session_2 made 1 "ksqgtl *** RC" in mode=4, failed "ksqgtl: RETURNS 51", 
                    with ela= 10003442 us on 'enq: RC - Result Cache: Contention'
                     and ela= 18805872 us on 'PL/SQL lock timer'.
                    total ela is about 28.8 seconds.
           "ksqgtl *** RC" is marked with "timeout=10".

               name|mode=1380122628 chunkNo=1 blockNo=1 obj#=1591122
            means:
               "1380122628" = 0x52430004 = RC 04 
               "1591122" is object_id of SYS.OBJ$
               "chunkNo=1 blockNo=1" is RC row pointer, similar to Heap Table ROWID: "block_id row_number".  
v$enqueue_statistics query output is consistent with above trace output:
  4 REQ# (674-670), 1 WAIT# (9-8), 3 SUCC_REQ# (669-666), 1 FAILED_REQ# (5-4)
  CUM_WAIT_TIME increase 10000 ms (225480 - 215480).

5. Hidden Parameters: _result_cache_timeout


Above the trace output reveals "timeout=10" for 'enq: RC - Result Cache: Contention' (ela= 10003442). Lookup all 17 Result Cache Hidden Parameters listed in Blog: PL/SQL Function Result Cache Invalidation (I)

The only matched one with default value 10 is:
  _result_cache_timeout: maximum time (sec) a session waits for a result, default = 10
Let's make a few tests to gamble its behaviours:
    Each test is done by two sessions to add the same item with different time delay.
    Different tests are carried out by varying build_time.
    Then modify "_result_cache_timeout" from 10 to 33 to make more tests.


Case_1. build_time=8.5 < 10, Session_2 starts 2 seconds aftet Session_1.


-- Session_2 started within NEW phase (10 seconds timeout) of Session_1.
-- Item ready in Session_1 at 11:36:05. 
-- Session_2's timeout at 11:35:59 + 10 =  11:36:09.
-- (11:36:05 < 11:36:09): Session_2 can get result before timeout. 

--============ Session_1 (started at 11:35:57) ============--
11:35:06 SQL> exec :ret := test_build_time(8.5);
Elapsed: 00:00:08.51
11:36:05 SQL>

--============ Session_2 (started at 11:35:59) ============--
11:35:06 SQL> exec :ret := test_build_time(8.5);
Elapsed: 00:00:06.17
11:36:05 SQL>

Case_2. build_time=20.1 > 10, Session_2 starts 2 seconds aftet Session_1.


-- Session_2 started within NEW phase of Session_1.
-- Item ready in Session_1 at 11:52:35. 
-- Session_2's timeout at 11:52:17 + 10 =  11:52:27.
-- (11:52:35 > 11:52:27): Session_2 can NOT get result before timeout. 

--============ Session_1 (started at 11:52:15) ============--
11:47:34 SQL> exec :ret := test_build_time(20.1);
Elapsed: 00:00:20.10
11:52:35 SQL>

--============ Session_2 (started at 11:52:17) ============--
11:47:47 SQL> exec :ret := test_build_time(20.1);
Elapsed: 00:00:30.12     -- timeout(10) + build_time(20.1) = 30.12
11:52:47 SQL>

Case_3. build_time=20.2 > 10, Session_2 starts 13 seconds aftet Session_1.


-- Session_2 started within BYPASS phase of Session_1.
-- Item ready in Session_1 at 11:53:53. 
-- Session_2's timeout at 11:53:47 + 10 =  11:53:57.
-- (11:53:53 < 11:53:57): Session_2 can get result before timeout.  

--============ Session_1 (started at 11:53:33) ============--
11:52:35 SQL> exec :ret := test_build_time(20.2);
Elapsed: 00:00:20.21
11:53:53 SQL>

--============ Session_2 (started at 11:53:47) ============--
11:52:47 SQL> exec :ret := test_build_time(20.2);
Elapsed: 00:00:20.21
11:54:07 SQL>

Increase "_result_cache_timeout" from 10 to 33, and make further tests:

alter system set "_result_cache_timeout"=33;

Case_4. build_time=20.4 < 33, Session_2 starts 13 seconds aftet Session_1.


-- Session_2 started within NEW phase (33 seconds timeout) of Session_1.
-- Item ready in Session_1 at 12:19:15. 
-- Session_2's timeout at 12:19:08 + 33 =  12:19:42.
-- (12:19:15 < 12:19:42): Session_2 can get result before timeout.  

--============ Session_1 (started at 12:18:55) ============--
12:18:51 SQL> exec :ret := test_build_time(20.4);
Elapsed: 00:00:20.42
12:19:15 SQL>

--============ Session_2 (started at 12:19:08) ============--
12:19:06 SQL> exec :ret := test_build_time(20.4);
Elapsed: 00:00:07.00
12:19:15 SQL>

Case_5. build_time=40.2 > 33, Session_2 starts 8 seconds aftet Session_1.


-- Session_2 started within NEW phase of Session_1.
-- Item ready in Session_1 at 13:14:56. 
-- Session_2's timeout at 13:14:20 + 33 =  13:14:53.
-- (13:14:56 > 13:14:53): Session_2 can NOT get result before timeout.  

--============ Session_1 (started at 13:14:12) ============--
13:14:02 SQL> exec :ret := test_build_time(40.2);
Elapsed: 00:00:40.20
13:14:56 SQL>

--============ Session_2 (started at 13:14:20) ============--
13:12:57 SQL> exec :ret := test_build_time(40.2);
Elapsed: 00:01:13.21   -- timeout(33) + build_time(40.2) = 73.2 = 01:13.21
13:15:33 SQL>

Case_6. build_time=40.1 > 33, Session_2 starts 12 seconds aftet Session_1.


-- Session_2 started within NEW phase of Session_1.
-- Item ready in Session_1 at 12:24:39. 
-- Session_2's timeout at 12:24:11 + 33 =  12:24:43.
-- (12:24:39 < 12:24:43): Session_2 can get result before timeout.  

--============ Session_1 (started at 12:23:59) ============--
12:19:15 SQL> exec :ret := test_build_time(40.1);
Elapsed: 00:00:40.12
12:24:39 SQL>

--============ Session_2 (started at 12:24:11) ============--
12:19:15 SQL> exec :ret := test_build_time(40.1);
Elapsed: 00:00:28.71
12:24:39 SQL>

Case_7. build_time=40.3 > 33, Session_2 starts 35 seconds aftet Session_1.


-- Session_2 started within BYPASS phase of Session_1.
-- Item ready in Session_1 at 13:39:42. 
-- Session_2's timeout at 13:39:37 + 33 =  13:40:10.
-- 13:39:42 < 13:40:10): Session_2 can get result before timeout.  

--============ Session_1 (started at 13:39:02) ============--
13:38:39 SQL > exec :ret := test_build_time(40.3);
Elapsed: 00:00:40.30
13:39:42 SQL >

--============ Session_2 (started at 13:39:37) ============--
13:38:32 SQL > exec :ret := test_build_time(40.3);
Elapsed: 00:00:40.30
13:40:17 SQL >
In summary, it looks like:
   When Session_2 started within NEW phase of Session_1, it experienced a long wait if it can not get the result built by Session_1
   within its timeout, for example, Case_2 (_result_cache_timeout=10) and Case_5 (_result_cache_timeout=33).

   When Session_2 started within BYPASS phase of Session_1, it built the RC item itself, hence "BYPASS" the Session_1's Build.
   It took exact the specified time, for example, Case_3 (_result_cache_timeout=10) and Case_7 (_result_cache_timeout=33).

If we repeat the test of Section 4, trace file will show "timeout=33":

ksqgtl *** RC-00000001-00000001-00000000-00000000 mode=4 flags=0x10000 timeout=33 ***
By the way, above tests showed that Result Cache can not be used in "Delay" (or "TIMEOUT") functions since only Session_1 has a specified duration in the first time, all other function calls take (almost) 0 time, or nondeterministic time.

Saturday, July 28, 2018

row cache mutex in Oracle 12.2.0.1.0

In Oracle 12.2.0.1.0 (12cR2), "row cache mutex" replaced 12.1.0.2.0 (12cR1) "latch: row cache objects", similar to "latch: library cache" substitution by "library cache: mutex X" in the previous release.

Oracle Global Customer Support Document: Oracle Database 12c Release 2 Support Lessons Learned from Early Adopters wrote:
    "row cache mutex" replaces "latch: row cache objects" but rarely seen

As a start point to be acquainted with this new mechanism, this Blog takes the same Test Code in Blog: nls_database_parameters, dc_props, latch: row cache objects to focus only on Dictionary Cache dc_props.

We will run tests in both 12cR2 and 12cR1 by varying the number of parallel sessions, compare their behaviors, and then try to measure their scalability.

Note: All tests are done in 12.2.0.1.0 (12cR2) and 12.1.0.2.0 (12cR1) on Linux with 6 physical processors.

Update: (31-Aug-2021): "row cache mutex" seems caused hot objects, see:

  Bug 31135517 - High row cache mutex waits on hot objects - superseded (Doc ID 31135517.8)
  
  Description
      This problem is introduced in release 12.2 when latches are replaced with the introduction
      of row cache mutexes.
       
      In nearly all cases, the mutex architecture is better because the locking can be targeted at
      the object level. Because the mutexes are embedded within the objects themselves, there are
      no false conflicts caused by sessions trying to lock different objects within the same cache.
      So in general, contention is improved.
       
      It is unfortunate that where  one or two very 'hot' row cache objects exist(s), the previous
      architecture is better because the current architecture requires two mutex compared to one
      latch get.  The reasons for this are complex, but it is primarily because many row cache objects
      have multiple keys, and are linked into the hash table on multiple hash chains. The potential for
      contention is increased because the two mutex gets introduce multiple choke points.
       
      The fix implements 'hot object cloning' to alleviate row cache mutex  contention.  
       
      Stack from hang analyze trace will likely contain:
      ... kslwaitctx ksfwaitctx kgxWait kgxExclusive kqrGetMutexByAddr ..


1. 12cR2 "row cache mutex" vs. 12cR1 "latch: row cache objects"


The new Event "row cache mutex" is exposed with row cache parameters:

  P1TEXT = cache id 
  P2TEXT = where requested

select event, p1text, p1, p2text, p2, p3text, p3
  from v$session v where event in ('row cache mutex');
  
EVENT            P1TEXT    P1  P2TEXT           P2  P3TEXT  P3
---------------  --------  --  ---------------  --  ------  --
row cache mutex  cache id  15  where requested  19          0
"cache id" can be used to directly pinpoint the exact contention row cache objects.

Whereas in lower Releases, Event "latch: row cache objects" is exposed with latch parameters, and v$latch_children.child# is used to join with x$kqrst.kqrstcln and filtered by v$latch_children.addr = :P1RAW to find row cache info.

select event, p1text, p1, p1raw, p2text, p2, p2raw, p3text, p3, p3raw
  from v$session v where event in ('latch: row cache objects');

EVENT                     P1TEXT   P1          P1RAW             P2TEXT  P2   P2RAW             P3TEXT  P3  P3RAW
------------------------  -------  ----------  ----------------  ------  ---  ----------------  ------  --  -----
latch: row cache objects  address  5970309816  0000000163DBB2B8  number  411  000000000000019B  tries   0   00

select la.child# latch_child, kqrstcid cache#, kqrsttxt parameter, la.addr latch_addr --, dc.*, la.*
from x$kqrst dc, v$latch_children la 
where dc.kqrstcln = la.child# 
  and la.name     = 'row cache objects'
  and la.addr     = '0000000182D98958'; 

LATCH_CHILD  CACHE#  PARAMETER  LATCH_ADDR
-----------  ------  ---------  ----------------
18           15      dc_props   0000000163DBB2B8
12cR2 v$rowcache contains 71 rows, one new dictionary cache: 'dc_cdbprops' is added.

By the way, 'row cache objects' LATCH# is numbered as "506" (12cR1 is "411"), but hardly used.

select name, latch# from v$latch where name in ('row cache objects');

NAME               LATCH#  GETS
-----------------  ------  ----
row cache objects  506     456


2. Test


Run following test script in 12cR2 and 12cR1 on a Linux with 6 physical processors.
The script consists of 8 cases with 1, 2, 3, 4, 5, 6, 9 and 12 parallel sessions respectively.
Each case runs for 10 minutes.

declare
  type    t_num_tab is table of number;
  l_nums  t_num_tab := new t_num_tab(1, 2, 3, 4, 5, 6, 9, 12);
begin
  clean_jobs; 
  sys.dbms_workload_repository.create_snapshot('ALL');
  
  for n in 1..l_nums.count loop
    nls_select_jobs(l_nums(n), 1e9);    -- n Sessions
    dbms_lock.sleep(600);               -- 10 minutes
    clean_jobs; 
    sys.dbms_workload_repository.create_snapshot('ALL');
  end loop;
end;
/  


3. Test Result


After test, collect all 8 AWR reports in 12cR2 and 12cR1, extract relevant 4 Sections to compare.


3.1 12cR2


Top 10 Foreground Events by Total Wait Time

Session# Event            Waits     Total Wait Time (sec)   Avg Wait   % DB time  Wait Class
-------- ---------------  --------  ---------------------  --------   ---------   -----------
1        row cache mutex  9         0                       10.33us    0.0        Concurrency
2        row cache mutex  12,836    18                      1.46ms     1.6        Concurrency
3        row cache mutex  48,390    111                     2.31ms     5.9        Concurrency
4        row cache mutex  111,312   280                     2.52ms     10.5       Concurrency
5        row cache mutex  199,253   491                     2.47ms     14.6       Concurrency
6        row cache mutex  322,542   886                     2.75ms     20.1       Concurrency
9        row cache mutex  418,819   3030                    7.24ms     35.6       Concurrency
12       row cache mutex  573,556   4673                    8.15ms     38.7       Concurrency

SQL ordered by Elapsed Time
(SQL Id:   7mgsfc44trnr8
 SQL Text: SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET')

Session# Elapsed Time (s) Executions  Elapsed Time per Exec (s)  %Total  %CPU     %IO 
-------- ---------------- ----------  -------------------------  ------  -------  ----
1        498              3,174,194   0.00                       81.47   100.40   0.00
2        983              5,876,575   0.00                       83.48   98.52    0.00
3        1,515            8,383,434   0.00                       79.99   93.22    0.00
4        2,087            10,518,830  0.00                       77.82   87.48    0.00
5        2,533            11,688,284  0.00                       74.97   81.51    0.00
6        3,112            12,242,878  0.00                       70.67   71.93    0.00
9        4,894            9,121,274   0.00                       57.49   34.12    0.00
12       6,685            9,300,453   0.00                       55.32   25.77    0.00

Mutex Sleep Summary 

Session# Mutex Type  Location     Sleeps     Wait Time (ms)
-------- ----------  -----------  ---------  --------------
1        Row Cache   [19] kqrpre  12         0   
2        Row Cache   [19] kqrpre  16,734     18,744  
3        Row Cache   [19] kqrpre  70,398     108,322
4        Row Cache   [19] kqrpre  164,963    266,506
5        Row Cache   [19] kqrpre  290,989    466,843
6        Row Cache   [19] kqrpre  478,910    831,835
9        Row Cache   [19] kqrpre  890,437    2,929,696   
16       Row Cache   [19] kqrpre  1,268,073  4,499,378

1        Row Cache   [13] kqreqd  8          0 
2        Row Cache   [13] kqreqd  415        75
3        Row Cache   [13] kqreqd  1,536      300
4        Row Cache   [13] kqreqd  4,555      850
5        Row Cache   [13] kqreqd  9,913      1,625
6        Row Cache   [13] kqreqd  27,487     3,565
9        Row Cache   [13] kqreqd  20,223     14,429
16       Row Cache   [13] kqreqd  24,497     22,772

(Row Cache  [19] kqrpre: find obj 
 Row Cache  [13] kqreqd: reget)

Dictionary Cache Stats

Session# Cache     Get Requests  Pc t Miss  Scan Reqs  Pct Miss  Mod Reqs  Final Usage
-------- --------  ------------  ---------  ---------  --------  --------  -----------
1        dc_props  222,194,045   0.00       0                    0         70
2        dc_props  406,987,135   0.00       0                    0         70
3        dc_props  572,806,160   0.00       0                    0         70
4        dc_props  708,102,414   0.00       0                    0         70
5        dc_props  776,478,519   0.00       0                    0         70
6        dc_props  807,080,147   0.00       0                    0         70
9        dc_props  605,040,134   0.00       0                    0         70
12       dc_props  616,520,863   0.00       0                    0         70


3.2 12cR1


Top 10 Foreground Events by Total Wait Time

Session# Event                      Waits      Total Wait Time (sec)  Avg Wait(ms)   % DB time   Wait Class
-------- ------------------------   ---------  ---------------------  ------------   ---------   -----------
1        latch: row cache objects   12         0                      0.02           0.0         Concurrency
2        latch: row cache objects   19,775     1                      0.03           0.1         Concurrency
3        latch: row cache objects   259,069    6                      0.02           0.3         Concurrency
4        latch: row cache objects   612,329    16                     0.03           0.7         Concurrency
5        latch: row cache objects   1,008,481  36                     0.04           1.2         Concurrency
6        latch: row cache objects   1,189,823  116                    0.10           3.1         Concurrency
9        latch: row cache objects   1,036,139  2441                   2.36           30.2        Concurrency
12       latch: row cache objects   948,170    3861                   4.07           34.5        Concurrency

SQL ordered by Elapsed Time

Session# Elapsed Time (s)  Executions  Elapsed Time per Exec (s)  %Total  %CPU    %IO 
-------- ----------------  ----------  -------------------------  ------  ------  ----
1        506               6,164,161   0.00                       85.76   100.53  0.00
2        1,056             10,848,660  0.00                       88.01   100.32  0.00
3        1,614             9,977,224   0.00                       91.07   99.91   0.00
4        2,227             8,172,259   0.00                       93.72   99.69   0.00
5        2,843             6,951,871   0.00                       94.78   99.06   0.00
6        3,493             6,890,154   0.00                       93.93   96.45   0.00
9        5,243             7,245,168   0.00                       64.80   49.04   0.00
12       6,728             7,562,209   0.00                       60.19   36.65   0.00

Latch Sleep Breakdown

Session# Latch Name         Get Requests   Misses       Sleeps     Spin Gets
-------- -----------------  -------------  -----------  ---------  -----------
1        row cache objects  1,109,597,151  633          14         619   
2        row cache objects  1,953,334,110  105,279,309  20,757     105,259,556
3        row cache objects  1,796,543,339  378,078,181  269,547    377,819,261
4        row cache objects  1,471,777,609  541,981,017  632,528    541,369,153
5        row cache objects -3,043,081,012  596,060,413  1,034,539  595,053,127
6        row cache objects  1,229,364,378  689,496,609  1,200,975  688,326,423
9        row cache objects  1,304,630,648  561,205,207  1,091,371  560,171,774
16       row cache objects  1,361,872,758  607,120,518  1,007,494  606,174,258

 * -3,043,081,012 is due to 32-bit overflow, it should be 1,251,886,284 (=2^32 - 3,043,081,012).

Dictionary Cache Stats

Session# Cache     Get Requests  Pc t Miss  Scan Reqs  Pct Miss  Mod Reqs  Final Usage
-------- --------  ------------  ---------  ---------  --------  --------  -----------
1        dc_props  369,850,105   0.00       0                    0         60
2        dc_props  651,060,932   0.00       0                    0         60
3        dc_props  598,832,676   0.00       0                    0         60
4        dc_props  490,574,970   0.00       0                    0         60
5        dc_props  417,291,212   0.00       0                    0         60
6        dc_props  413,622,949   0.00       0                    0         60
9        dc_props  434,872,658   0.00       0                    0         60
12       dc_props  453,953,683   0.00       0                    0         60


4. Discussions


4.1 Top 10 Foreground Events by Total Wait Time


    12cR2 "row cache mutex" has about 2 times less "Waits" than 12cR1 "latch: row cache objects".
    That is the advantage of light-weight "Mutex" over "Latch".
    "Total Wait Time" and "Avg Wait" increase linearly when Session# <= 6 (number of physical processors), non-linearly when > 6.
    12cR2 "row cache mutex" has higher concurrency ("% DB time") than 12cR1 "latch: row cache objects".

    Strangely 12cR2 Mutex "Avg Wait" is much higher than that of 12cR1 Latch.


4.2 SQL ordered by Elapsed Time


    "Executions" in 12cR2 reaches maximum at Session# = 6, 20% higher than 12cR1 peak value at Session# = 2.
    It means 12cR1 can use less CPU resource to reach its peak value.

    12cR1 has higher throughput than 12cR2 when less Sessions (less than 4); but lower when more Sessions.


4.3 Mutex Sleep Summary vs Latch Sleep Breakdown


    12cR2 "row cache mutex" Sleeps and Wait Time increase non-linearly.
    12cR1 "latch: row cache objects" Misses and Sleeps increase non-linearly although "Get Requests" are almost similar.

    In 12cR1, "Waits" in AWR "Top 10 Foreground Events by Total Wait Time" is close to "Sleeps" in "Latch Sleep Breakdown".
    in 12cR2, it is far from "Sleeps" in "Mutex Sleep Summary".


4.4 Dictionary Cache Stats


    dc_props "Get Requests" depends on query "Executions".
    Each query (SQL Id: 7mgsfc44trnr8) Execution triggers 70 "Get Requests" in 12cR2, and 60 in 12cR1
    since dc_props rows (Field "Final Usage") increased from 60 in 2cR1 to 70 in 12cR2.


5. Test Code


create or replace procedure nls_select(p_cnt number) as
  l_val VARCHAR2 (256 Byte);
begin
for i in 1..p_cnt loop
  select value into l_val from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
end loop;
end;
/

create or replace procedure nls_select_jobs(p_job_cnt number, p_cnt number) as
  l_job_id pls_integer;
begin
  for i in 1.. p_job_cnt loop
    dbms_job.submit(l_job_id, 'begin while true loop nls_select('||p_cnt||'); end loop; end;');
  end loop;
  commit;
end;    
/

create or replace procedure clean_jobs as
begin
  for c in (select job from dba_jobs) loop
    begin
       dbms_job.remove (c.job);
    exception when others then null;
    end;
    commit;
  end loop;

  for c in (select d.job, d.sid, (select serial# from v$session where sid = d.sid) ser 
              from dba_jobs_running d) loop
    begin
      execute immediate
             'alter system kill session '''|| c.sid|| ',' || c.ser|| ''' immediate';
      dbms_job.remove (c.job);
    exception when others then null;
    end;
    commit;
  end loop;
  
  dbms_lock.sleep(2);
  
  -- select * from dba_jobs;
  -- select * from dba_jobs_running;
end;
/

-- exec nls_select_jobs(2, 1e9);
-- exec clean_jobs; -- stop all jobs