Skip to content

Columnar segment format with per-column zstd + blake3 checksum - #3

Merged
pbudzik merged 1 commit into
mainfrom
feat/columnar-segment-format
May 16, 2026
Merged

Columnar segment format with per-column zstd + blake3 checksum#3
pbudzik merged 1 commit into
mainfrom
feat/columnar-segment-format

Conversation

@pbudzik

@pbudzik pbudzik commented May 16, 2026

Copy link
Copy Markdown
Owner

Summary

Closes review item #6. Replaces the length-prefixed bincode-row segment format with a real columnar layout (spec §6.1) — per-column zstd compression, blake3 footer checksum, format-versioned headers, and readers that reject corrupt or truncated files.

File format

header:
  magic        b"UDBRAW1\n"  (8 bytes)
  version      u8 = 1
  row_count    u32 LE
  num_columns  u16 LE

per column (×14):
  name_len         u16 LE
  name             utf-8 bytes
  encoding         u8  (0 = Plain)
  codec            u8  (0 = None, 1 = Zstd, 2 = Lz4)
  compressed_len   u32 LE
  compressed_bytes bytes

footer:
  checksum   u64 LE  (low 8 bytes of blake3 over everything above)
  magic_end  b"UDBEND01"  (8 bytes)

Each column payload before compression is a bincode-serialized Vec<T> of the column's native type (Vec<String>, Vec<i64>, Vec<i128>, Vec<Option<String>>, Vec<SmallDimensions>, etc.). Zstd is applied per-column. Co-locating homogeneous data makes zstd's history window dramatically more effective — the new compression test packs 10k repetitive events into under 500 KB.

Extensibility

  • Encoding enum reserves discriminants for Dictionary / Delta / Zigzag / RLE — these can be added without bumping the file format version, and the reader rejects unknown values with a corrupt-segment error.
  • Codec enum covers None / Zstd / Lz4; the reader picks the decoder per column header.
  • Adding a column is additive; old readers fail loud ("missing column").

Validation

Every open verifies start magic + version + end magic + checksum. Any deviation surfaces as InvalidData. Two integration tests verify this:

  • checksum_mismatch_is_rejected — flips a byte inside the body
  • missing_end_magic_is_rejected — truncates the tail

Plumbing

  • RawSegmentWriter::finish() now returns (row_count, checksum).
  • Flusher and compaction worker thread the checksum into SegmentMeta.checksum, populating a field that was previously hardcoded to 0.
  • Reader API unchanged (read_next(), scan_by_account()), so the query executor needed no edits.

Test plan

  • cargo build --all-targets clean with -D warnings
  • cargo test --all-targets — 23 tests pass (6 unit + 8 durability + 4 bucketing + 5 segment_format)
  • CI green on this PR

Still on the backlog

🤖 Generated with Claude Code

…§6.1)

Replaces the length-prefixed bincode-row segment format with a real
columnar layout:

  magic "UDBRAW1\n" + version + row_count + num_columns
  for each of 14 columns:
    name + encoding + codec + compressed_len + compressed_bytes
  footer: u64 checksum (blake3) + magic "UDBEND01"

Each column payload pre-compression is bincode-serialized Vec<T> of
the column's native type; zstd is applied per-column. Co-locating
homogeneous data (string IDs together, all timestamps together, etc.)
makes zstd's history window much more effective — 10k repetitive
events fit in under 500 KB on disk (segment_format::compression test).

Writer + reader rewritten; flusher and compaction worker thread the
returned checksum through into SegmentMeta, populating a field that
was previously hardcoded to 0.

Format extensibility:
- Encoding discriminants reserved for Dictionary / Delta / Zigzag / RLE;
  reader rejects unknown values with a corrupt-segment error.
- Codec discriminants cover None / Zstd / Lz4; reader picks the
  decoder per column.
- Adding a column is additive; old readers fail loud.

Validation on read: start magic + version + end magic + checksum must
all match, or open fails with InvalidData. Two new tests verify byte-
flip and truncation rejection.

tests/segment_format.rs (5 tests): round-trip preservation including
Correction events and i128 quantities; empty-segment validity;
checksum mismatch rejection; truncation rejection; compression
sanity. README adds a Segment format section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pbudzik
pbudzik merged commit 20c0b77 into main May 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant