Skip to content

fix(puffin): implement LZ4 footer compression - #2921

Open
1fanwang wants to merge 2 commits into
apache:mainfrom
1fanwang:fix/puffin-footer-lz4-compression
Open

fix(puffin): implement LZ4 footer compression#2921
1fanwang wants to merge 2 commits into
apache:mainfrom
1fanwang:fix/puffin-footer-lz4-compression

Conversation

@1fanwang

Copy link
Copy Markdown

Which issue does this PR close?

Supersedes #2438, which the stale bot closed after it sat in the review queue. Same change, rebased onto current main, with @xanderbailey's review feedback from that PR already folded in. GitHub refuses to reopen the old PR, hence the new one.

What changes are included in this PR?

The Puffin spec allows a footer payload to be uncompressed or LZ4-compressed. PuffinWriter already exposes a compress_footer flag that records LZ4 as the footer codec, and validate_puffin_compression already accepts Lz4 as a valid Puffin codec — but the codec itself was never implemented, so close() with compress_footer = true failed:

FeatureUnsupported => LZ4 compression is not supported currently

The reader had the symmetric gap: a Puffin file carrying an LZ4-compressed footer could not be read back.

This implements the codec with lz4_flex in the frame format — a single frame with content size present, as the spec requires. lz4_flex was already in the lock file as a transitive parquet dependency, so no new crate enters the tree.

Are these changes tested?

Yes. Unit tests cover the codec and the Puffin writer/reader:

  • Codec round-trips (empty, highly compressible, less compressible) across LZ4, Zstd and Gzip, plus an LZ4 frame magic-number assertion.
  • The issue reproducer — close() with compress_footer = true — now succeeds, and footer metadata plus blobs round-trip through an LZ4 footer.
  • The reader honors the FooterPayloadCompressed flag and decodes an LZ4-compressed footer.

The tests drive the real write/read path, not the codec in isolation: reverting the two Lz4 arms in compression.rs makes all four fail at close() with the error from the issue.

Before the fix — Lz4 arms reverted
running 4 tests
test compression::tests::test_lz4_frame_magic_number ... FAILED
test puffin::metadata::tests::test_lz4_compressed_footer_is_decoded ... FAILED
test puffin::writer::tests::test_compress_footer_lz4_round_trips ... FAILED
test puffin::writer::tests::test_compress_empty_footer_lz4_succeeds ... FAILED

---- puffin::writer::tests::test_compress_empty_footer_lz4_succeeds stdout ----
thread 'puffin::writer::tests::test_compress_empty_footer_lz4_succeeds' panicked at crates/iceberg/src/puffin/writer.rs:327:30:
called `Result::unwrap()` on an `Err` value: FeatureUnsupported => LZ4 compression is not supported currently

test result: FAILED. 0 passed; 4 failed
After the fix — cargo test -p iceberg --lib -- compression:: puffin::
running 47 tests
test compression::tests::test_compression_codec_compress ... ok
test compression::tests::test_lz4_frame_magic_number ... ok
test compression::tests::test_snappy_compression_is_unsupported ... ok
test puffin::metadata::tests::test_lz4_compressed_footer_is_decoded ... ok
test puffin::writer::tests::test_compress_empty_footer_lz4_succeeds ... ok
test puffin::writer::tests::test_compress_footer_lz4_round_trips ... ok
test puffin::writer::tests::test_write_lz4_compressed_metric_data ... ok
test puffin::writer::tests::test_uncompressed_empty_puffin_file_is_bit_identical_to_java_generated_file ... ok
test puffin::writer::tests::test_zstd_compressed_metric_data_is_bit_identical_to_java_generated_file ... ok
...
test result: ok. 47 passed; 0 failed; 0 ignored; 0 measured; 1426 filtered out

cargo clippy -p iceberg --all-targets --all-features -- -D warnings, cargo fmt --check and cargo machete are clean.

1fanwang added 2 commits July 28, 2026 17:41
The PuffinWriter exposed a `compress_footer: bool` flag that set the
FooterPayloadCompressed bit in the file footer and recorded LZ4 as the
codec, but `CompressionCodec::Lz4.compress` returned `FeatureUnsupported`,
so calling `PuffinWriter::new(_, _, true)` always failed at close time:

    thread '...' panicked at crates/iceberg/src/puffin/writer.rs:350:
    called `Result::unwrap()` on an `Err` value:
    FeatureUnsupported => LZ4 compression is not supported currently

The reader side had the same hole: a Puffin file written by another
implementation with FooterPayloadCompressed=1 was unreadable.

Wire LZ4 through `CompressionCodec::{compress,decompress}` using
`lz4_flex`'s frame encoder/decoder, matching the Puffin spec requirement
of "LZ4 single compression frame with content size present" by setting
`FrameInfo::content_size(Some(len))` on the encoder. The reader path is
symmetric — `FrameDecoder::read_to_end` consumes the same frame.

Tests:
- `test_compress_empty_footer_lz4_succeeds` — direct reproducer from
  the issue (close() with no blobs and compress_footer=true).
- `test_compress_footer_lz4_round_trips` — full encode/decode loop with
  one blob and file properties.
- `test_write_lz4_compressed_metric_data` — was previously asserting the
  error string; now asserts round-trip of two blobs.
- `test_lz4_compressed_footer_is_decoded` (metadata) — verifies the
  reader honors the FooterPayloadCompressed flag.
- `test_compression_codec_lz4_roundtrip` — round-trip on empty + mixed
  payloads, and asserts the frame begins with the LZ4 magic 0x184D2204.

Closes apache#2419.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
- generalize the multi-codec compress test to cover empty and
  less-compressible payloads for every codec; split the LZ4
  frame-magic-number assertion into its own test
- rename the Snappy case to test_snappy_compression_is_unsupported
- fix the Puffin footer test docs: pre-fix, close() failed with
  FeatureUnsupported rather than writing raw JSON, and these are
  feature tests, not regressions; drop issue-number references

Signed-off-by: 1fanwang <1fannnw@gmail.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.

BUG: failed to write compressed puffin footer

1 participant