Skip to content

A record that shrinks back inside its slot stays a chunk chain for ever - nothing ever un-spills it #6178

Description

@lvca

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions