Thursday, March 27, 2014

dbms_aq.dequeue - latch: row cache objects on AIX

dbms_aq.dequeue uses an ADT object_type to pick payload from queue, and this can cause a heavy
   latch: row cache objects (latch#: 280 and child#: 8)

which is used to protect Row Cache "dc_users" (cache# 10 and 7 in v$rowcache) in a CPU overloaded AIX System with Oracle 11.2.0.3.0.

At first, let's look at the special behaviour of dbms_aq.dequeue (see appended TestCase):
 exec qstat;
 exec deq(1, 5);
 exec qstat;


The output looks like:
 SQL> exec qstat;
  LOCKED_TOTAL=9,   PINNED_TOTAL=9
  SQL_TEXT=select , EXECUTIONS=19, ROWS_PROCESSED=3
  SQL_TEXT=insert , EXECUTIONS=3,  ROWS_PROCESSED=3
 SQL> exec deq(1, 5);
 SQL> exec qstat;
  LOCKED_TOTAL=10,  PINNED_TOTAL=10
  SQL_TEXT=select , EXECUTIONS=22, ROWS_PROCESSED=3
  SQL_TEXT=insert , EXECUTIONS=3,  ROWS_PROCESSED=3


It shows that the dequeue SELECT executed 3 times, and the LOCKED_TOTAL on the payload object: qtab_obj increased 1. By a 10046 event trace, we  can see that two selects were run immediately after dequeue, and the third one was after 5 seconds, and finally throws: "ORA-25228: timeout".

By enqueuing one message, and then dequeue it:
 exec qstat;
 exec enq(1);
 exec qstat;
 exec deq(1, 5);
 exec qstat;


The output is:
 SQL> exec qstat;
  LOCKED_TOTAL=12,  PINNED_TOTAL=12
  SQL_TEXT=select , EXECUTIONS=23, ROWS_PROCESSED=4
  SQL_TEXT=insert , EXECUTIONS=4,  ROWS_PROCESSED=4
 SQL> exec enq(1);
 SQL> exec qstat;
  LOCKED_TOTAL=13,  PINNED_TOTAL=13
  SQL_TEXT=select , EXECUTIONS=23, ROWS_PROCESSED=4
  SQL_TEXT=insert , EXECUTIONS=5,  ROWS_PROCESSED=5
 SQL> exec deq(1, 5);
 SQL> exec qstat;
  LOCKED_TOTAL=14,  PINNED_TOTAL=14
  SQL_TEXT=select , EXECUTIONS=24, ROWS_PROCESSED=5
  SQL_TEXT=insert , EXECUTIONS=5,  ROWS_PROCESSED=5


We can see 1 enqueue INSERT, 1 dequeue SELECT, and LOCKED_TOTAL increased 1 for enqueue, and 1 for dequeue.

Based on this observation, probably we can reconstruct one dbms_aq.dequeue pseudo code:
 declare
  l_rec qtab%rowtype;
   cursor c is select  /*+ INDEX(TAB AQ$_QTAB_I) */ ... for update skip locked;
 begin
  open c;

  -- 1st fetch
  fetch c into l_rec;
  if c%found then return; end if;

  -- 2nd fetch
   fetch c into l_rec;
  if c%found then return; end if;

  -- thread_wait with timeout 5 seconds.

  -- Within 5 seconds, if there is message available, waked up by a thread_post.
  thread_wait(5);

  fetch c into l_rec;
  if c%found then return; end if;

  finally close c;
          throw ORA-25228;
 end;


By the way, "skip locked" option seems originally designed for AQ, and this select can not guarantee the consistency due to "skip locked" rows which are not deterministic to the result set.

Now we run a CPU intensive test on AIX Power7 System with Entitled Capacity 4, SMT=4 and Oracle 11.2.0.3.0:

 exec loop_job_cpu_burning(48);
 exec loop_job_enq(48, 0.01);
 exec loop_job_deq(48, 1);


Then repeatedly run:
 select event, v.* from v$active_session_history v where p2 = 280 order by sample_time desc;
 select event, v.* from v$session v where p2 = 280;
 select * from v$latch_children where latch# = 280 and child# in (8);
 select * from v$latchholder;


we will observe:
  latch: row cache objects

To make this event more noticeable, launch some dequeue jobs with NO_WAIT:
  exec loop_job_deq(16, 0);

then there will be even more latch misses and sleeps (also some other events: "latch: cache buffers chains", "buffer busy waits", "redo copy").

This also indicates that if dequeue is faster than enqueue, a higher dequeue wait will reduce CPU load because each empty dequeue also triggers three dequeue select, and hence locks on payload object_type.

By running,
 select locked_total, pinned_total, locks, pins, v.*
 from v$db_object_cache v
 order by v.locked_total desc;


we can notice huge LOCKED_TOTAL on payload object_type: "QTAB_OBJ". This is probably the root cause of "latch: row cache objects" caused by dbms_aq.dequeue.

In fact, it was first discovered by a 10046 Event Trace on a dequeue session and a system library_cache dump with:
  alter session set events 'immediate trace name library_cache level 10';

Once the test is finished, remove all the jobs by:
  exec clean_job;

UNIX Process Monitoring


By AIX truss or trace on a dequeue session process, we can observe continuous
  thread_wait, thread_post
to sysnchonize IPC mechanism, but hardly see real work (for example: kread).

Running ps on a dequeue session process a few times (for example, PID 31785184):
 ps -flp 31785184
it shows that both Column C (CPU utilization) and PRI (priority) are changed each time. And IBM AIX document states:

for the sched_other policy (AIX default), CPU utilization is used in determining process scheduling priority. Large values indicate a CPU intensive process and result in lower process priority, whereas small values indicate an I/O intensive process and result in a more favorable priority.

One reasonable explanation from UNIX level is that the process holds the single:
  latch: row cache objects (latch#: 280 and child#: 8)
consumes a certain CPU (spin_gets), and was evaluated to a lower priority, yield CPU to others, and is not able to release this single latch. Whereas the other processes which are desperately requesting this latch are starving the CPU, but can't get this latch. And finally it results in a single latch contention, and all processes are serialized by this single Oracle resource.

We often see this latch taking on average several ms (3-40 ms), and occasionally more than one hour (that contradicts the common believe that latch is a short activity).

As we know that AIX Schedo Dispatcher uses a timeslice to choose the next processes.The default value for timeslice is a single clock tick, that is, 10 milliseconds.

One possible guess for the huge LOCKED_TOTAL on payload object_type: "QTAB_OBJ" is that AIX run-time linking problem in which each dequeue action requires a lock on "QTAB_OBJ" object file (or shared library (SO)).

Bug 14382262  latch free wait event in"kokc descriptor allocation latch"


Oracle seems aware of this huge LOCKED_TOTAL and published a patch to fix it. As tested, the patch stopped the increasing of LOCKED_TOTAL, but "latch: row cache objects" gets even worse. So all the tests in this Blog is without this patch.

latchstat tool


Imitated from vmstat, a small script: latchstat is created to to monitor latch activities, for example:
  exec latchstat(4, 1, 280, 8);

(code is appended at the end of Blog).

---------------------- setup ---------------------- 
create or replace type qtab_obj is object (id integer, pr_id integer);
/      

begin sys.dbms_aqadm.drop_queue_table(queue_table => 'QTAB', force=> TRUE); end;
/

begin
  sys.dbms_aqadm.create_queue_table
  (
    queue_table           => 'QTAB'
   ,queue_payload_type    => 'QTAB_OBJ'
   ,compatible            => '8.1'
   ,sort_list             => 'PRIORITY,ENQ_TIME'
   ,multiple_consumers    =>  false                
   ,message_grouping      =>  0
   ,comment               =>  'Test Queue Table'
   ,secure                =>  false
   );
end;
/

begin
  sys.dbms_aqadm.stop_queue (queue_name => 'QTAB_QUEUE');
  sys.dbms_aqadm.drop_queue (queue_name => 'QTAB_QUEUE');
end;
/

begin
  sys.dbms_aqadm.create_queue
  (
    queue_name     => 'QTAB_QUEUE'
   ,queue_table    => 'QTAB'
   ,queue_type     =>  sys.dbms_aqadm.normal_queue
   ,max_retries    =>  100
   ,retry_delay    =>  2
   ,retention_time =>  604800
   ,comment        => 'Test Queue'
   );
end;
/

begin sys.dbms_aqadm.start_queue(queue_name => 'QTAB_QUEUE', enqueue => true, dequeue => true); end;
/


---------------------- queuing ----------------------   
create or replace procedure cpu_work(p_cnt number) as
  l_x number;
begin
  for i in 1..p_cnt loop
    l_x := l_x + sqrt(i);
  end loop;
end;
/
create or replace procedure enq(p_cnt number, p_sleep_second number := 0.01) as
  l_enqueue_options     dbms_aq.enqueue_options_t;
  l_message_properties  dbms_aq.message_properties_t;
  l_message_id          raw(16);
  l_message             qtab_obj;
begin
  cpu_work(1000);
  for i in 1..p_cnt loop
    l_message := qtab_obj(i, 2*i);
    if p_sleep_second > 0 then dbms_lock.sleep(p_sleep_second); end if;
    dbms_aq.enqueue(queue_name            => 'QTAB_QUEUE',
                    enqueue_options       => l_enqueue_options,
                    message_properties    => l_message_properties,
                    payload               => l_message,
                    msgid                 => l_message_id);
    commit;
  end loop;
  cpu_work(1000);
end;
/
         
create or replace procedure deq(p_cnt number, p_wait_second binary_integer := 1) as
  l_dequeue_options     dbms_aq.dequeue_options_t;
  l_message_properties  dbms_aq.message_properties_t;
  l_message_id          raw(16);
  l_message             qtab_obj := qtab_obj(null, null);
begin
  l_dequeue_options.wait       := p_wait_second;
  cpu_work(1000);  
  for i in 1..p_cnt loop
   begin 
     dbms_aq.dequeue(queue_name            => 'QTAB_QUEUE',
                     dequeue_options       => l_dequeue_options,
                     message_properties    => l_message_properties,
                     payload               => l_message,
                     msgid                 => l_message_id);
     commit;
    exception when others then null;
   end;
  end loop;
  cpu_work(1000);
end;         
/         
create or replace procedure loop_job_enq(p_job_cnt number, p_sleep_second number := 0.01)
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 enq(100, '|| p_sleep_second ||'); end loop; end;');
    end loop;
    commit;
end;   
/
create or replace procedure loop_job_deq(p_job_cnt number, p_wait_second binary_integer := 1)
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 deq(100, '|| p_wait_second ||'); end loop; end;');
    end loop;
    commit;
end;   
/
create or replace procedure loop_job_cpu_burning (p_job_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 null; end loop; end;');
    end loop;
    commit;
end;   
/
create or replace procedure qstat as
  l_txt varchar2(100);
begin
  select 'QTAB_TOTAL= '||count(*)||', '||'READY='||count(decode(state, 0, 1))||', At: '||systimestamp
  into l_txt from QTAB;
  dbms_output.put_line(l_txt);

  select 'LOCKED_TOTAL='||locked_total||',   '||'PINNED_TOTAL='||pinned_total into l_txt
  from v$db_object_cache v where name in ('QTAB_OBJ');
  dbms_output.put_line(l_txt);
 
  -- 313buw98w5y1a  insert into "K"."QTAB"
  -- 3yxj9h80vc0jw  select  /*+ INDEX(TAB AQ$_QTAB_I) */ ... for update skip locked 
   
  for c in (select substr(sql_text, 1, 7) sql_text, executions, rows_processed
            from v$sql v where sql_id in ('313buw98w5y1a','3yxj9h80vc0jw') and executions > 0)
  loop
    dbms_output.put_line('SQL_TEXT='||c.sql_text||', '||'EXECUTIONS='||c.executions
                                    ||', '||'ROWS_PROCESSED='||c.rows_processed);
  end loop;
end;
/
create or replace procedure clean_job as
begin
  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;
 
  for c in (select job from dba_jobs) loop
    begin
      dbms_job.remove(c.job);
    exception when others then null;
    end;
    commit;
  end loop;
end;
/

---------------------- latchstat---------------------- 
set echo on
drop type t_latch_rec_obj force;
create or replace type t_latch_rec_obj as object(
      snap                    number
     ,tim                     timestamp
     ,name                    varchar2(30)
     ,v1                      number
     ,v2                      number
     ,v3                      number
     ,v4                      number
     ,v5                      number
     ,v6                      number
     ,v7                      number
     ,v8                      number
     ,v9                      number
     ,v10                     number
    );
/
   
create or replace type t_lacth_rec_tab as table of t_latch_rec_obj;
/

create or replace procedure latchstat(cnt number, interval number, latch_num number, child_num number := null) as
 l_col_len_s       pls_integer := 8;
 l_col_len_l       pls_integer := 16;
  l_rec_tab         t_lacth_rec_tab := t_lacth_rec_tab();
  l_rec_tab_child   t_lacth_rec_tab := t_lacth_rec_tab();
  l_latch_name      varchar2(20);
  l_children_cnt    pls_integer;
  l_top_5_children  varchar2(100);
begin
 
 select max(name), count(*)
 into  l_latch_name, l_children_cnt
 from v$latch_children v where latch# in (latch_num); 
  
 select listagg(child# ||'('|| sleeps ||')', ' / ') within group (order by wait_time desc) child_sleeps
 into   l_top_5_children
 from (select * from v$latch_children v where latch# in (latch_num) order by wait_time desc)
 where rownum <= 5;

  dbms_output.put_line('  Latch: '                 || l_latch_name
                    || ', Children#: '             || l_children_cnt
                    || ', Top 5 Children(Sleeps): '|| l_top_5_children
                    || ', At: '                    || systimestamp);
                 
 dbms_output.put_line(null);                 
 
  dbms_output.put_line(lpad('P_gets', l_col_len_s)
                 ||lpad('misses', l_col_len_s)
                 ||lpad('sleeps', l_col_len_s)
                 ||lpad('spin_gets', l_col_len_l)
                 ||lpad('wait_time(ms)', l_col_len_l)
                 ||lpad('avg_wait_time', l_col_len_l)
                 ||lpad(' | ', l_col_len_s)
                 ||lpad('C_gets', l_col_len_s)
                 ||lpad('misses', l_col_len_s)
                 ||lpad('sleeps', l_col_len_s)
                 ||lpad('spin_gets', l_col_len_l)
                 ||lpad('wait_time(ms)', l_col_len_l)
                 ||lpad('avg_wait_time', l_col_len_l)
                 );
                              
  for i in 1..cnt loop
    select t_latch_rec_obj(i, systimestamp, l.name, l.gets, l.misses, l.sleeps, l.spin_gets, l.wait_time, k.gets, k.misses, k.sleeps, k.spin_gets, k.wait_time)
    bulk collect into l_rec_tab
    from v$latch l, v$latch_children k
    where l.latch#  = latch_num
     and l.latch#  = k.latch#(+)
      and child_num = k.child#(+);
   
    dbms_lock.sleep(interval);
   
    -- only select one Row --
    for c in
      (select l.name, v1.snap, v1.tim snap_start, systimestamp snap_end
             ,(l.gets - v1.v1) l_get_d, (l.misses - v1.v2) l_misses_d, (l.sleeps - v1.v3) l_sleeps_d
             ,(l.spin_gets - v1.v4) l_spin_gets_d, round((l.wait_time - v1.v5)/1000) l_wait_time_d
             ,(case when (l.gets - v1.v1) > 0 then round((l.wait_time - v1.v5)/(l.gets - v1.v1), 2)
                    else null
               end) l_avg_wait_time_d
             ,(k.gets - v1.v6) k_get_d, (k.misses - v1.v7) k_misses_d, (k.sleeps - v1.v8) k_sleeps_d
        ,(k.spin_gets - v1.v9) k_spin_gets_d, round((k.wait_time - v1.v10)/1000) k_wait_time_d
        ,(case when (k.gets - v1.v6) > 0 then round((k.wait_time - v1.v10)/(k.gets - v1.v6), 2)
               else null
          end) k_avg_wait_time_d
       from   table(l_rec_tab) v1, v$latch l, v$latch_children k
       where  l.latch#  = latch_num
         and  l.latch#  = k.latch#(+)
         and  child_num = k.child#(+)
      )
    loop
      -- Parent -- 
      dbms_output.put(  lpad(c.l_get_d, l_col_len_s)
                      ||lpad(c.l_misses_d, l_col_len_s)
                      ||lpad(c.l_sleeps_d, l_col_len_s)
                      ||lpad(c.l_spin_gets_d, l_col_len_l)
                      ||lpad(c.l_wait_time_d, l_col_len_l)
                      ||lpad(c.l_avg_wait_time_d, l_col_len_l));
         
      -- Child --  
   dbms_output.put(  lpad(' | ', l_col_len_s)
                   ||lpad(c.k_get_d, l_col_len_s)
                   ||lpad(c.k_misses_d, l_col_len_s)
                   ||lpad(c.k_sleeps_d, l_col_len_s)
                   ||lpad(c.k_spin_gets_d, l_col_len_l)
                   ||lpad(c.k_wait_time_d, l_col_len_l)
                   ||lpad(c.k_avg_wait_time_d, l_col_len_l));
         
    dbms_output.put_line(null);       
    end loop;
   
  end loop;
end;
/


/*
 ----- Usage latchstat(cnt, interval, latch_num, child_num) ------
 ----- Example (latch_num=280: "row cache objects", child_num=8: "dc_users" ) ------

  exec latchstat(4, 1, 280);
  exec latchstat(4, 1, 280, 8);
*/

set serveroutput on
set echo off

Tuesday, March 11, 2014

Hot Block Identification and latch: cache buffers chains

When Hot Block Event:
    latch: cache buffers chains
    buffer busy waits
occurs intensively, system performance is sluggish and CPU starved.
Locating Hot Block originals could be the first step before tuning system and SQL statements.
This Blog will first show 2 queries to list the Hot Blocks, and 1 query to connect Hot Blocks and their fetching Sessions. Then try to demonstrate some Workaround and Fixes.

Note. All tests are done in Oracle 12.1.0.2.0 and 11.2.0.3.0


1. Hot Block x$bh Query



with sq as (select object_name, data_object_id from dba_objects)
select hladdr cbc_latch_addr
      ,sum(tch) tch_sum
      ,listagg(tch || '-' || obj || '(' || object_name || ')/' || file# || '/' ||dbablk, ';') 
         within group (order by tch desc)  "tch-obj(name)/file/blk_list"
      ,count(*) protected_db_blocks_cnt
from  x$bh b, sq
where b.obj = sq.data_object_id
  and tch > 0
group by hladdr
order by tch_sum desc;


2. Hot Block Tracking Query



alter system set "_db_hot_block_tracking"=true;

with sq as (select object_name, data_object_id from dba_objects)    
select object_name ,h.*, b.*
  from x$kslhot h, v$bh b, sq
where b.block# = h.kslhot_id
  and b.objd   = sq.data_object_id
order by h.kslhot_ref desc, b.file#, b.block#; 
Once detecting the Hot Block, listing the rows in the block is straightforward. If the Hot Block belongs to a heap table, dbms_rowid.rowid_block_number will do that.

In case of Index block and IOT table block, an internal Oracle function can be used to map block to the entries. Voilà an example:

create table test_iot
(
  id number(9), name varchar2(10),  
  constraint test_iot_key
  primary key (id, name)
  enable validate
)
organization index;

insert into test_iot select level, 'test_'||level from dual connect by level < 100000;

commit;

select object_id from dba_objects where object_name = 'TEST_IOT_KEY';

--returns 

  278020
Now we can build the mapping between block# and rows:

select dbms_rowid.rowid_block_number(sys_op_lbid (278020, 'L', t.rowid))  block#
      ,t.*
  from test_iot t
 where id is not null or name is not null;
By the way, following short query can be used to check index quality:

select block#
      ,count(*) rows_per_block
from (
  select dbms_rowid.rowid_block_number(sys_op_lbid (278020, 'L', t.rowid))  block#
        ,t.*
    from test_iot t
   where id is not null or name is not null
)
group by block#
order by block#;  
as discussed in Blog: "Index Efficiency 3"


3. Hot Blocks and Sessions


We can connect Data Blocks with fetching Sessions via CBC Latch Address during the last 10 wait events as follows:

with lch_sid as 
 (select /*+ materialize */ event, p1, sid, sum(wait_time_micro) wait_time_micro, count(*) wait_cnt
   from v$session_wait_history 
  where (p2=228 or event = 'latch: cache buffers chains') group by event, p1, sid)
,lch as 
 (select /*+ materialize */ event, p1
       ,listagg(sid || '(' || wait_time_micro || '/' || wait_cnt || ')', ';')
          within group (order by wait_time_micro desc)  "sid(wait_time_micro/wait_cnt)"
    from lch_sid group by event, p1) 
select hladdr cbc_latch_addr, w.p1 latch_addr_number, sum(tch) tch_sum, count(*) protected_db_blocks_cnt
       ,w."sid(wait_time_micro/wait_cnt)"
       ,listagg(tch || '-' || b.obj || '(' || ob.object_name || ')/' || file# || '/' ||dbablk, ';') 
          within group (order by tch desc)  "tch-obj(name)/file/blk_list"
  from  x$bh b, dba_objects ob, lch w
 where b.obj = ob.data_object_id(+)
   and tch > 0
   and to_number(hladdr, 'XXXXXXXXXXXXXXXX') = w.p1
 group by hladdr, w.p1
 order by tch_sum desc; 


4. Workaround and Fixes


Hot Block is a phenomenon of frequently access / update of same DB Block concurrently by multiple sessions, exposed as "latch: cache buffers chains" (CBC Latch) contention.

DB Data Block is either Table Block or Index Block.


4.1 Table Block


We can use "MINIMIZE RECORDS_PER_BLOCK" to control the number of rows in each Block, one optimal value should be no more than "_db_block_max_cr_dba" (Maximum Allowed Number of CR buffers per dba), which is 6 in default (PCTFREE is not able to control the exact number of rows). For example,

drop table test_tab;
              
create table test_tab 
  INITRANS   26           -- prevent Segments ITL Waits and Deadlocks
as select level id, rpad('ABC', 10, 'X') val from dual connect by level <= 5;

alter table test_tab minimize records_per_block;

truncate table test_tab;

insert into test_tab select level id, rpad('ABC', 10, 'X') val from dual connect by level <= 100;

commit;
Then verify the number of entries per Block:

select block, count (*) cnt 
  from (select rowid rid, 
               dbms_rowid.rowid_object (rowid) object, 
               dbms_rowid.rowid_relative_fno (rowid) fil, 
               dbms_rowid.rowid_block_number (rowid) block, 
               dbms_rowid.rowid_row_number (rowid) ro, 
               t.* 
          from test_tab t 
         order by fil, block, ro) 
 group by block 
 order by block, cnt desc;
 

     BLOCK   CNT
  -------- -----
   1035776     5
   1035777     5
   1035778     5
   ... 
  20 rows selected.

select dbms_rowid.rowid_to_absolute_fno(t.rowid, 'K', 'TEST_TAB') af,
       dbms_rowid.rowid_relative_fno (rowid) rf,
       dbms_rowid.rowid_block_number (rowid) block,
       dbms_rowid.rowid_row_number(rowid)    row_no,
      (sys_op_rpb(rowid)) rpb,
       t.* 
  from test_tab t
 order by af, rf, block, rpb;

      AF  RF     BLOCK  ROW_NO  RPB   ID VAL
  ----- --- --------- ------- ---- ---- ----------
   1548   0   1035776       0    0   31 ABCXXXXXXX
   1548   0   1035776       1    1   32 ABCXXXXXXX
   1548   0   1035776       2    2   33 ABCXXXXXXX
   1548   0   1035776       3    3   34 ABCXXXXXXX
   1548   0   1035776       4    4   35 ABCXXXXXXX
   1548   0   1035777       0    0   36 ABCXXXXXXX
   ...
  100 rows selected. 
Blog: Spreading Out Data With MINIMIZE RECORDS_PER_BLOCK reveals the hidden mechanic ("Hakan factor") behind the scene:

select spare1
      ,least(1, bitand(spare1, (power(2, 15)))) 
         "16th bit Hakan factor"          -- "minimize records_per_block" flag
from sys.tab$ where obj# = (select obj# from sys.obj$ where name = 'TEST_TAB');

   SPARE1 16th bit Hakan factor
  ------- ---------------------
    32772                     1


4.2 Index Block


We need to use PCTFREE, and INITRANS to approximate "RECORDS_PER_BLOCK".

drop index test_tab#u1;

create unique index test_tab#u1 on k.test_tab (id) 
  pctfree    90
  initrans   26        -- prevent Segments ITL Waits and Deadlocks
;

exec dbms_stats.gather_table_stats(null, 'TEST_TAB', cascade => TRUE);
Then verify the number of entries per Block:

select object_id from dba_objects where object_name = 'TEST_TAB#U1';

-- return 2267423

select block#
      ,count(*) rows_per_block
from (
  select dbms_rowid.rowid_block_number(sys_op_lbid (2267423, 'L', t.rowid))  block#
        ,t.*
    from TEST_TAB t
   where id is not null
)
group by block#
order by block#; 

    BLOCK# ROWS_PER_BLOCK
  -------- --------------
   1035796              5
   1035797              5
   1035798              5
   ...
  20 rows selected.
Above method is not able to regulate exact number of index entries per block. Moreover, we need to check if both Leaf block and Branch block are subject to the same "PCTFREE, and INITRANS" conditions.

Dumping Leaf block and Branch block:

Leaf block dump
===============
header address 18446741324863441060=0xfffffd7ffc0108a4
kdxcolev 0              <<<--- index level (0 represents Leaf blocks)                                     
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 1
kdxcosdc 0
kdxconro 5              <<<--- number of index entries
kdxcofbo 46=0x2e
kdxcofeo 7401=0x1ce9
kdxcoavs 7355
kdxlespl 0
kdxlende 0
kdxlenxt 1035799=0xfce17
kdxleprv 1035797=0xfce15
kdxledsz 6
kdxlebksz 7456

kdxconro: number of index entries

Branch block dump
=================
header address 18446741324863505996=0xfffffd7ffc02064c
kdxcolev 1              <<<--- index level (1 represents Branch blocks)         
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 1
kdxcosdc 0
kdxconro 19              <<<--- number of index entries
kdxcofbo 66=0x42
kdxcofeo 7923=0x1ef3
kdxcoavs 7857
kdxbrlmc 1035796=0xfce14
kdxbrsno 0
kdxbrbksz 8056 
kdxbr2urrc 15
which shows the number of index entries:
  Leaf block    "kdxconro 5"    
  Branch block  "kdxconro 19"
So it seems that Branch block is not obliged to the same "PCTFREE, and INITRANS" as Leaf block.

Another observation is that the 5 Index Rows per Block is not persistent after Table is modified. That means some blocks can contain more or less than 5 Rows.


5. Index Hot Block Application Fix


Index Block can experience more acute CBC Latch contention because its size is usually smaller, and hence one block can contain more entries.

Since we have no way to apply "minimize records_per_block" to Index Blocks, in the application, we can try to minimize INDEX usage to cut down excess index access.

For example, by caching ROWID of previously fetched ROWs, at subsequent time, we can make the direct table fetch, and therefore bypass INDEX access:

declare
  l_id           number;
  l_val          varchar2(10);
  type tab_rowid is table of rowid index by pls_integer;
 rowid_cache    tab_rowid;
begin
  for k in 1..100 loop
    for j in 1..10 loop
      if rowid_cache.exists(k) then
        begin
          select /*+ not use index access */ id, val, rowid into l_id, l_val, rowid_cache(k) 
            from test_tab 
           where rowid = rowid_cache(k)
             and id    = k;
        exception when others then dbms_output.put_line('No_Data_Found: id='||k||', rowid='||rowid_cache(k));
        end;
      end if;
      
      if l_id is null or l_id != k then
        select /*+ use index access */ id, val, rowid into l_id, l_val, rowid_cache(k)  
          from test_tab 
         where id    = k;      
        dbms_output.put_line('Fill rowid_cache: id='||k||', rowid='||rowid_cache(k));
      end if; 
    end loop;
  end loop;
end;
/
Then we have two XPLANs: one single Run with Index reading 3 Blocks; other repeated Runs without Index reading one single Block.

  /*+ use index access */:     3 Block Reads (1 Branch block + 1 Leaf block + 1 Table Block)
  /*+ not use index access */: 1 Block Read  (1 Table Block)
  

SELECT /*+ use index access */ ID, VAL, ROWID FROM TEST_TAB WHERE ID = :B1

  Rows     Row Source Operation
  -------  ---------------------------------------------------
        1  TABLE ACCESS BY INDEX ROWID TEST_TAB (cr=3 pr=3 pw=0 time=145 us cost=1 size=14 card=1)
        1   INDEX UNIQUE SCAN TEST_TAB#U1 (cr=2 pr=2 pw=0 time=102 us cost=1 size=0 card=1)(object id 2267423)
      

SELECT /*+ not use index access */ ID, VAL, ROWID FROM TEST_TAB WHERE ROWID = :B1 AND ID = :B2
 
  Rows     Row Source Operation
  -------  ---------------------------------------------------
        1  TABLE ACCESS BY USER ROWID TEST_TAB (cr=1 pr=0 pw=0 time=10 us cost=1 size=14 card=1)

Wednesday, November 27, 2013

Remove Stale Native Code Files on AIX

3 days after posting Blog: PGA,
    SGA space usage watching from UNIX
about ipcs command for Oracle SGA, there was a problem reported on Oracle 11.2.0.3.0 caused by Stale Native Code Files.

Searching in Oracle Documentation, we found one hit:
    Oracle® Database Readme 11g Release 2 (11.2) E41331-02

    4.29.2 Stale Native Code Files Are Being Cached
  
    Natively compiled PL/SQL and native code generated by the JIT compiler for Oracle JVM, may be cached in operating system files. The SHUTDOWN ABORT and SHUTDOWN IMMEDIATE commands do not clean these cached native code files (reference Bug 8527383).

   The name patterns are as follows where sid_name is the system identifier name:
   JOXSHM_EXT_*_sid_name_*
   PESHM_EXT_*_sid_name_*
   PESLD_sid_name_*


However as observed with Oracle 11.2.0.3.0 on AIX 7.1.0.0, the name patterns looks like:

 /JOXSHM_EXT_*_instname_sidname
 /PESLD_instname_*_*


(sidname is the ID of shared memory segment)

(Update-2022-Dec-21: In Oracle 19c, the name pattern is like JOEZSHM_* instead of JOXSHM_EXT_*)

In fact, when summing up shared memory segments plus stale objects, it is more than Oracle reported SGA size since such stale objects are no more visible to Oracle. As a consequence, if there exist a large number of such Stale objects, Database can be made outages due to space starvation.

To this problem, Oracle provides certain workaround described in MOS: (Doc ID 1120143.1)

   Stale Native Code Files Are Being Cached with File Names Such as: JOXSHM_EXT*, PESHM_EXT*, PESLD* or SHMDJOXSHM_EXT*

but it is only applied to platforms except AIX since on AIX Stale Native Code Files such as (JOXSHM_EXT*) are kept as shared memory segments not as physical files, so there is no way to delete them by rm command.

Tests on AIX and Linux show the different shutdown behaviors:

 shutdown immediate: clean current stale files (created by this startup), but not previous ones.
                                           Probably because sidname does not belong to current running instance.
 shutdown normal:       same as shutdown immediate
 shutdown abort:         no clean

This Blog will try to provide a workaround for AIX by a small demo with detail steps.

(1). On AIX DB, run the appended TestCase.

(2). View all native and JIT compiled code by:

  select * from dba_plsql_object_settings where plsql_code_type = 'NATIVE';
 
  select * from dba_java_methods where is_native = 'YES';

  Here also two non-documented methods:

   select * from sys.ncomp_dll$ n, dba_objects o where n.obj# = o.object_id;

    call sys.dbms_feature_plsql_native to generate a plsqlNativeReport.
   
    declare
      l_is_used   number;
      l_aux_count number;
      l_report    clob;
    begin
      sys.dbms_feature_plsql_native(o_is_used=>l_is_used, o_aux_count=>l_aux_count, o_report=>l_report);
      dbms_output.put_line('o_is_used='||l_is_used);
      dbms_output.put_line('o_aux_count='||l_aux_count);
      dbms_output.put_line('o_report='||l_report);
    end;   

(3). Shutdown database by:

          shutdown abort

(4). On AIX, monitor memory usage:

 ipcs -ar |grep -e JOXSHM_EXT -e PESHM -e PESLD | awk '{cnt+=1; sum+=$10} END {print "Count=",cnt,"Sum=",sum,"Average=",sum/cnt}'

(5). remove them by:

 ipcs -ar |grep -e JOXSHM_EXT -e PESHM -e PESLD | awk ' {name = $17; cmd = "ipcrm -r -m " name; print "Run ", cmd; system( cmd )}'


Addendum (2016.01.21):

The above workaround seems also adopted by Oracle MOS: (Doc ID 1120143.1) in which there is one original text:
    There is no fix to remove the stale files.
that was seen in 06-Nov-2013.
Now one controversial text was added:
    On AIX these files can then be removed using the 'ipcrm' command.


There is one MOS Note on Solaris and Linux: Ora-7445 [Ioc_pin_shared_executable_object()] (Doc ID 1316906.1)
The error is most often related to an inconsistency that has been detected between the java shared object loaded in memory and the backing store image stored on disk as a result of calling java code and having the JIT compiler enabled.
    On Solaris these files are written to /tmp and have names like .SHMDJOXSHM_EXT_...
    On Linux these files are written to /dev/shm and have names like JOXSHM_EXT_...
Where IOC could be an abbreviation of Inversion of Control, something like callbacks.


Addendum (2015.12.28):

The Blog: What the heck are the /dev/shm/JOXSHM_EXT_x files on Linux? talked about such files on Linux.


Test Case


alter system set plsql_code_type=native;

create or replace package bla as
  procedure foo;
end;
/
create or replace package body bla as
  procedure foo is begin null; end;
end;
/
exec bla.foo;

alter system set java_jit_enabled=true;

create or replace and compile java source named "Hello" as
 public class Hello {
    public static String world ()
    {
       return "hello world";
    }
 }
/
create or replace function helloworld
 return varchar2 as language java
 name 'Hello.world () return java.lang.string';
/
select helloworld from dual;

Tuesday, November 19, 2013

PGA, SGA memory usage watching from UNIX

In the last two Blogs, we talked about how to measure PL/SQL package PGA memory usage and investigate ORA-04030 errors in incident file and alert.log:
    dbms_session package_memory_utilization
    ORA-04030 incident file and alert.log

In this Blog, we try to watch memory usage by UNIX commands on both AIX and Solaris systems at first, and then show sga_max_size impact on the memory allocation of both systems. All the tests are done on Oracle 11.2.0.3.0.

1. AIX


With the appended script, we can allocate 2GB PGA by:

    SQL >   exec pga_mem_test.allo(2048);

Then on OS level, you can watch it by (14090450 is process ID of the session):

   $>  svmon -P 14090450 -O filtercat=exclusive -O filterprop=data -O pgsz=on -O mpss=on
  -------------------------------------------------------------------------------
       Pid Command          Inuse      Pin     Pgsp  Virtual
  14090450 oracle          613027       48        0   613027

       PageSize                Inuse        Pin       Pgsp    Virtual
       s    4 KB              612947          0          0     612947
       m   64 KB                   5          3          0          5

      Vsid      Esid Type Description              PSize  Inuse   Pin Pgsp Virtual
    d90b51        12 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    ae0826        15 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    d30b5b        18 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    1d0b95        17 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    c20b4a        19 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    6c07e4        13 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    800b08        14 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    f00b78        16 work text data BSS heap           s      0     0    0   65536
                                                       m   4096     0    0       0
    240bac        11 work text data BSS heap           s    666     0    0   64058
                                                       m   3962     0    0       0
    3f0bb7        1a work text data BSS heap           s      8     0    0   22648
                                                       m   1415     0    0       0
     e0b86  80020014 work USLA heap                    s     44     0    0    1804
                                                       m    110     0    0       0
    3d0bb5  9001000a work shared library data          s    103     0    0     103
                                                       m      0     0    0       0
    420aca f00000002 work process private              m      5     3    0       5
    a10b29  ffffffff work application stack            s     11     0    0      43
                                                       m      2     0    0       0
    890b01  8001000a work private load data            s      3     0    0       3
                                                       m      0     0    0       0

                                                      
In Segment detail section: counter for mixed size segments are converted as s (small) size, and displayed under column Virtual. For example, segment: 240bac, 666 + 16*3962 = 64058.

That is why in PageSize section, s (4KB) is shown with 612947 Inuse and Virtual.

However counter for segments with only m (medium) size is not converted, for example, segment: 420aca. And in PageSize section, m (64KB) is shown with only 5 Inuse and Virtual.

In fact, among 613027 pages(each 4KB), only 835 (666 + 8 + 44 + 103 + 11 + 3) are s pages, and the rest are m pages. That means, s pages amounts to 3.2 MB, and m pages is about 2390MB. So this process is mainly allocated with 64KB pages.
 
With option unit=MB, we can see them in MB:

  $>  svmon -P 14090450 -O filtercat=exclusive -O filterprop=data -O pgsz=on -O mpss=on -O unit=MB
  -------------------------------------------------------------------------------
       Pid Command          Inuse      Pin     Pgsp  Virtual
  14090450 oracle         2394.64     0.19        0  2394.64


As we know, process space distribution is calculated by:

    Virtual (virtual space) = Inuse(physical memory) + Pgsp (paging space)

so this process is all allocated in physical memory due to Pgsp = 0.

If we take the option segment=category, we can see SYSTEM segments and SHARED segments (only part of Oracle SGA) (some details are removed).

  $svmon -P 14090450 -O segment=category -O filterprop=data -O pgsz=on -O mpss=on -O unit=MB
  -------------------------------------------------------------------------------
       Pid Command          Inuse      Pin     Pgsp  Virtual
  14090450 oracle         3542.34     36.6        0  3267.93
       PageSize                Inuse        Pin       Pgsp    Virtual
       s    4 KB             2690.65          0          0    2416.25
       m   64 KB              851.69       36.6          0     851.69
  ...............................................................................
  SYSTEM segments                      Inuse      Pin     Pgsp  Virtual
                                        47.3     36.4        0     47.3
  ...............................................................................
  EXCLUSIVE segments                   Inuse      Pin     Pgsp  Virtual
                                     2394.64     0.19        0  2394.64
  ...............................................................................
  SHARED segments                      Inuse      Pin     Pgsp  Virtual
                                     1100.40        0        0   826.00


and eventually segment detail can be shown by:
    $>  svmon -D 240bac -O frame=on
    
If we allocate a session with 12GB PGA, we can see that 3.77G Pgsp (paging space) is used.
But only part of EXCLUSIVE segments are in paging space, the whole SYSTEM segments and SHARED segments are still kept in memory. 

  $svmon -P 45940898 -O unit=auto
  ----------------------------------------------------------
        Pid Command          Inuse      Pin     Pgsp  Virtual
   45940898 oracle           10.8G    36.6M    3.77G    14.6G
   
   
Even though a session's whole PGA is initially allocated in real memory, AIX VMM can still page out of part of it into paging space when memory is required by other active sessions. 
   
The SHARED segments reported above seems only a part of entire SGA since it is much smaller than SEGSZ of ipcs:

  $ipcs -ma
    T        ID     KEY        MODE       OWNER    GROUP  CREATOR   CGROUP NATTCH     SEGSZ
    m   2268082   00000000 --rw-r-----   oracle      dba   oracle      dba     25 2113929216


We can also watch whole system memory usage by:

  $svmon -G -O unit=auto
 --------------------------------------------------------------------------------------
                   size       inuse        free         pin     virtual  available   mmode
    memory        60.0G       25.6G       15.7G       25.6G       42.8G         0K     Ded
    pg space      8.00G       4.22G

                   work        pers        clnt       other
    pin           12.2M          0K          0K       2.11G
    in use        25.5G          0K     119.00M    



By the way, Temp space used by Global Temporary Tables (GTT), or Temporary Table created by subquery_factoring_clause (implicit or materialize hint) are recorded in TEMP_SPACE_ALLOCATED of V$ACTIVE_SESSION_HISTORY / DBA_HIST_ACTIVE_SESS_HISTORY.

Oracle Doc said about it:
    Amount of TEMP memory (in bytes) consumed by this session at the time this sample was taken.

here the "memory" probably should be interpreted as underlined temporary segment on disk, not memory.

When we create a big GTT, we can neither observe memory consumption with SVMON for the particular session, nor system wide by VMSTAT. However, we can see the intensive I/O activities of disk on which temporary segment is located with IOSTAT.

A query on V$TEMPSEG_USAGE or V$TEMPSTAT can also justify this disk usage.


Update (September 17, 2020): Un-released shared memory segment after shutdown


Occasionally we noticed that shared memory segments (shmid) used by SGA are not released after DB shutdown (or restart), specially when using "startup force" (combination of shutdown abort and startup). Database alert.log contains the message ("attacheded" is Oracle typo):

Warning: 2 processes are still attacheded to shmid 362322707:
 (size: 65536 bytes, creator pid: 35810862, last attach/detach pid: 6080768)
ipcs command shows:

$ > ipcs -ma
T        ID     KEY        MODE       OWNER    GROUP  CREATOR   CGROUP NATTCH       SEGSZ     CPID    LPID    ATIME   DTIME     CTIME
m 362322707 0xffffffff D-rw-------   oracle      dba   oracle      dba      1       65536 35810862 6080768 21:20:14 21:20:14 21:20:14
m 163093320 0xffffffff D-rw-------   oracle      dba   oracle      dba      1 43597383680 35810862 6080768 21:20:14 21:20:14 21:20:14

Note: The first "D" in MODE "D-rw" means:
        If the associated shared memory segment has been removed. 
        It disappears when the last process attached to the segment detaches it.
        
   It seems there exist some processes attached on the shared memory segment to be removed (MODE "D-rw"),
   but it is pending on the termination of those attached processes.
 
   In Oracle DB, the problem seems caused by DB shutdown abort.
 
   There are two alternatives to remove them:
 
    (a). Kill all attached (and Stopped) processes on "D-rw" Schmid and check if the Schmid is removed.
         To perform it, at first you need to find all processes started before last DB start, and still attached on the DB.
    (b). Reboot Lpar to cleanup all Schmid.

#-- With T option, write the output with the date

$ > ipcs -Top
T        ID     KEY        NATTCH  CPID      LPID      ATIME             DTIME             CTIME           
m 362322707 0xffffffff     1       35810862  6080768   21:20:14 Sep 17   21:20:14 Sep 17   21:20:14 Sep 17  
m 163093320 0xffffffff     1       35810862  6080768   21:20:14 Sep 17   21:20:14 Sep 17   21:20:14 Sep 17  
With ipcs "S" Flags, we can find a list of SID attached to shared memory id, for exaample, above 163093320:

$ > ipcs -maS

m 163093320 0xffffffff D-rw-------   oracle      dba   oracle      dba      1 43597383680 35810862 6080768 21:20:14 21:20:14 21:20:14
SID :
0x235aa6 0x376a2f4  
With svmon "S" Flags, we can displays the memory-usage statistics for segments that the SIDs parameter specifies. The output also includes a list of OS PIDs which are still attacheded on the SID (belongs to a SHMID):

$> svmon -S 0x235aa6 0x376a2f4 -O unit=KB -O pidlist=on

Unit: KB
    Vsid      Esid Type Description              PSize  Inuse   Pin Pgsp Virtual
 3ddeb5f  70001003 work shmat/mmap                   m     64     0    0      64
                   pid(s)=32664296, 45810862
 2d49a54  70001001 work shmat/mmap                   m     64     0    0      64
                   pid(s)=32664296, 45810862
Then with ps command, we can find the details of OS processes (Oracle sessions). Their start time (or elapsed time) shows that they still exist even after DB shutdown:

$ > ps aux 32664296

USER          PID %CPU %MEM   SZ  RSS    TTY STAT    STIME  TIME COMMAND
oracle   32664296  0.0  2.0 253992 189560      - A      Aug 18  0:05 oracletestdb

$ > ps -o cp,pcpu,tcpu,pid,etime,state,time,tdiskio,user,pagein,rssize,vsz,ppid,wchan,shmpgsz,spgsz,thcount,tag,command,args=Args -p 32664296

 CP  %CPU        TCPU      PID     ELAPSED ST        TIME TDISKIO     USER PAGEIN   RSS   VSZ     PPID    WCHAN SHMPGSZ SPGSZ THCNT TAG             COMMAND  Args
  0   0.0           - 32664296 29-23:46:55 A     00:00:05       -   oracle      0 189560 10872        1        -      4K   64K     1 -               oracle   oracletestdb
Blog: Identifying shared memory segment users using lsof shows how to find the similar mapping in Linux with ipcs and lsof command to map OS process id to shmid. (In AIX, lsof seems not showing shmid, neither "procfiles -cn")

By the way, Oracle SYSRESV Utility (MOS Doc ID 123322.1) provides sysresv command to manage OS resources (not OS process id displayed):

$ > $ORACLE_HOME/bin/sysresv -d on -l $ORACLE_SID


Update (August 23, 2021): JAVA_JIT_ENABLED enabled compiled shared memory segments


From Oracle 19c, when JAVA_JIT_ENABLED enabled, java native compiled targets are stored in shared memory: JOEZSHM. Following are some example output from Linux and AIX.

Oracle command sysresv cannot show those JOEZSHM shared memory, hence it cannot remove them with -i or -f option.

--- Linux: ipcs, sysresv not display /dev/shm ---

linuxdb$ ipcs -m
  ------ Shared Memory Segments --------
  key        shmid      owner      perms      bytes      nattch     status
  0x00000000 3014656    oracle     600        8908800    112
  0x00000000 3047425    oracle     600        4563402752 56
  0x00000000 3080194    oracle     600        7868416    56
  0x8dbd5b2c 3112963    oracle     600        45056      56

linuxdb$ ls -ltr /dev/shm
  total 32768
  drwxr-x--- 2 oracle dba       40 Jun 14 07:23 orahpatch_linuxdb
  -rwxrwx--- 1 oracle dba 16777216 Aug 23 09:53 JOEZSHM_linuxdb_1_0_1_0_0_658830875
  -rwxrwx--- 1 oracle dba 16777216 Aug 23 09:54 JOEZSHM_linuxdb_1_0_0_0_0_2043113040
	
linuxdb$ $ORACLE_HOME/bin/sysresv -d on -l $ORACLE_SID
  ------ Shared Memory Segments --------
  key        shmid      owner      perms      bytes      nattch     status
  0x00000000 3014656    oracle     600        8908800    111
  0x00000000 3047425    oracle     600        4563402752 56
  0x00000000 3080194    oracle     600        7868416    56
  0x8dbd5b2c 3112963    oracle     600        45056      56


--- AIX: sysresv not display JOEZSHM, not show size of Shared Memory (less info than sysresv on Linux ) ---

aixdb$ ipcs -mar
  IPC status from /dev/mem as of Mon Aug 23 14:41:00 CEST 2021
  T        ID     KEY        MODE       OWNER    GROUP  CREATOR   CGROUP NATTCH     SEGSZ  CPID  LPID   ATIME    DTIME    CTIME  RTFLAGS NAME
  Shared Memory:
  m  20972544   00000000 --rw-------   oracle      dba   oracle      dba    157 5419040768 16777572 19071430 14:40:19 14:40:19 12:46:19
  m  20972545 0xecf3d44c --rw-------   oracle      dba   oracle      dba    157     65536 16777572 19071430 14:40:19 14:40:19 12:46:19
  m  20972546   00000000 --rw-------   oracle      dba   oracle      dba    157  16777216 16777572 19071430 14:40:19 14:40:19 12:46:19
  m  19923971   00000000 --rw-------   oracle      dba   oracle      dba    157  33554432 16777572 19071430 14:40:19 14:40:19 12:46:19
  m         - 0xffffffff --rw-rw----   oracle      dba   oracle      dba      0  16777216 11797042     0 no-entry no-entry 16:27:36    -    /JOEZSHM_aixdb_1_0_0_0_0_2042869279
  m         - 0xffffffff --rw-rw----   oracle      dba   oracle      dba      0  16777216 13304362     0 no-entry no-entry 16:35:53    -    /JOEZSHM_aixdb_1_0_0_0_0_2042708934

aixdb$ $ORACLE_HOME/bin/sysresv -d on -l $ORACLE_SID
  IPC status from /dev/mem as of Mon Aug 23 10:01:28 CEST 2021
  T        ID     KEY        MODE       OWNER    GROUP
  Shared Memory:
  m  20972544   00000000 --rw-------   oracle      dba
  m  20972545 0xecf3d44c --rw-------   oracle      dba
  m  20972546   00000000 --rw-------   oracle      dba
  m  19923971   00000000 --rw-------   oracle      dba


2. Solaris


On OS level, you can watch it by:

   $pmap -x 23739
                

             Address     Kbytes        RSS       Anon     Locked Mode   Mapped File
    ---------------- ---------- ---------- ---------- ---------- ------ -----------      
    FFFFFD7C4648D000         64         64         64          - rw---    [ anon ]
    0000000060000000      49152      49152          -      49152 rwxsR    [ ism shmid=0x800002d ]
    0000000080000000    7143424    7143424          -    7143424 rwxsR    [ ism shmid=0x800002e ]
    0000000240000000         20         20          -         20 rwxsR    [ ism shmid=0x900002f ]
    ---------------- ---------- ---------- ---------- ---------- ------ -----------
            total Kb    9947208    9936408    2536840    7192596       


Where pid: 25032 is process ID of the session with a PGA allocation of a 2GB memory table.

The above output shows that most of mapping is with a size of 64K marked as "[ anon ]".
The total Anon amounts to 2536840 (a little more than 2GB).
The total Locked is 7192596, which is the SGA size, and are marked like "[ ism shmid=0x800002e ]".

The SGA of this DB is allocated with ISM, therefore all SGA memory is locked, no swap space is needed to back it. We can roughly estimate virtual space, RSS (physical memory), and swap space by:

  virtual space   = 9947208 - 7192596 = 2,754,612 KB
  physical memory = 9936408 - 7192596 = 2,743,812 KB
  swap space      = 2754612 - 2743812 =    10,800 KB


since process 25032 is the only session with large PGA size and swap space is quite low,
we can say that PGA of this session is mainly allocated in physical memory.
     
Similarly we can watch SGA size by:

  $>  ipcs -ma
   T         ID      KEY        MODE        OWNER    GROUP  CREATOR   CGROUP NATTCH      SEGSZ 
   m   16777255   0x4a725828 --rw-rw----   oracle      dba   oracle      dba     84 7365218304
 

here SEGSZ of ipcs is correlated well with total Locked of pmap.

3. Linux



Run:
 SQL > exec pga_mem_test.allo(2048);

and watch PGA memory on Linux allocation by:

 $> pmap -x 46332 |grep -e zero |awk '{cnt+=1; Kbytes+=$2; RSS+=$3; Dirty+=$4} END {print "Count=",cnt,",Kbytes=",Kbytes,",RSS=",RSS,",Dirty=",Dirty,",Avg=",Kbytes/cnt}'
   Count= 37544 ,Kbytes= 2440768 ,RSS= 2403836 ,Dirty= 2403836 ,Avg= 65.0109

where Kbytes should be the allocated size.

Similarly we can watch SGA size in Linux by:

 $> ipcs -m
   ------ Shared Memory Segments --------
   key        shmid      owner      perms      bytes      nattch     status
   0x00000000 1455849472 oracle     640        33554432   21
   0x00000000 1455882241 oracle     640        4513071104 21
   0x19d100cc 1455915010 oracle     640        2097152    21

Pick one PID of Oracle session, and run:

 $> pmap -x 49169 |grep shmid
   0000000060000000   32768     448     448 rw-s-    [ shmid=0x56c68000 ]
   0000000062000000 4407296  276736  276736 rw-s-    [ shmid=0x56c70001 ]
   000000016f000000    2048       4       4 rw-s-    [ shmid=0x56c78002 ]

where 3 shared memory segments match each other:
  shmid=1455849472=0x56c68000  
  shmid=1455882241=0x56c70001
  shmid=1455915010=0x56c78002   
  
and total sga allocated is:
  4548722688 bytes (=33554432+4513071104+2097152)


4. sga_max_size on AIX


Recently a DB on AIX experienced performance problem due to shared pool memory resizing. The memory is configured as follows:

  sga_max_size       26'239'565'824

  shared_pool_size     2'214'592'512
  java_pool_size        268'435'456
  large_pool_size        536'870'912
  log_buffer              83'886'080
  db_cache_size        9'261'023'232
  db_keep_cache_size    1'006'632'960
  db_recycle_cache_size   1'006'632'960


and Automatic Memory Management is disabled (memory_target not specified).

AWR report shows 9 "Memory Resize Ops", where "DEFAULT buffer cache" is indicated by "SHR/IMM", and "shared pool" is signalized by "GRO/IMM".

It also shows the changes of db_cache_size and shared_pool_size:

  Parameter Name    Begin value   End value
  ----------------  ------------   -------------
  db_cache_size     9'261'023'232  8'657'043'456
  shared_pool_size  2'214'592'512  2'818'572'288


from which we can see 603'979'776 Bytes is moved from db_cache_size to shared_pool_size.
However, AIX "ipcs -a" shows:

  T        ID     KEY        MODE       OWNER    GROUP  CREATOR   CGROUP NATTCH     SEGSZ 
  ---------------------------------------------------------------------------------------
  m  20971562   00000000 --rw-rw----   oracle      dba   oracle      dba    379 201326592 
  m 143656025   00000000 --rw-rw----   oracle      dba   oracle      dba    379 26038239232
  m  20972634 0xc1bd22ac --rw-rw----   oracle      dba   oracle      dba    379     16384


so the AIX real allocated memory:

    201'326'592 + 26'038'239'232

is exactly equal to sga_max_size (26'239'565'824) (KEY marked with 0xc1bd22ac of SEGSZ = 16384 is omitted).

In conclusion, SGA_MAX_SIZE specifies the SGA maximum size of AIX allocated shared_memory, but not SGA effectively used size. It is advisable not to specify this parameter.

If sga_max_size is not specified, allocated shared_memory is determined by:

 select sum(value)/1024/1024
   from v$parameter
 where name in('shared_pool_size', 'java_pool_size', 'large_pool_size', 'streams_pool_size',

               'log_buffer', 'db_cache_size', 'db_keep_cache_size', 'db_recycle_cache_size'
               );


and sga_max_size is also deduced the above calculation, and AIX real allocated memory obeys to this value too. 


5. sga_max_size on Solaris x86-64


Repeated the test on a Solaris Operating System (x86-64) DB by specifying sga_max_size = 2600M,
it behaves the identical to AIX.

          Address     Kbytes        RSS       Anon     Locked Mode   Mapped File
  ---------------------------------------------------------------------------------------------
 0000000060000000      32768      26624          -          - rwxs-    [ dism shmid=0x5000020 ]
 0000000080000000    2629632     897024          -          - rwxs-    [ dism shmid=0x5000021 ]
 0000000160000000         12          4          -          - rwxs-    [ dism shmid=0x6000022 ]


Besides this, SGA memory allocation is changed to DISM.

Kbytes column confirms that 2600M = 32768K + 2629632K. However RSS shows that only 897024K of 2629632K (about 34%)  are allocated in physical memory, and no more memory is under Column Locked.

DISM is implemented with a dism daemon which is running oradism to dynamically manage memory allocation.

During database Starting up, DISM is started after all Mandatory Background Processes.

oradism ($ORACLE_HOME/bin/oradism) should be configured with permission:
            -rwsr-xr-x
in order to be running with root privileges.

If sga_max_size is not configured, it is computed as sum of all component groups in v$sga, and ISM is used.
If sga_max_size is configured greater than above computed value, DISM is used (over configured memory goes to Group "Variable Size")
If less or equal (same as not configured), it is still ISM.

Seeing the output of SWAP in Solaris running Oracle:

$ swap -l
swapfile   dev    swaplo   blocks     free
/dev/swap  181,1  8        100663288  100663288

it was asked why SWAP areas not used at all. Here is some essence:

A running Oracle instance is a "Cache of Bits (text, data, bss, stack)". Because of Cache, it is preferred in memory (RSS), whereas SWAP is a device of disk.

Even Oracle double accentuates it by the term "Buffer_Cache", where both "Buffer" and "Cache" are synonyms for memory.

In seldom case, Oracle SGA or PGA are using SWAP (disk), which could be an indication of performance degrade. For Solaris, one case is DISM.

So no use of SWAP conforms to Oracle's intention.

Update (2019-09-30)

With Optimized Shared Memory (OSM) in Solaris 11.3, the new mode for System V shared memory, we observed that Oracle 12c SGA can not go to swap space (same as ISM), but PGA can allocate in swap space (the non-swapped part in physical memory marked as "RSS").

If sga_max_size is bigger than available physical memory, instance can not be started and throws error: ORA-27125 ("unable to create shared memory segment").

Test Code 



create or replace package pga_mem_test as
  procedure allo (i_mb int);
end;
/
create or replace package body pga_mem_test as
  type t_tab_kb   is table of char(1024);   -- 1KB
  p_tab_1mb          t_tab_kb := t_tab_kb();
  type t_tab_mb   is table of t_tab_kb;    
  p_tab_mb           t_tab_mb := t_tab_mb();
  p_sid              number   := sys.dbms_support.mysid;
 
 -------------------------------------------
 procedure rpt(l_name varchar) is
    l_v$process_mem            varchar2(4000);
    l_v$process_memory_mem     varchar2(4000);
 begin
  select 'Used/Alloc/Freeable/Max >>> '||
          round(pga_used_mem/1024/1024)    ||'/'||round(pga_alloc_mem/1024/1024)||'/'||
            round(pga_freeable_mem/1024/1024)||'/'||round(pga_max_mem/1024/1024)
      into l_v$process_mem
      from v$process
      where addr = (select paddr from v$session where sid = p_sid);
    
   select 'Category(Alloc/Used/Max) >>> '||
            listagg(Category||'('||round(allocated/1024/1024)||'/'||
                    round(used/1024/1024)||'/'||round(max_allocated/1024/1024)||') > ')
    within group (order by Category desc) name_usage_list
      into l_v$process_memory_mem
      from v$process_memory
      where pid = (select pid from v$process
                    where addr = (select paddr from v$session where sid = p_sid));

   dbms_output.put_line(rpad(l_name, 20)||' > '||rpad(l_v$process_mem, 50));
   dbms_output.put_line('             ------ '||l_v$process_memory_mem);
 end rpt;
  
 -------------------------------------------  
  procedure allo (i_mb int) is
  begin
   rpt('Start allocate: '||i_mb||' MB');
 
   select 'M' bulk collect into p_tab_1mb from dual connect by level <= 1024;  -- 1MB
 
   for i in 1..i_mb loop   -- i_mb MB
    p_tab_mb.extend;
    p_tab_mb(i) := p_tab_1mb;
   end loop;
 
   rpt('End allocate: '||i_mb||' MB');
  end allo;
 
end;
/