Add page-level ArrowParquetWriter for writing VectorSchemaRoot to Parquet - #3734
Open
qzyu999 wants to merge 1 commit into
Open
Add page-level ArrowParquetWriter for writing VectorSchemaRoot to Parquet#3734qzyu999 wants to merge 1 commit into
qzyu999 wants to merge 1 commit into
Conversation
Implements a true page-level Arrow-to-Parquet writer that bypasses the RecordConsumer/ColumnWriter pipeline entirely, writing pages directly to PageWriter. Architecture: - ArrowParquetWriter: manages ParquetFileWriter, row groups, and per-column strategy selection - ArrowColumnWriter: strategy interface for per-column writing - ArrowColumnWriterFactory: selects optimal strategy based on column type, nullability, and encoding - ZeroCopyPlainWriter: wraps Arrow data buffer directly as page BytesInput (zero copy for non-null fixed-width PLAIN columns) - NullablePlainWriter: scans validity bitmap, bulk-copies non-null runs, encodes definition levels as RLE runs - LevelEncoder: produces RLE-encoded repetition/definition levels - StatsComputer: computes page statistics from Arrow buffers in a single sequential scan For non-null INT32 columns, this writer performs ZERO per-value method calls. The Arrow buffer bytes ARE the Parquet page bytes. Phase 1 scope: flat schemas, fixed-width types (INT32, INT64, FLOAT, DOUBLE, FIXED_LEN_BYTE_ARRAY), PLAIN encoding. Variable-width types and dictionary encoding are future phases. Closes apache#3733
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
ArrowParquetWriterto theparquet-arrowmodule — a page-level writer that accepts ArrowVectorSchemaRootbatches and produces valid Parquet files without per-row object construction.Closes #3733. Related: #2264, #3353.
Motivation
Multiple downstream projects (Iceberg #17748, Fluss #4047, Paimon) work with Arrow-columnar data internally but must materialize row objects to write Parquet via
ParquetWriter<T>.write(T). This PR provides a direct Arrow-to-Parquet path, eliminating the row-object API mismatch. Arrow C++/Python have had this sincewrite_table().Design
Bypasses
RecordConsumer/ColumnWriterentirely. Writes assembled pages directly toPageWriterwith per-column strategy selection:BytesInput. O(1) level encoding. Data bytes are not transformed — Arrow's little-endian layout IS Parquet PLAIN encoding.Does not extend
ParquetWriter<T>becausewrite(T)increments an internal record count by 1 per call, incompatible with batch semantics.Key properties
ConcatenatingByteBufferCollector.collect()copies page bytes duringwritePage()— Arrow buffers can be freed afterwriteBatch()returnsStatsResultvalue object, no shared mutable statePageWriter.getMemSize()sum, not heuristicTypes supported
INT32, INT64, FLOAT, DOUBLE, BOOLEAN, BINARY (string), FIXED_LEN_BYTE_ARRAY — nullable and required.
Not included (follow-up PRs)
UnsupportedOperationException.UnsupportedOperationException.Relationship to #3530 (Performance Improvements series)
This PR is complementary to the encoding-level optimizations in #3530. Specifically:
PlainValuesWriter.writeInteger()use directByteBuffer.putInt(). Our zero-copy path bypassesPlainValuesWriterentirely for required columns, but nullable/varwidth paths would benefit from GH-3530: Optimize PLAIN encoding and decoding with direct ByteBuffer I/O #3565's faster fallback when dict encoding is eventually added.LevelEncoder.encodeFromValidityBitmap()usesRunLengthBitPackingHybridEncoderfor nullable columns. GH-3530: Optimize RLE hybrid encoder/decoder scalar hot-path performance #3568's optimizations would make this faster.writeIntegers(int[], offset, count)batch methods toValuesWriter. When this lands, a futureDictionaryWriterstrategy could use batch index encoding rather than per-value.Statistics: irreducible O(N) scan
The stats scan (min/max/NaN count) is the minimum work required to produce a valid Parquet file with predicate pushdown support. For the zero-copy path, it is the ONLY per-value work performed. Future optimizations possible:
Tests
10 round-trip tests (write via
ArrowParquetWriter, read via standardParquetReader):Dependencies added
parquet-hadoop(compile) —ParquetFileWriter,ColumnChunkPageWriteStorearrow-memory-netty(test) — Arrow allocator runtimehadoop-common+hadoop-mapreduce-client-core(test) — forParquetReaderin round-trip testsparquet-hadooptest-jar (test) —GroupReadSupportDiscussion point
Adding
parquet-hadoopas a compile dependency toparquet-arrowincreases the module's dependency footprint.ParquetFileWriterandColumnChunkPageWriteStoreexist only inparquet-hadoop— no alternative implementations. Every Java Parquet writer (Iceberg, Spark, Flink) depends onparquet-hadoop. Alternative: create a newparquet-arrow-hadoopmodule. Open to guidance.How to run tests