Write a segment over instead of writing it again - #412
Open
tamnd wants to merge 1 commit into
Open
Conversation
A segment used to be an index followed by a body, with every chunk sitting immediately after the chunk before it, so a chunk that grew by a byte moved every chunk after it and the only way to change one value was to write the column again. That is why a one cell write cost the whole column and why an insert cost 143 ms whatever the table held. The body now comes first and the trailer last, and a chunk is wherever its entry says it is: start, len, and one u64 of whatever the layout needs, the fence for MiniBlock and the zipped size for FullZip. A chunk written again goes to the tail and leaves a hole, live_bytes counts what the entries still reach, and a segment more than half garbage is laid out again on the next write, so the waste is bounded at 2x and the copy is paid once per doubling rather than once per write. Blocks the unchanged body prefix still covers are kept by pointer, so an append writes about two blocks whatever the column holds and frees none of the ones it had. The decoded pool is keyed on the first block and that key survives a rewrite, so the file forgets the segment's pooled state when it reuses it. The label bitset gets the same treatment. A change reads the chunks holding the rows it names, one read per chunk rather than one per row, and writes those chunks and the ones past the old end. An appended row takes its table's own label and nothing else unless a change says otherwise. Checksums move from one per segment to one per block. A full read verifies them and a point read does not, because crc32c over a 256 KiB block costs about twice what the read itself costs and the checkpoint already promised the block is the one it wrote. The zone map becomes a bound rather than a census: a write widens min and max and never narrows them, so a value inside the zone may be absent and a value outside it is certainly absent. Format version and min reader version go to 2. A version 1 binary refuses a version 2 file rather than reading the trailer as a body, and there is no converter because nothing writes version 1 any more.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last four boxes on #391.
A segment used to be an index followed by a body, with every chunk immediately after the one before it, so a chunk that grew by a byte moved every chunk after it and the only way to change one value was to write the column again. That is why a one cell write cost the whole column and why an insert cost 143 ms whatever the table held.
The body now comes first and the trailer last, and a chunk is wherever its entry says it is: start, len, and one u64 of whatever the layout needs, the fence for MiniBlock and the zipped size for FullZip. A chunk written again goes to the tail and leaves a hole,
live_bytescounts what the entries still reach, and a segment more than half garbage is laid out again on the next write, so the waste is bounded at 2x and the copy is paid once per doubling rather than once per write.Blocks the unchanged body prefix still covers are kept by pointer, so an append writes about two blocks whatever the column holds and frees none of the ones it had. The decoded pool is keyed on the first block and that key survives a rewrite, so the file forgets the segment's pooled state when it reuses it.
The label bitset gets the same treatment. A change reads the chunks holding the rows it names, one read per chunk rather than one per row, and writes those chunks and the ones past the old end. An appended row takes its table's own label and nothing else unless a change says otherwise.
Checksums move from one per segment to one per block. A full read verifies them and a point read does not, and the reason is read amplification rather than the cost of the crc: a block is 256 KiB and a MiniBlock chunk is 1024 rows, so verifying to answer a point read would mean reading thirty to sixty times more bytes than the answer needs. The point path bounds every access by the meta instead, and rejects extents outside the body, truncated chunks and count mismatches. The commit message on the first draft of this said the crc cost about twice the read, which was wrong and is not the argument.
The zone map becomes a bound rather than a census: a write widens min and max and never narrows them, so a value inside the zone may be absent and a value outside it is certainly absent.
The deferral bounds are re-derived rather than restated.
DEFERRED_CELLSis 1024 and its comment said a few hundred cells is a copy of a few kilobytes, which named the wrong number twice.written_cellscharges one per appended row, not one per cell, so a row of a wide table costs the same as a cell of a narrow one. ACellis 24 bytes and a column entry(u32, Cell)is 32, so a row of a ten column table is 320 bytes and a full patch is about 320 KiB copied on every commit that adds to it. That is the ceiling rather than the usual case, and the string bound below it is what keeps a patch of a thousand long strings from being the real number.Format version and min reader version go to 2. A version 1 binary refuses a version 2 file rather than reading the trailer as a body, and there is no converter because nothing writes version 1 any more.
Workspace tests pass, 194 plus 309 plus the rest, and clippy is clean on
--all-targets.