Re: Buffer locking is special (hints, checksums, AIO writes)
Andres Freund <andres@anarazel.de>
Hi, On 2026-02-07 14:38:53 +0200, Heikki Linnakangas wrote: > On 03/02/2026 00:33, Andres Freund wrote: > > - Now that we use the normal order of WAL logging, we don't need to delay > > checkpoint starts anymore. > > > > I think the explanation for why that is ok is correct [1], but it needs to > > be looked at by somebody with experience around this. Maybe Heikki? > > So that's patch 0004 "bufmgr: Switch to standard order in > MarkBufferDirtyHint()". Yes, looks correct to me. Thanks for checking! Somehow I went back and forth about it being right multiple times... > > /* > > * Update RedoRecPtr so that we can make the right decision. It's possible > > * that a new checkpoint will start just after GetRedoRecPtr(), but that > > * is ok, as the buffer is already dirty, ensuring that any BufferSync() > > * started after the buffer was marked dirty cannot complete without > > * flushing this buffer. If a checkpoint started between marking the > > * buffer dirty and this check, we will emit an unnecessary WAL record (as > > * the buffer will be written out as part of the checkpoint), but the > > * window for that is small. > > */ > > RedoRecPtr = GetRedoRecPtr(); > > That "small window" is actually pretty big if you think of it a little more > loosely. Our rule is that we write the full page image if a checkpoint has > started since the page LSN, but that's very conservative already. It would > be sufficient to write the full page image only if the checkpoint has > already flushed the page. This small window is just a special case of that > conservatism. I mainly want to mention that window because I have to think about it when analyzing the correctness of the approach. If the window is not mentioned, at least I have to think about whether the window is dangerous in some form. > It would be sufficient to write the full page image only if the checkpoint > has already flushed the page. Today that would probably not quite be sufficient, due to issues around re-dirtying the page during checkpointer's flush (and thus needing to be written out again, with the chance of a torn write that has no FPI to repair it). But that will soon be impossible. I think the actual rule would need to be more complicated, I think we would need to generate an FPI for the first modification after the checkpoint flush, even though the LSN is newer than the redo LSN, because we didn't generate one earlier? Otherwise we could get into a situation where there is no non-torn on-disk page version after a later crash, I think? Consider: 1) modify page w/ FPI 2) redo pointer determined at X 3) modify page w/o FPI, as the page hasn't yet been flushed at X+1 4) checkpointer flushes page 5) checkpoint completes, at X+2 6) page is dirtied, w/o FPI X+3, as X+1 > X 7) in the middle of writing out the page, we crash, the page is torn For recovery we will replay starting from position X. Then will replay the record from 3), which will be skipped due to the LSN. Then we will replay X+3, which either will be skipped due to the LSN condition (if the page header survived the torn page), leading to the changes to the "old portion" of the torn page not being replayed, or we will replay the WAL record, applying it to a torn page (or failing to read in the page due to checksum errors). If we only needed to think about buffers that stay in memory, we could "just" tackle this by remember that the page will need to be FPId during the next modification in the BufferDesc, but that doesn't help us if the page is evicted and reread... > I've been thinking of trying track that more accurately for a long time, > because it would smoothen the WAL spike when a checkpoint begins. It'd indeed be nice to improve that. Another thing it'd be helpful is widening when we can write out hint bits on standbys. If the rule were just that we can skip an FPI if the page still needs to be written out by the checkpoint, it'd be fairly simple - we could utilize BM_CHECKPOINT_NEEDED. But as hinted at above, I think it's a it more complicated. Greetings, Andres Freund
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
bufmgr: Fix ordering of checks in PinBuffer()
- c0af4eb4e71e 19 (unreleased) landed
-
Use UnlockReleaseBuffer() in more places
- 8df3c48e466c 19 (unreleased) landed
-
bufmgr: Make UnlockReleaseBuffer() more efficient
- f39cb8c01106 19 (unreleased) landed
-
bufmgr: Don't copy pages while writing out
- 41d3d64e87af 19 (unreleased) landed
-
Fix use of wrong variable in _hash_kill_items()
- f5eb854ab6d6 19 (unreleased) landed
-
Fix bug due to confusion about what IsMVCCSnapshot means
- ce5d489166e5 19 (unreleased) landed
-
bufmgr: Switch to standard order in MarkBufferDirtyHint()
- a766125efd60 19 (unreleased) landed
-
bufmgr: Remove the, now obsolete, BM_JUST_DIRTIED
- b0f4ff3c9266 19 (unreleased) landed
-
Require share-exclusive lock to set hint bits and to flush
- 82467f627bd4 19 (unreleased) landed
-
heapam: Don't mimic MarkBufferDirtyHint() in inplace updates
- f4a4ce52c0d1 19 (unreleased) landed
-
bufmgr: Allow conditionally locking of already locked buffer
- 333f586372aa 19 (unreleased) landed
-
bufmgr: Avoid spurious compiler warning after fcb9c977aa5
- 84705b37273d 19 (unreleased) landed
-
lwlock: Remove ForEachLWLockHeldByMe
- 55fbfb738b00 19 (unreleased) landed
-
lwlock: Remove support for disowned lwlwocks
- d40fd85187d0 19 (unreleased) landed
-
bufmgr: Implement buffer content locks independently of lwlocks
- fcb9c977aa5f 19 (unreleased) landed
-
bufmgr: Change BufferDesc.state to be a 64-bit atomic
- dac328c8a682 19 (unreleased) landed
-
lwlock: Improve local variable name
- 556c92a68972 19 (unreleased) landed
-
lwlock: Invert meaning of LW_FLAG_RELEASE_OK
- 9a385f61666c 19 (unreleased) landed
-
bufmgr: Make definitions related to buffer descriptor easier to modify
- ff219c198789 19 (unreleased) landed
-
heapam: Add batch mode mvcc check and use it in page mode
- 0b96e734c590 19 (unreleased) landed
-
freespace: Don't modify page without any lock
- 45f658dacb9c 19 (unreleased) landed
-
heapam: Move logic to handle HEAP_MOVED into a helper function
- 548de59d93d5 19 (unreleased) landed
-
bufmgr: Optimize & harmonize LockBufHdr(), LWLockWaitListLock()
- 09ae2c8bac8d 19 (unreleased) landed
-
bufmgr: Add one-entry cache for private refcount
- 30df61990c67 19 (unreleased) landed
-
bufmgr: Separate keys for private refcount infrastructure
- edbaaea0a95e 19 (unreleased) landed
-
Add pg_atomic_unlocked_write_u64
- 7902a47c20b1 19 (unreleased) landed
-
Rename BUFFERPIN wait event class to BUFFER
- 6c5c393b7403 19 (unreleased) landed
-
bufmgr: Turn BUFFER_LOCK_* into an enum
- 156680055dc5 19 (unreleased) landed
-
lwlock: Fix, currently harmless, bug in LWLockWakeup()
- 81f773895321 19 (unreleased) landed
- da3971496531 15.16 landed
- 89c8a1b9069f 16.12 landed
- 427e886a79a5 17.8 landed
- 332693e75969 14.21 landed
- 8082b759d9b5 18.2 landed
-
bufmgr: Use atomic sub for unpinning buffers
- 5310fac6e0fc 19 (unreleased) landed
-
bufmgr: Allow some buffer state modifications while holding header lock
- c75ebc657ffc 19 (unreleased) landed
-
bufmgr: Fix valgrind checking for buffers pinned in StrategyGetBuffer()
- c819d1017ddb 19 (unreleased) landed
-
bufmgr: Don't lock buffer header in StrategyGetBuffer()
- 5e8998592879 19 (unreleased) landed
-
bufmgr: fewer calls to BufferDescriptorGetContentLock
- 3baae90013df 19 (unreleased) landed
-
bufmgr: Fix signedness of mask variable in BufferSync()
- 2a2e1b470b9b 19 (unreleased) landed
-
bufmgr: Introduce FlushUnlockedBuffer
- 3c2b97b29ee3 19 (unreleased) landed
-
Improve ReadRecentBuffer() scalability
- 819dc118c0f6 19 (unreleased) landed