Skip to content

fix: read and write string and binary chunks past 2 GiB in every encoding - #637

Merged
dfa1 merged 1 commit into
mainfrom
fix/633-large-chunks
Nov 1, 2026
Merged

dfa1 merged 1 commit into
mainfrom
fix/633-large-chunks

Conversation

@dfa1

@dfa1 dfa1 commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Closes #633. Follow-up to #629, which covered FSST and the segment path.

Fixes (all found on real Raincloud files)

  • VarBinOffsetArray reads U32 offsets unsigned — Rust-written varbin with a 2–4 GiB data buffer (mmmu) failed as out of range; an offset past the buffer still fails as VortexException.
  • VarBinEncodingEncoder copies rows straight into its off-heap buffer (i64 offsets) when they don't fit one byte[], instead of refusing the chunk.
  • VarBinViewEncodingEncoder fills data buffers up to Integer.MAX_VALUE bytes each (Rust's MAX_BUFFER_LEN), views naming their buffer, instead of wrapping an int total.
  • ZstdEncodingEncoder sums its payload in a long and closes a varbin frame before 1 GiB; ZstdEncodingDecoder keeps its 2 GiB per-frame cap (a guard until ADR 0004's resource limits), so every frame Java writes stays readable. Was a raw IndexOutOfBoundsException on a compact import of peoples-speech.
  • ZstdEncodingDecoder rebuilds varbin rows with i64 offsets past 2 GiB.

Tests

  • Buffer and frame caps are injectable (package-private constructors): unit tests reach both splits with small caps — exact fill, overflow, inlined rows, empty/all-inlined, a row over the cap, values-per-frame combined with the byte cap. These run in CI.
  • VarBinArrayTest$U32Offsets: unsigned read past 2^31, and a hostile offset past the buffer still rejected.
  • LargeChunkInteropIntegrationTest (renamed from FsstLargeChunkInteropIntegrationTest, large tag, opt-in): forces fsst/varbin/varbinview/zstd on ~2.46 GB of rows; vortex-jni and vortex-java read the rows past 2 GiB. The three new cases fail on main.
  • Java writes i64 varbin offsets, so the unsigned u32 read is cross-checked only by Raincloud's mmmu (now passes conformance; a fixture that size can't be committed).

Real data: mmmu passes conformance and imports (CSV identical); a compact import of peoples-speech-clean-validation exports a byte-identical CSV.

CHANGELOG: #629's line narrowed to FSST (it claimed every encoding), #633 added.

Reviewed independently (vortex-reviewer agent): no blockers; its should-fix items (CI coverage of the split logic, a negative u32 test, a test javadoc that overstated what the varbin case covers) are addressed in this commit. Rust's 8192-values-per-frame zstd framing is a separate parity gap: #636.

🤖 Generated with Claude Code

https://claude.ai/code/session_011fRbYryTxfcu5j2CXwkSzd

…ding

of the int limits on a chunk past 2 GiB:

- VarBinOffsetArray read u32 offsets as signed ints, so a Rust-written
  varbin column with a data buffer between 2 and 4 GiB failed as out of
  range (mmmu). U32 offsets are read unsigned; an offset past the data
  buffer still fails as a VortexException.
- VarBinEncodingEncoder refused a chunk past 2 GB of row bytes, which no
  byte[] holds contiguously. It copies the rows straight into its off-heap
  buffer, with i64 offsets.
- VarBinViewEncodingEncoder summed its data in an int and wrapped. Its rows
  now fill data buffers up to Integer.MAX_VALUE bytes each (Rust's
  MAX_BUFFER_LEN), the views naming their buffer.
- ZstdEncodingEncoder summed its length-prefixed payload in an int and
  wrapped (raw IndexOutOfBoundsException on a compact import of
  peoples-speech). The total is a long, and a varbin frame closes before
  1 GiB: ZstdEncodingDecoder caps a frame at 2 GiB until ADR 0004's resource
  limits exist, so every frame this writer emits stays readable.
- ZstdEncodingDecoder rebuilt varbin rows with i32 offsets; it takes i64
  ones once the rows pass 2 GiB.

Tests: the buffer and frame caps are injectable, so unit tests reach both
splits with small caps (exact fill, overflow, inlined rows, empty, a row
over the cap, values per frame combined with the byte cap).
LargeChunkInteropIntegrationTest (the former FsstLargeChunk one, `large`
tag) forces fsst, varbin, varbinview and zstd on ~2.46 GB of rows and has
vortex-jni and vortex-java read the rows past 2 GiB; the three new cases
fail without this change. Java writes i64 varbin offsets, so the unsigned
u32 read is pinned by VarBinArrayTest and cross-checked only by Raincloud's
mmmu, which now passes conformance; mmmu also imports, and a compact import
of peoples-speech exports a byte-identical CSV.

Rust's zstd writes 8192 values per frame where Java's compact writes one
frame per array: #636.

Closes #633

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fRbYryTxfcu5j2CXwkSzd
@dfa1
dfa1 force-pushed the fix/633-large-chunks branch from 92687cf to b7191dd Compare November 1, 2026 20:13
@dfa1
dfa1 merged commit b8ffd79 into main Nov 1, 2026
6 checks passed
@dfa1
dfa1 deleted the fix/633-large-chunks branch November 1, 2026 20:20
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.

Values past 2 GiB in one chunk: varbin read/write and zstd write still use int sizes

1 participant