Skip to content

Cascade FSST offset/length children (#299)#302

Merged
dfa1 merged 1 commit into
mainfrom
fsst-cascade-offsets
Jul 25, 2026
Merged

Cascade FSST offset/length children (#299)#302
dfa1 merged 1 commit into
mainfrom
fsst-cascade-offsets

Conversation

@dfa1

@dfa1 dfa1 commented Jul 24, 2026

Copy link
Copy Markdown
Owner

What

FsstEncodingEncoder emitted its two per-row children — the uncompressed lengths and the cumulative code offsets — as raw vortex.primitive leaves (2–4 bytes/row each), never cascaded. The Rust reference bitpacks the monotonic offsets and constant-folds the lengths of fixed-width columns. Leaving them raw was the single largest remaining size gap on the nyc-311 corpus.

Per-chunk encoding tree, before vs after:

vortex-jni (Rust)              vortex-java before            vortex-java after
  vortex.fsst                    vortex.fsst                   vortex.fsst
    vortex.constant   (lengths)    vortex.primitive  (raw)       vortex.constant      (lengths)
    fastlanes.bitpacked (offsets)  vortex.primitive  (raw)       fastlanes.bitpacked  (offsets)

How

encodeCascade now exposes both children as open primitive child slots so the cascade bitpacks the offsets and picks vortex.constant for fixed-width lengths. The shared compression work is extracted into a compress() helper; encode() keeps the terminal raw-primitive layout for cascade depth 0.

No wire or reader change — the reader already decodes bitpacked/constant FSST children (it reads Rust files that use exactly this shape; RaincloudConformance + interop suites cover it).

Impact (real nyc-311 corpus, 18.49M rows × 42 cols)

vortex-java vs vortex-jni (1762 MB)
Before this PR 1934.23 MB 1.10×
After 1732.14 MB 0.98×

−202 MB from this change alone — vortex-java is now smaller than the Rust reference on nyc-311. This helps every FSST-encoded column, not just the date columns that surfaced it.

Testing

  • New unit tests: encodeCascade exposes the two offset children as open slots; falls back to terminal at depth 0.
  • FSST round-trip (33 encoder tests) + 217 Java-writes-Rust-reads interop tests pass.
  • Full ./mvnw verify green.

🤖 Generated with Claude Code

FsstEncodingEncoder emitted its two per-row children — uncompressed lengths and
cumulative code offsets — as raw vortex.primitive leaves (2-4 bytes/row each),
never cascaded. The Rust reference bitpacks the monotonic offsets and
constant-folds the lengths of fixed-width columns; leaving them raw was the
single largest remaining size gap on nyc-311 (the date columns compress the
bytes with FSST but paid full-width offsets).

encodeCascade now exposes both children as open primitive child slots so the
cascade bitpacks the offsets and picks vortex.constant for fixed-width lengths.
The shared compression work is extracted into a compress() helper; encode()
keeps the terminal raw-primitive layout for cascade depth 0. No wire/reader
change — the reader already decodes bitpacked/constant FSST children (it reads
Rust files that use them).

nyc-311 re-encode: 1934.23 MB -> 1732.14 MB (1.10x -> 0.98x vortex-jni), i.e.
vortex-java is now smaller than the Rust reference. Round-trip + 217
Java-writes-Rust-reads interop tests pass; full verify green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dfa1
dfa1 merged commit 147ee0e into main Jul 25, 2026
6 checks passed
@dfa1
dfa1 deleted the fsst-cascade-offsets branch July 25, 2026 06:18
dfa1 added a commit that referenced this pull request Jul 25, 2026
encodeUtf8Cascade exposed the dict values pool as an open child (#299 Finding 2b)
but left the codes as a raw vortex.primitive leaf — paying the full U8/U16/U32
code width per row. The Rust reference bitpacks dict codes (fastlanes.bitpacked),
and the primitive dict path here already exposes its codes the same way; only the
Utf8 path was missing it.

Now both children are left open: codes (index 0) as a typed primitive array the
cascade bitpacks, and the values pool (index 1) for the FSST/VarBin competition.
No wire/reader change — the reader already decodes bitpacked dict codes (it reads
Rust files that use them).

nyc-311 re-encode: 1934.23 MB -> 1862.23 MB (1.10x -> 1.06x vortex-jni); the win
spans every per-chunk Utf8 dict column, not only the street columns that surfaced
it. Composes with #302 (they compress different children). Round-trip + 217
Java-writes-Rust-reads interop tests pass; full verify green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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