Deliberately left out of scope in PR #6170 (#6163), filed so the decision is recorded rather than forgotten.
What happens
Once a record has spilled, it is a chunk chain for the rest of its life. When it shrinks back to something its slot's own region can hold, updateMultiPageRecord frees the continuation chunks and leaves a chain of exactly one chunk - a FIRST_CHUNK marker, a 4-byte size, an 8-byte next-chunk pointer that is 0, and the content. It is never turned back into a plain record, however small it becomes.
After #6163 this is much more common than it used to be: the head chunk now takes back the room its region has, so a record that oscillates spends much of its life as a single-chunk chain rather than as a chain spread over pages.
What it costs
- 13 bytes per record, permanently, over what a plain record of the same content needs.
- Every read and write takes the chunk path:
getRecordInternal walks a chain, updateRecordInternal goes through the FIRST_CHUNK branch, the disjoint-slot merge uses SLOT_KIND_FIRST_CHUNK plus chunkChainTailFingerprint (a walk of a chain that has nothing past its head, to produce EMPTY_CHUNK_TAIL_FINGERPRINT) instead of the plain-record path.
bucket.check() reports it as a multi-page record for ever, which makes the layout statistics say something that is no longer true - and those statistics are what several tests assert on.
None of it is a correctness problem. It is a shape that no longer matches the data.
Why it was not done in #6170
Un-spilling changes the slot's SHAPE (chunk head -> plain record), which is a different kind of write from the ones the merge tracks today: the pre-image is a chunk image and the final image a plain record, so it needs its own SLOT_KIND_* and a replay that puts the plain marker back - the mirror of SLOT_KIND_RECORD_SPILLED_TO_CHUNK, which exists for exactly the opposite transition. That is a self-contained piece of work and did not belong in a fix for the sizing rule.
Suggested approach
In updateMultiPageRecord, when the new content fits the slot's region as a PLAIN record (getNumberSpace(size) + size <= regionEnd - recordPositionInPage) and the chain has no chunk past the head, write it as a plain record, free the chain, and track it as a new SLOT_KIND_CHUNK_COLLAPSED_TO_RECORD whose replay re-checks the committed region exactly as the spill's replay does. The region arithmetic and the replay symmetry both already exist since #6163; this is the third transition between the same two shapes.
Measure first: how often does a spilled record shrink back that far in practice? If the answer is "almost never outside tests", the 13 bytes are not worth a new slot kind, and this should be closed with that written down.
Deliberately left out of scope in PR #6170 (#6163), filed so the decision is recorded rather than forgotten.
What happens
Once a record has spilled, it is a chunk chain for the rest of its life. When it shrinks back to something its slot's own region can hold,
updateMultiPageRecordfrees the continuation chunks and leaves a chain of exactly one chunk - aFIRST_CHUNKmarker, a 4-byte size, an 8-byte next-chunk pointer that is 0, and the content. It is never turned back into a plain record, however small it becomes.After #6163 this is much more common than it used to be: the head chunk now takes back the room its region has, so a record that oscillates spends much of its life as a single-chunk chain rather than as a chain spread over pages.
What it costs
getRecordInternalwalks a chain,updateRecordInternalgoes through theFIRST_CHUNKbranch, the disjoint-slot merge usesSLOT_KIND_FIRST_CHUNKpluschunkChainTailFingerprint(a walk of a chain that has nothing past its head, to produceEMPTY_CHUNK_TAIL_FINGERPRINT) instead of the plain-record path.bucket.check()reports it as a multi-page record for ever, which makes the layout statistics say something that is no longer true - and those statistics are what several tests assert on.None of it is a correctness problem. It is a shape that no longer matches the data.
Why it was not done in #6170
Un-spilling changes the slot's SHAPE (chunk head -> plain record), which is a different kind of write from the ones the merge tracks today: the pre-image is a chunk image and the final image a plain record, so it needs its own
SLOT_KIND_*and a replay that puts the plain marker back - the mirror ofSLOT_KIND_RECORD_SPILLED_TO_CHUNK, which exists for exactly the opposite transition. That is a self-contained piece of work and did not belong in a fix for the sizing rule.Suggested approach
In
updateMultiPageRecord, when the new content fits the slot's region as a PLAIN record (getNumberSpace(size) + size <= regionEnd - recordPositionInPage) and the chain has no chunk past the head, write it as a plain record, free the chain, and track it as a newSLOT_KIND_CHUNK_COLLAPSED_TO_RECORDwhose replay re-checks the committed region exactly as the spill's replay does. The region arithmetic and the replay symmetry both already exist since #6163; this is the third transition between the same two shapes.Measure first: how often does a spilled record shrink back that far in practice? If the answer is "almost never outside tests", the 13 bytes are not worth a new slot kind, and this should be closed with that written down.