Add bounded codec runtime and compression handlers - #19285
Conversation
9adf1cc to
db04987
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## xiangfu0/codex/codec-stack/01-config #19285 +/- ##
===========================================================================
+ Coverage 39.17% 67.22% +28.04%
- Complexity 1423 1424 +1
===========================================================================
Files 3465 3476 +11
Lines 220562 221055 +493
Branches 35201 35285 +84
===========================================================================
+ Hits 86409 148596 +62187
+ Misses 126278 60594 -65684
- Partials 7875 11865 +3990
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
db04987 to
8088771
Compare
|
Two notes from review:
|
There was a problem hiding this comment.
Pull request overview
Adds a closed, bounded codec runtime to pinot-segment-local, building on the codec DSL while remaining disconnected from index formats.
Changes:
- Adds LZ4, Snappy, Gzip, and Zstd handlers.
- Adds pipeline validation, bounded execution, and buffer cleanup.
- Adds round-trip, corruption, ordering, visibility, and bounds tests.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
ZstdCodecDefinitionTest.java |
Tests empty Zstd frames. |
CompressionCodecCorruptInputTest.java |
Tests corrupt inputs and bounds. |
CodecRegistryTest.java |
Tests registry closure and aliases. |
CodecPipelineValidatorTest.java |
Tests pipeline ordering rules. |
CodecPipelineExecutorTest.java |
Tests execution, bounds, and visibility. |
ZstdCodecDefinition.java |
Implements bounded Zstd handling. |
SnappyCodecDefinition.java |
Implements Snappy handling. |
Lz4CodecDefinition.java |
Implements length-prefixed LZ4 handling. |
GzipCodecDefinition.java |
Implements framed Deflate/Gzip handling. |
CodecRegistry.java |
Defines the closed codec registry. |
CodecPipelineValidator.java |
Validates stage ordering and context. |
CodecPipelineExecutor.java |
Executes bounded codec pipelines. |
CodecOptions.java |
Defines codec option contracts. |
CodecKind.java |
Classifies pipeline stages. |
CodecDefinition.java |
Defines codec metadata contracts. |
CodecContext.java |
Carries stored-type context. |
CodecBufferUtils.java |
Provides direct-buffer utilities. |
ChunkCodecHandler.java |
Defines codec execution operations. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (payloadLimit < Integer.BYTES) { | ||
| throw new IOException("GZIP payload too short to contain uncompressed-size footer: " + payloadLimit + " bytes"); | ||
| } | ||
| int decompressedSize = src.getInt(payloadLimit - Integer.BYTES); |
| ByteBuffer compressed = src.duplicate(); | ||
| compressed.position(0); | ||
| compressed.limit(src.limit() - Integer.BYTES); |
| output = i == 0 ? dst : ByteBuffer.allocateDirect(maxOutputAfterStage[i - 1]); | ||
| _stages.get(i).decodeInto(current, output); |
…TD(0) - Snappy was the only pipeline codec missing the sanity cap on the header-declared decompressed size; a corrupt or hostile header must not drive allocation. Rather than adding a fourth private copy of the cap, hoist it into CodecBufferUtils.checkDeclaredDecompressedSize and route all four codecs' guard sites through it, so the bound cannot be omitted from a future codec or silently drift between implementations. Corrupt- input tests cover both Snappy decode paths at the cap boundary (cap + 1). - CodecRegistry.register() rejects the reserved wrapper name via the shared CodecSpecParser.REMOVED_WRAPPER_NAME constant as defense in depth alongside the parser and CodecInvocation reservations. - ZSTD(0) is rejected: zstd treats level 0 as "use the default level", so it would behave identically to ZSTD(3) under a second canonical spelling, and the canonical spec is frozen into segment headers. Rejection is reversible later; acceptance is not.
8088771 to
bfe108c
Compare
|
Pushed `bfe108c3d9` (rebased onto the updated #19284 head) addressing review findings on the runtime. Snappy decode size cap, fixed as a pattern rather than a fourth copy. `SnappyCodecDefinition.decode` allocated from the header-declared decompressed size with only a negative check, while LZ4/GZIP/ZSTD each carried a 1 GiB sanity cap. That gap is exactly the drift a per-codec hand-rolled constant produces, so instead of adding a fourth private copy I hoisted it into `CodecBufferUtils.checkDeclaredDecompressedSize(declared, codec, source)` and routed all four codecs through it. A future codec cannot omit the bound, and the policy is single-sourced. New corrupt-input tests cover both Snappy paths — including `decodeInto`, which is the one the bounded executor actually uses in production — at the boundary (cap + 1), plus a garbage-input test Snappy was missing. `CodecRegistry.register()` rejects the reserved name `CODEC` as defense in depth alongside the parser and `CodecInvocation` reservations, referencing `CodecSpecParser.REMOVED_WRAPPER_NAME` (made public in #19284) rather than re-spelling the literal. `ZSTD(0)` is now rejected; the accepted range is `[1, maxCompressionLevel]`. zstd treats level 0 as "use the default level", so `ZSTD(0)` and `ZSTD(3)` would be identical behavior under two canonical spellings — the same one-spelling-per-behavior rule that motivates the leading-zero fix, and worth settling before the header format freezes. Use bare `ZSTD` for the default. One follow-up worth filing separately (deliberately not fixed here, since it is a different long-shipped subsystem): the legacy `SnappyDecompressor.decompressedLength()` in `io/compression` has the same unbounded-header shape, and shipped callers such as `VarByteChunkForwardIndexReaderV4` allocate from it. Verification: 53 codec tests plus 177 downstream gate/validation/handler tests green (`CodecPipelineExecutorTest`, `CodecPipelineValidatorTest`, `CodecRegistryTest`, `CompressionCodecCorruptInputTest`, `ZstdCodecDefinitionTest`, `ForwardIndexTypeTest`, `TableConfigUtilsTest`, `ForwardIndexCreatorFactoryTest`, `OpenStructIndexTypeTest`, `ForwardIndexHandlerTest`); spotless/checkstyle/license clean. |
|
Note: pushed a master sync to this branch (merge commit, no code changes of its own) so the downstream stack includes the #19282 chunk-caching fix that the reload tests in #19308 depend on. The full stack continuing this PR: #19305 (DELTA/DELTADELTA) → #19306 (T64/GORILLA) → #19307 (V7 format) → #19308 (reload + enable) → #19309 (integration tests + docs). Each is based on its predecessor's branch. |
Context
This is the second layer extracted from #18229 and depends on #19284 through GitHub's native stacked-PR feature.
What changed
pinot-segment-local; it does not publish a pluggable codec SPI.ZSTD(level)options and the legacyZSTANDARDinput alias.Safety and scope
Only
CodecPipelineExecutoris public across local packages; registry, handlers, options, context, and validator remain package-private. The only public decode entry point requires explicit decoded-size, per-stage, and cumulative bounds.This PR changes no forward-index creator, reader, writer, handler, config, or on-disk format. All fail-closed gates from #19284 remain in place, so
codecSpecis still not usable after this layer alone.Tests
pinot-segment-localgit diff --checkand stackrange-diffare clean