In-flight Record Handoff
TTDB-2524 · logindex replay

In-flight Record Handoff

A record parsed while its page sits outside the buffer pool can be lost when a backend faults that page in. Two mechanisms hand the record to that backend: the one on the branch, and the prototype that replaces it.

01The window being closed

A single-page record takes no mini-transaction page lock, so nothing keeps a reader out while the startup process parses it. The logindex entry is already inserted, but lastReplayedEndRecPtr still sits below the record, and that frontier is what bounds a backend's fault-in replay.

record inserted, frontier still behind it STARTUP BACKEND parse record R logindex insert lookup: not in pool frontier ← R.end fault P in replay ≤ frontier R missed, OUTDATE clear no descriptor to mark time
The lookup misses, so there is no buffer descriptor to mark OUTDATE on. The backend's replay stops below R.end, the page goes valid with the flag clear, and the record is served stale until something else replays it.

02Variant A — publish before the parse

What is on the branch today. Before the parse begins, the startup writes the record's page tags and EndRecPtr into one global slot under a seqlock; every fault-in reads that slot from polar_logindex_io_lock_apply and arms the buffer's watermark when a tag matches.

STARTUP publish tag[2] + lsn seqlock write parse · logindex insert lookup: miss frontier ← R.end clear slot XLogRecoveryCtl: seqlock | ntags | tag[2] | lsn one dedicated cache line, written for every single-page record write seqlock read BACKEND BufTableInsert read page probe slot, match tag arm watermark re-arm OUTDATE
The publication must precede the parse, because the reader probes only after it has already inserted the buffer and read the page — so the slot has to be live for the whole window. Publishing that early means the pages are predicted: block 0 of the record plus the visibility-map page derived from it, with an assertion to catch a parse callback that ever touches a third.

03Variant B — publish at the miss, under the lock

The prototype. The startup publishes where it discovers the problem — the buffer-table miss inside polar_logindex_outdate_parse — into a slot for that mapping partition, while holding the partition lock it took for the lookup. The backend reads that slot at BufTableInsert, under the same lock in exclusive mode.

STARTUP partition lock · shared parse · logindex insert lookup: miss slot[part] ← (tag, R.end) frontier ← R.end the same partition lock orders these: the insert cannot precede the publication BACKEND partition lock · exclusive BufTableInsert read slot[part], arm watermark read page re-arm
Shared and exclusive are mutually exclusive, so the lock that already guards the lookup and the insert also serializes the slot — no seqlock, and nothing to read on a fault-in that does not hit a partition with a live record. The tag published is the one the lookup used, which is already the visibility-map tag on the derived-page call, so nothing is predicted.

04The shared state

VARIANT A seqlock | ntags | tag[0] | tag[1] | lsn one padded cache line written every single-page record seqlock-read on every fault-in VARIANT B tag | lsn tag | lsn tag | lsn … 4096 slots, one per mapping partition · 32 B each · 128 KB written only when the lookup misses read under the lock the reader already holds Both feed the same per-buffer watermark: polar_outdate_lsn on the descriptor, compared against the replay bound after the page is replayed.
The watermark half of the fix is unchanged between the two. What moves is only how a record reaches a page the startup could not mark.

05What actually changes

Variant A · on the branchVariant B · prototype
Published wherebefore the parse, in polar_logindex_parse_xlogat the buffer-table miss, in polar_logindex_outdate_parse
Published whatblock 0's tag + derived VM tag + LSNthe tag the lookup used + LSN
Synchronizationdedicated seqlockthe buffer mapping partition lock, already held on both sides
Backend readseqlock read per fault-in, after insert and I/Oplain read at BufTableInsert
Startup cost per recordseqlock write region + a clearone 32-byte store, only on a miss
Shared memoryone padded slot128 KB
Invariant to holdpublished tags must cover every page the rmgr parse callbacks touchthe startup parses serially and the frontier passes record N before N+1 is parsed
Spurious replay passesnoneonly while a record is live in the same partition
C diff vs TTP_15_STABLE+308 / −7+237 / −5

B removes the seqlock, the tag prediction, the fork derivation, the two coverage assertions, the arm_lsn plumbing and the in_flight_published out-parameter — and does less work per record on the startup's path, which is the path the mini-transaction removal was bought for.

Outcome: A ships. B leaves one interleaving uncovered (below), and closing it forces the probe back into the replay path keyed by partition rather than by page. That trades A's exact tag match for partition-granular arming — a page can be armed because a different page of its partition owes a record. The simplification is not worth giving up that precision.

06Where B falls short