Re: strange perf regression with data checksums
Tomas Vondra <tomas@vondra.me>
From: Tomas Vondra <tomas@vondra.me>
To: Aleksander Alekseev <aleksander@timescale.com>
Cc: PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Date: 2025-05-19T16:13:02Z
Lists: pgsql-hackers
Attachments
- backtrace.txt (text/plain)
Hi,
I looked at this again, and I think the reason is mostly obvious. Both
why it's trashing, and why it happens with checksums=on ...
The reason why it happens is that PinBuffer does this:
old_buf_state = pg_atomic_read_u32(&buf->state);
for (;;)
{
if (old_buf_state & BM_LOCKED)
old_buf_state = WaitBufHdrUnlocked(buf);
buf_state = old_buf_state;
... modify state ...
if (pg_atomic_compare_exchange_u32(&buf->state, &old_buf_state,
buf_state))
{
...
break;
}
}
So, we read the buffer state (which is where pins are tracked), possibly
waiting for it to get unlocked. Then we modify the state, and update it,
but only if it didn't change. If it did change, we retry.
Of course, as the number of sessions grows, the probability of something
updating the state in between increases. Another session might have
pinned the buffer, for example. This causes retries.
I added a couple counters to track how many loops are needed, and with
96 clients this needs about 800k retries per 100k calls, so about 8
retries per call. With 32 clients, this needs only about 25k retries, so
0.25 retry / call. That's a huge difference.
I believe enabling data checksums simply makes it more severe, because
the BufferGetLSNAtomic() has to obtain header lock, which uses the same
"state" field, with exactly the same retry logic. It can probably happen
even without it, but as the lock is exclusive, it also "serializes" the
access, making the conflicts more likely.
BufferGetLSNAtomic does this:
bufHdr = GetBufferDescriptor(buffer - 1);
buf_state = LockBufHdr(bufHdr);
lsn = PageGetLSN(page);
UnlockBufHdr(bufHdr, buf_state);
AFAICS the lock is needed simply to read a consistent value from the
page header, but maybe we could have an atomic variable with a copy of
the LSN in the buffer descriptor?
regards
--
Tomas Vondra
Commits
-
Make _bt_killitems drop pins it acquired itself.
- 7c319f54917f 18.0 landed
- 40aa5ddea1c0 17.6 landed
- c7f25feb3862 16.10 landed
- d2ec671092a1 15.14 landed
- 7c7c0a77dc8a 14.19 landed
- 38c8d298704c 13.22 landed
-
Avoid BufferGetLSNAtomic() calls during nbtree scans.
- e6eed40e4441 18.0 landed
-
Reduce pinning and buffer content locking for btree scans.
- 2ed5b87f96d4 9.5.0 cited