Skip to content

feat: support gzip compression in native Parquet writes#4930

Open
andygrove wants to merge 7 commits into
apache:mainfrom
andygrove:feat-parquet-write-gzip
Open

feat: support gzip compression in native Parquet writes#4930
andygrove wants to merge 7 commits into
apache:mainfrom
andygrove:feat-parquet-write-gzip

Conversation

@andygrove

Copy link
Copy Markdown
Member

Which issue does this PR close?

Closes #4929. Part of #1625.

Rationale for this change

Comet's native Parquet writer supported none, snappy, lz4, and zstd, so a write configured with spark.sql.parquet.compression.codec=gzip fell back to Spark's writer. gzip is a valid Spark Parquet codec and is widely used, so this was an avoidable loss of native write coverage.

Reviewing that code path surfaced two related gaps, both fixed here:

  • parseCompressionCodec did not follow Spark's codec precedence. Spark's ParquetOptions resolves compression > parquet.compression (ParquetOutputFormat.COMPRESSION) > spark.sql.parquet.compression.codec, but Comet checked only the first and third. df.write.option("parquet.compression", "gzip") with the default SQLConf was therefore written natively as snappy, silently substituting the user's requested codec.
  • Spark accepts uncompressed as a spelling of none, but the supported-codec set listed only none, so uncompressed fell back to Spark's writer unnecessarily.

native/core also requested the parquet crate with default-features = false while naming only experimental and arrow. The codec features (snap, lz4, zstd, and gzip's flate2) were enabled only incidentally, through Cargo feature unification with other crates that pull parquet with default features. A dependency bump that changed that would have left the writer compiling fine but failing at execution time with "Disabled feature at compile time", so the features are now requested explicitly.

What changes are included in this PR?

  • Add Gzip = 4 to the protobuf CompressionCodec enum.
  • Give the native Parquet writer its own ParquetCompression enum instead of borrowing the shuffle crate's CompressionCodec. gzip is a Parquet codec, not a shuffle codec, and the shuffle path has no business carrying it. The shuffle branch of the planner is unchanged and still rejects any codec it does not map. gzip maps to Compression::GZIP(GzipLevel::default()), i.e. level 6, matching parquet-mr's zlib default.
  • Request the parquet codec features explicitly in native/core/Cargo.toml.
  • Emit the gzip codec from CometDataWritingCommand, honor Spark's parquet.compression precedence, and accept uncompressed as an alias of none.

Compression levels remain non-configurable (gzip and zstd use fixed defaults), so parquet-mr's parquet.compression.codec.{gzip,zstd}.level configs are still ignored. That is out of scope here and noted in the issue.

How are these changes tested?

CometParquetWriterSuite had no compression coverage at all, so this adds it:

  • A codec matrix test over none, uncompressed, snappy, lz4, zstd, and gzip. For each, it asserts CometNativeWriteExec is in the executed plan (so a silent fallback to Spark's writer cannot make the test pass vacuously), reads the data back and compares against the source, and opens the Parquet footer to assert every column chunk reports the expected codec (so a write that ignored the requested codec cannot pass either).
  • A test that .option("parquet.compression", "gzip") beats a snappy SQLConf default and produces a natively written GZIP file.
  • A fallback test using lz4_raw, a codec Spark writes but Comet does not support: the plan must contain no CometNativeWriteExec, and the data must still round-trip. This catches a codec being added to the supported set without a matching serde case.

A Rust unit test covers the ParquetCompression to parquet::basic::Compression mapping for all five variants.

This work was scaffolded with the brainstorming, writing-plans, and subagent-driven-development skills.

Add Gzip to the CompressionCodec proto enum and introduce a
Parquet-specific ParquetCompression enum in parquet_writer.rs so the
native writer can honor gzip without affecting the shuffle codec. The
planner maps the new proto variant to ParquetCompression::Gzip, which
writes with parquet's default GzipLevel (6), matching parquet-mr's zlib
default. The shuffle writer path is unchanged and still rejects Gzip
via its catch-all error arm.
…writer

The native writer's gzip, snappy, lz4, and zstd support only worked
because Cargo feature unification happened to pull in parquet-rs's
flate2/snap/lz4/zstd features via other workspace dependencies. Declare
these features directly on the parquet dependency so the native writer
does not silently lose codec support if that unification changes.
parseCompressionCodec only checked the compression option and the
SQLConf default, skipping the parquet.compression option that Spark's
own ParquetOptions treats as the middle rung of precedence. Fix the
lookup to match Spark's compression, parquet.compression, SQLConf
order, and accept "uncompressed" as an alias for "none" since Spark
does the same.

Add coverage for the parquet.compression option taking precedence over
the SQLConf default, for uncompressed as a none alias, and for an
unsupported codec (brotli) causing the write to fall back to Spark's
own writer instead of CometNativeWriteExec.
compression_to_parquet was a one-line wrapper around
ParquetCompression::to_parquet with a single caller; call it directly
instead.
The unsupported-codec fallback test relied on Spark's write failing
with ClassNotFoundException for BrotliCodec, so it only passed because
the environment lacks a Brotli codec class rather than because Comet
routed the write through the fallback path. Use lz4_raw instead, which
Spark can write directly via parquet-mr and Comet does not support, so
the test can assert a real successful round trip and the correct
footer codec instead of intercepting an unrelated failure.

Revert the allowFailure plumbing added to captureWritePlan for that
test since it is no longer needed.
@andygrove andygrove added this to the 1.0.0 milestone Jul 14, 2026

@parthchandra parthchandra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. approved pending ci.

}
}

test("parquet write with each supported compression codec") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a requirement but do you think it might be useful to add a test to cover codec precedence? I.e. specify zstd in the conf, but then override it to gzip

@andygrove

Copy link
Copy Markdown
Member Author

Spark 3.4 failure:

- parquet write with unsupported compression codec falls back to Spark *** FAILED *** (33 milliseconds)
  java.lang.IllegalArgumentException: The value of spark.sql.parquet.compression.codec should be one of brotli, uncompressed, lz4, gzip, lzo, snappy, none, zstd, but was lz4_raw
  at org.apache.spark.internal.config.TypedConfigBuilder.$anonfun$checkValues$1(ConfigBuilder.scala:119)

Spark 3.4's PARQUET_COMPRESSION config validates against a fixed set of
codecs (brotli, uncompressed, lz4, gzip, lzo, snappy, none, zstd) that
does not include lz4_raw, so withSQLConf threw before the fallback path
ran. Guard the test with assume(isSpark35Plus, ...) so it runs on
versions where lz4_raw is a valid codec value.
Gzip,
}

impl ParquetCompression {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this enum, cant we reuse Compression ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the PR description:

"Give the native Parquet writer its own ParquetCompression enum instead of borrowing the shuffle crate's CompressionCodec. gzip is a Parquet codec, not a shuffle codec, and the shuffle path has no business carrying it."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case we prob can just add GZIP variant into CompressionCodec and reuse, however if we follow Apache Spark there are 2 enums indeed, for parquet and internal compression like shuffle.

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.

Support gzip compression in native Parquet writes

3 participants