Skip to content

Hardcode constant detection into the cascading compressor#8667

Merged
connortsui20 merged 1 commit into
developfrom
claude/hardcode-schemes-compressor-97kru1
Jul 7, 2026
Merged

Hardcode constant detection into the cascading compressor#8667
connortsui20 merged 1 commit into
developfrom
claude/hardcode-schemes-compressor-97kru1

Conversation

@connortsui20

Copy link
Copy Markdown
Member

Summary

Closes: #8666

The five per-type constant schemes (bool/int/float/string/binary) were "half builtin": they lived in vortex-compressor's builtins module but still had to be registered. Constant handling was also scattered across three places: the compressor's own all-null short-circuit, the per-type constant schemes, and an inline is_constant check in TemporalScheme.

Constant detection is now built directly into CascadingCompressor. A new crate-private constant module detects constant leaves using the cheapest available evidence per type.

Changes in behavior:

  • The big change is that constant detection is unconditional:, as it applies even for a compressor constructed with an empty scheme list, and can no longer be disabled via exclude_schemes. I think this is fine because it is essentially never a good idea to not check for a constant array (except maybe for tiny arrays, but I don't think we need to worry about that), and we have no code that tries to exclude constant array.
  • Constant detection now covers ALL leaf types uniformly, adding decimal and extension arrays.
  • Constant string/binary arrays no longer pay for dict/FSST sampling before the (previously deferred, last-registered) constant check happens.
  • Also a small perf change: The old scheme-based path found the first valid element with a per-index is_valid loop that re-derived validity on every call; the new compress_constant function materializes the validity mask once and uses Mask::first.

@connortsui20 connortsui20 requested review from AdamGS and robert3005 July 7, 2026 10:13
@connortsui20 connortsui20 added the changelog/chore A trivial change label Jul 7, 2026
The five per-type constant schemes (bool/int/float/string/binary) were
"half builtin": they lived in vortex-compressor's builtins module but
had to be registered by downstream crates for constant arrays to be
detected. Constant handling was also scattered across three places: the
compressor's own all-null short-circuit, the per-type constant schemes,
and an inline is_constant check in TemporalScheme.

Constant detection is now built directly into CascadingCompressor. A new
crate-private `constant` module detects constant leaves using the
cheapest available evidence per type (distinct counts when another
scheme already requested them, max-min for integers, bool stats, and a
vectorized is_constant scan otherwise) and encodes them as
ConstantArray, wrapped in MaskedArray when nulls are present. The check
runs after the empty/all-null short-circuits and before any scheme
evaluation, and is skipped while compressing samples since a constant
sample does not imply a constant array.

Changes in behavior:

- Constant detection now covers ALL leaf types uniformly, adding decimal
  and extension arrays. Constant extension arrays (e.g. timestamps)
  short-circuit to a top-level ConstantArray instead of competing with
  storage compression by size, which lets TemporalScheme drop its inline
  is_constant check.
- Constant detection is unconditional: it applies even for a compressor
  constructed with an empty scheme list, and can no longer be disabled
  via exclude_schemes.
- Constant string/binary arrays no longer pay for dict/FSST sampling
  before the (previously deferred, last-registered) constant check
  resolves; the constant pre-pass runs before all scheme estimation.

The old scheme-based path found the first valid element with a per-index
is_valid loop that re-derived validity on every call; the new
compress_constant materializes the validity mask once and uses
Mask::first.

Signed-off-by: Connor Tsui <connor@spiraldb.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLaoUmBcuLPmkxrwcPAEgZ
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20 connortsui20 force-pushed the claude/hardcode-schemes-compressor-97kru1 branch from 7574535 to 561de5b Compare July 7, 2026 10:18
@codspeed-hq

codspeed-hq Bot commented Jul 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚡ 5 improved benchmarks
❌ 1 regressed benchmark
✅ 1601 untouched benchmarks
⏩ 42 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation chunked_varbinview_canonical_into[(1000, 10)] 154.7 µs 191 µs -18.99%
Simulation encode_varbin[(1000, 8)] 161.2 µs 140 µs +15.13%
Simulation encode_varbin[(1000, 4)] 159.4 µs 139.1 µs +14.62%
Simulation encode_varbin[(1000, 32)] 164.9 µs 144.8 µs +13.91%
Simulation encode_varbin[(1000, 2)] 156.3 µs 138.5 µs +12.87%
Simulation chunked_varbinview_opt_canonical_into[(100, 100)] 341 µs 305.8 µs +11.51%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/hardcode-schemes-compressor-97kru1 (561de5b) with develop (a6e3d3e)

Open in CodSpeed

Footnotes

  1. 42 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@connortsui20 connortsui20 marked this pull request as ready for review July 7, 2026 10:21
@connortsui20 connortsui20 merged commit 8f72595 into develop Jul 7, 2026
71 of 73 checks passed
@connortsui20 connortsui20 deleted the claude/hardcode-schemes-compressor-97kru1 branch July 7, 2026 10:56
brancz pushed a commit to polarsignals/vortex that referenced this pull request Jul 9, 2026
Scalar serialization writes ScalarValue::Tuple as a protobuf ListValue
regardless of whether the dtype is List or FixedSizeList, but
list_from_proto only accepted List. Since vortex-data#8667 made constant detection
unconditional in the cascading compressor, a constant FixedSizeList
column (e.g. a UUID stored as fixed_size_list(u8)[16]) is written as a
ConstantArray whose scalar metadata then fails to deserialize on read:

    expected List dtype for ListValue, got fixed_size_list(u8)[16]

Scalar validation already handles FixedSizeList + Tuple (including the
size check), so deserialization was the only gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Frederic Branczyk <frederic.branczyk@polarsignals.com>
brancz added a commit to polarsignals/vortex that referenced this pull request Jul 9, 2026
Scalar serialization writes ScalarValue::Tuple as a protobuf ListValue
regardless of whether the dtype is List or FixedSizeList, but
list_from_proto only accepted List. Since vortex-data#8667 made constant detection
unconditional in the cascading compressor, a constant FixedSizeList
column (e.g. a UUID stored as fixed_size_list(u8)[16]) is written as a
ConstantArray whose scalar metadata then fails to deserialize on read:

    expected List dtype for ListValue, got fixed_size_list(u8)[16]

Scalar validation already handles FixedSizeList + Tuple (including the
size check), so deserialization was the only gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Frederic Branczyk <fbranczyk@gmail.com>
brancz added a commit to polarsignals/vortex that referenced this pull request Jul 9, 2026
Scalar serialization writes ScalarValue::Tuple as a protobuf ListValue
regardless of whether the dtype is List or FixedSizeList, but
list_from_proto only accepted List. Since vortex-data#8667 made constant detection
unconditional in the cascading compressor, a constant FixedSizeList
column (e.g. a UUID stored as fixed_size_list(u8)[16]) is written as a
ConstantArray whose scalar metadata then fails to deserialize on read:

    expected List dtype for ListValue, got fixed_size_list(u8)[16]

Scalar validation already handles FixedSizeList + Tuple (including the
size check), so deserialization was the only gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Frederic Branczyk <fbranczyk@gmail.com>
brancz added a commit to polarsignals/vortex that referenced this pull request Jul 9, 2026
Scalar serialization writes ScalarValue::Tuple as a protobuf ListValue
regardless of whether the dtype is List or FixedSizeList, but
list_from_proto only accepted List. Since vortex-data#8667 made constant detection
unconditional in the cascading compressor, a constant FixedSizeList
column (e.g. a UUID stored as fixed_size_list(u8)[16]) is written as a
ConstantArray whose scalar metadata then fails to deserialize on read:

    expected List dtype for ListValue, got fixed_size_list(u8)[16]

Scalar validation already handles FixedSizeList + Tuple (including the
size check), so deserialization was the only gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Frederic Branczyk <fbranczyk@gmail.com>
connortsui20 pushed a commit that referenced this pull request Jul 9, 2026
…8696)

Scalar serialization writes `ScalarValue::Tuple` as a protobuf
`ListValue` for both `List` and `FixedSizeList` dtypes, but
`list_from_proto` only accepted `List`. Since #8667 made constant
detection unconditional, a constant fixed-size list column (e.g. UUIDs
stored as `fixed_size_list(u8)[16]`) is compressed to a `ConstantArray`
whose scalar metadata then fails to deserialize on read:

    expected List dtype for ListValue, got fixed_size_list(u8)[16]

so files written this way come back unreadable. Scalar validation
already handles `FixedSizeList` + `Tuple` (including the size check), so
deserialization was the only gap.

Includes a proto round-trip test that fails without the fix.

---------

Signed-off-by: Frederic Branczyk <fbranczyk@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/chore A trivial change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardcode ConstantScheme logic into the compressor

3 participants