Repository navigation
Conversation
Lz4CompressionCodec compressed with commons-compress's pure-Java LZ4, which takes minutes for some 8 MiB buffers. It now compresses each buffer with lz4-java into one frame of independent blocks of up to 4 MiB, without checksums, which every LZ4 frame decoder reads, and decompresses frames whose blocks are independent with lz4-java too, directly on the buffers' memory. A frame whose blocks are linked, as Arrow C++ writes for a buffer larger than 64 KiB, or that uses a dictionary, is still decompressed with commons-compress, since lz4-java cannot decode a block that refers to earlier ones. The new decoder checks the frame's block lengths, and that they can hold the recorded decompressed length, before allocating for it, and verifies the descriptor, block and content checksums the frame has. CompressionCodecBenchmarks measures the LZ4 and ZSTD codecs.
jbonofre
left a comment
There was a problem hiding this comment.
The CI is failing on Spotless check on Lz4ComporessionCode.java.
| <dependency> | ||
| <groupId>at.yawk.lz4</groupId> | ||
| <artifactId>lz4-java</artifactId> | ||
| <version>1.12.1</version> | ||
| </dependency> |
There was a problem hiding this comment.
arrow-tools shades every runtime dependency, so the release binary distribution will include lz4-java and the native libraries it ships, which are built from LZ4 and xxHash (BSD-2 Clause).
We have to add entries to tools/src/shade/LICENSE.txt for lz4-java 1.12.1 (Apache License 2.0) and for LZ4 and xxHash (BSD-2 Clause, with the license content inline).
It's a blocker for release.
There was a problem hiding this comment.
Added in 2f3abec: lz4-java 1.12.1 (Apache 2.0), plus LZ4 1.10.0 and xxHash 0.6.5 with their BSD 2-Clause texts inline. lz4-java 1.12.1 pins LZ4 at v1.10.0 and builds its native libraries from lz4.c, lz4hc.c and xxhash.c in LZ4's lib directory, so the xxHash it ships is the copy there. lz4-java has no NOTICE file, so NOTICE.txt is unchanged. The shaded arrow-tools jar now contains the nine lz4-java native libraries and the three entries. The spotless violations are fixed in 9a2cd0b.
Apply the google-java-format line wrapping that the spotless check reported for Lz4CompressionCodec. No functional change.
arrow-tools shades lz4-java, including the native libraries it ships. lz4-java 1.12.1 pins LZ4 at v1.10.0 and builds those libraries from lz4.c, lz4hc.c and xxhash.c in LZ4's lib directory, so they contain LZ4 1.10.0 and the xxHash 0.6.5 copy there, both under the BSD 2-Clause license. Add entries for all three, with the BSD texts inline. lz4-java has no NOTICE file, so NOTICE.txt is unchanged.
What's Changed
Lz4CompressionCodecuses commons-compress's pure-Java LZ4, which is slow enough that compressing an 8 MiB buffer of sequentialint64values takes several minutes (#192). This changes the codec to use lz4-java, the JNI bindings to liblz4 that Spark, Kafka and others use, while keeping every frame it could read before readable.Compression. Each buffer is written as one LZ4 frame of independent blocks of up to 4 MiB, without checksums, compressed with lz4-java directly from the buffer's memory. Every LZ4 frame decoder reads frames with independent blocks: liblz4 (Arrow C++, pyarrow),
lz4_flex(arrow-rs), lz4-java, and commons-compress.Decompression. A frame with independent blocks is decompressed with lz4-java directly into the output buffer. That covers frames from this codec, from arrow-rs, and from the previous version of this codec. The new decoder accepts any block size, block and content checksums (which it verifies), and a content size. It checks the frame's block lengths, and that they can hold the recorded decompressed length, before allocating for it.
A frame whose blocks are linked, or that uses a dictionary, still goes through commons-compress. lz4-java cannot decode a block that refers to earlier blocks, and Arrow C++ writes linked blocks: liblz4's default
LZ4F_preferences_tisLZ4F_blockLinked, so pyarrow 26 writes every buffer larger than 64 KiB as linked 64 KiB blocks. The LZ4 files in arrow-testing are small enough that liblz4 writes them as single, independent blocks, so the integration tests would not catch this.TestLz4CompressionCodeccovers linked frames written by commons-compress instead.Dependency. This adds
at.yawk.lz4:lz4-java1.12.1, the maintained fork oforg.lz4:lz4-java. The original artifact's last code release, 1.8.0, has several published advisories (for example CVE-2025-12183 and CVE-2025-66566), and its 1.8.1 is a relocation POM that points to the fork. Spark moved to the fork in 4.2. Both artifacts use thenet.jpountzpackages and theorg.lz4.javamodule name, and the codec uses only APIs present in both, so an application that pinsorg.lz4:lz4-javainstead still works. The codec asks for lz4-java's JNI implementation and falls back to its Java one only when the native library cannot load, becauseLZ4Factory.fastestInstance()settles for Java whenever lz4-java is not loaded by the system class loader.Performance
CompressionCodecBenchmarks(new, inperformance) on an Apple M3 Ultra, JDK 17, 8 MiB buffers:int64int64The previous codec is too slow for the same JMH run. From a simple timing harness on the same machine:
int64i % 1000as textCompression ratio
On the inputs above, the ratio is unchanged except for random decimal text, where commons-compress's more thorough match search reached 1.61 and lz4-java's fast compressor reaches 1.10. The new output matches what Arrow C++ writes by default: pyarrow's LZ4 codec also reaches 1.10 on the same kind of data, and 1.63 at compression level 9.
CommonsCompressionFactory.createCodec(LZ4_FRAME, level)ignores the level today. Mapping it to lz4-java's high-compression mode would give users who want the better ratio a way to ask for it; I left that out to keep this change focused, and can add it here or in a follow-up.Testing
TestCompressionCodecandTestArrowReaderWriterWithCompressionpass unchanged, including the decompressed-length check from GH-1206: validate decompressed length in Lz4CompressionCodec #1207.TestLz4CompressionCodec(new):LZ4FrameInputStreamboth read the frames this codec writes;AI usage
The code, tests and benchmark in this PR were written with an AI coding assistant. The frame behavior described above (pyarrow's linked blocks, lz4-java's rejection of them) and the benchmark numbers come from running them.
Closes #192.