After writing that article, I attended an excellent lecture by Mike Dietrich and Daniel Overby Hansen about noisy neighbors in a multitenant environment and how to make PDBs quieter and easier to live with in a larger society. During that lecture I thought about shared memory structures and semaphores — for example, how latches are shared across different PDBs…
Let’s investigate that in the context of checkpoint queue latches.
We already know that the function responsible for manipulating the checkpoint queue is kcbkgcql.
It is a small function and can be easily reverse-engineered. The part that’s relevant to us is:
Fortunately, we live in wonderful times of LLMs and we can use them to make our lives easier. This code is, of course, trivial, and we don’t really need ChatGPT to analyze it; but I want to show you how easy it is when you know how to ask a proper question:
Below you will find ARM assembler code:ldrb w22, [x19, #200]mov x2, #0x0mov w1, #0x0add x24, x19, #0x10ldr x25, [x19, #264]and w21, w22, #0x1and x3, x21, #0xfflsl x20, x3, #5add x4, x20, x3mov w3, w23add x20, x25, x4, lsl #3mov x0, x20bl 0xb5f5360 <kslgetl>Assuming that x19 contains address of Oracle block header in buffer cache and 200 bytes from this header is column set_ds from x$bh, translate this code to python. The premise is that I have value of SET_DS from X$BH and I want to calculate latch address, which passed to kslgetl for acquiring a proper latch.
16 LINESASM / UTF-8
And here is a nice answer:
Here’s the straight translation of what that AArch64 snippet does, distilled to the one thing that matters for the latch argument to kslgetl:
Read set_ds (byte) from the block header.
Take its least-significant bit.
If that bit is 1, choose latch “slot” 33; if 0, choose slot 0.
Latch slots are 8 bytes apart, so the byte offset is slot * 8.
Add that offset to a base pointer read from [block_header + 264].
def latch_address_for_set_ds(set_ds: int, base_ptr_from_hdr_264: int) -> int: """ Compute the latch address that is passed to kslgetl. Args: set_ds: integer value of X$BH.SET_DS (byte, but int is fine). base_ptr_from_hdr_264: 64-bit value loaded from [block_header + 264]. This is the base of the latch array/structure. Returns: Absolute address (int) of the latch to pass to kslgetl. """ # w22 <- ldrb [x19,#200] ==> set_ds lsb = set_ds & 0x1 # and w21, w22, #0x1 slot = lsb * 33 # x4 = (x3<<5) + x3 -> 33 * lsb (0 or 33) offset = slot << 3 # ... , lsl #3 -> slot * 8 bytes return base_ptr_from_hdr_264 + offset
17 LINESPYTHON / UTF-8
If you only have SET_DS
If you don’t have the 64-bit value from [block_header + 264], you can still compute which slot it will use and the byte offset within that array:
slot will be 0 when SET_DS is even, 33 when it’s odd.
The effective address passed to kslgetl is:addr = *(block_header + 264) + (slot * 8).
Everything else in your snippet (mov x2,#0, mov w1,#0, add x24,…, mov w3,w23) doesn’t affect the latch pointer; they’re other call arguments or dead/irrelevant for this specific calculation.
Actually, there’s a small twist — each block is protected by two checkpoint queue latches: if the first one is taken, the second one is used.
You can write a simple query that computes both checkpoint queue latches for each block of a table.
In my test environment I have two PDBs — RICK1 and RICK2. Each has the table HR.EMPLOYEES, a small table with only two data blocks. Let’s inspect the checkpoint Q latches after selecting blocks from that table.
set linesize 200set pagesize 100column name format a10select p.name, b.dbablk, to_char(case when mod(dbablk, 2) = 0 then to_number(set_ds,'XXXXXXXXXXXXXXXX') else to_number(set_ds,'XXXXXXXXXXXXXXXX')+264 end,'XXXXXXXXXXXXXXXX') as primary_checkpoint_q_latch, to_char(case when mod(dbablk, 2) = 0 then to_number(set_ds,'XXXXXXXXXXXXXXXX')+264 else to_number(set_ds,'XXXXXXXXXXXXXXXX') end,'XXXXXXXXXXXXXXXX') as secondary_checkpoint_q_latchfrom x$bh b, cdb_objects o, v$pdbs pwhere o.object_name='EMPLOYEES'and state!=0and b.obj=o.data_object_idand o.con_id=b.con_idand o.con_id=p.con_idand b.dbablk in (38452, 38453)order by primary_checkpoint_q_latch, dbablk, o.con_id/
SQL> get latch_names.sql 1* select name from v$latch_children where addr in ('00000001486E8168','00000001486E8270')SQL> /NAME--------------------------------------------------checkpoint queue latchcheckpoint queue latch
8 LINESSQL / UTF-8
So we have demonstrated that two different pluggable databases can use the same set of latches to protect their blocks. What would happen if one database held those latches constantly?
I created a larger table in RICK1: HR.EMPLOYEES_SKEW
SQL> update hr.employees_skew 2 set salary=salary;
2 LINESTEXT / UTF-8
From another session of RICK1 I will run a simple SELECT * FROM HR.EMPLOYEES_SKEW;
Let’s see what will happen for the select:
As you can see, the SELECT statement is waiting on buffer busy waits, which is a consequence of latch: checkpoint queue latch. The session 26 is not present in my view, because it won’t be visible from RICK1.
It will be visible only from RICK2 or from CDB level:
Usually checkpoint queue latch is being taken during commit, thanks to private redo strands and IMU.
So it would be possible to degrade the performance of the whole CDB by doing many commits (or rollbacks) — and this is only from the checkpoint queue / redo perspective. There are more latches and more shared structures that can cause nightmares.
declarecursor c_sql is select rowid as rid from hr.employees_skew;type t_rowid is table of c_sql%ROWTYPE index by pls_integer;v_rowid t_rowid;begin open c_sql; fetch c_sql bulk collect into v_rowid; close c_sql; for i in v_rowid.first..v_rowid.last loop update hr.employees_skew set salary=salary+1 where rowid=v_rowid(i).rid; commit; end loop;end;/
18 LINESSQL / UTF-8
On RICK1, from one session I created an active transaction which will make any other session to create CR blocks in a buffer cache:
Conclusion? Be careful what you integrate together! And always analyze database performance from CDB perspective. If you are having problems with overall performance understanding – JAS-MIN can help you 😉
I know, I know – AI is stupid… but we shouldn’t be and LLM is a tool that we should get familiar with. That’s why JAS-MIN gets integration with AI in two modes – one-time batch processing and backend assistant.
One of my customers experienced a weird behavior – thousands of child cursors with LANGUAGE_MISMATCH When I looked closer, into V$SQL_SHARED_CURSOR.REASON I saw something puzzling: While this part is normal: Be