Repository navigation
Conversation
db9b5c8 to
6ce73f9
Compare
22e6bde to
44ffbd8
Compare
ALP turns a float into an integer by multiplying it by 10^exponent and then by 10^-factor. This adds that arithmetic and the search that picks the pair for each vector. The search estimates the size of every pair on a sample of 256 values, counting the packed width of the values that convert and the full cost of those that do not, and breaks ties toward the larger exponent as the ALP paper does. After eight vectors it only tries the five pairs those vectors chose most often. The arithmetic follows the reference implementation's rounding and order of multiplication exactly. Rounding decides which values become exceptions, so a writer that differs by one bit writes a different page.
This adds the ALP page format for FLOAT and DOUBLE columns. A page has a seven-byte header, one offset per vector, and vectors of 1024 values. Each vector stores its scaling, a frame of reference, the bit-packed differences from that frame, and the values that did not survive the scaling as exceptions patched in by position. Packing uses the existing BitWriter and BitReader. The differences are computed in unsigned arithmetic, so a DOUBLE vector spanning most of the int64 range packs at 64 bits instead of overflowing. The decoder accepts any vector size a page declares, from 8 to 32768 values, so it reads pages from writers that do not use 1024. It checks every offset, bit width and exception position against the page before reading, and returns an error when the page holds fewer values than the page header claims instead of a short read the column reader would retry forever. ALP takes encoding id 10. The generated Thrift bindings have no checked-in source, so the new value is added to them by hand. Co-authored-by: Prateek Gaur <prateek.gaur@snowflake.com>
parquet-format marks ALP as a preview encoding, and a reader without it cannot open a file that uses it. Writing ALP therefore takes an explicit opt-in, WithALPEncoding for every column or WithALPEncodingFor for one, and NewWriterProperties panics when a column asks for ALP without it, as it does for any unusable encoding. The opt-in permits ALP but does not select it. A column still chooses it with WithEncoding or WithEncodingFor, and needs dictionary encoding off, because a writer that abandons its dictionary falls back to PLAIN. The column reader returns an error for an ALP page on a column that is not FLOAT or DOUBLE, where the decoder traits would otherwise panic. The tests write and read columns of several pages with and without nulls, under both data page versions.
A round trip through one implementation cannot show that it reads what other writers produce. This moves parquet-testing to the commit that adds alp_extended.zstd.parquet, whose ALP columns use vectors of 32, 1024 and 4096 values next to PLAIN copies of the same data, and checks that every ALP value decodes to the bits of its PLAIN copy. A second test writes an Arrow table with ALP columns and reads it back across compression codecs, several pages and row groups, nulls, and values that can only be stored as exceptions.
The exception rate drives what ALP costs, so the benchmarks encode and decode FLOAT and DOUBLE columns at several sizes with no exceptions, one exception in ten values, and nothing but exceptions. A batched DOUBLE decode reads 137 values at a time, so batches end partway through a vector the way column reader calls do.
44ffbd8 to
a017cac
Compare
# Conflicts: # parquet/file/file_reader_test.go
- Look the power-of-ten constants up once per vector (alpScaling) instead of once per value; encode is ~35-40% and decode ~30-44% faster. - Add the frame of reference back in the encoded integer's width, as the spec requires for FLOAT. - Reuse scratch for PutSpaced and the page header. - Add WithALPVectorSize/WithALPVectorSizeFor/WithALPVectorSizePath (power of two, 8-32768, default 1024) and apply it in the float column writers. - Document ALP in the package docs.
- Treat a FLOAT value whose rounded integer leaves int32 as an exception. Rounding a scaled value just under 2^31 could reach 2^31, which readers rebuild in int32 with the sign flipped (or a 33-bit packing width). - Avoid int overflow on 32-bit platforms when counting vectors and bounds-checking vector offsets in the decoder. - List the encodings the data pages actually used in the column chunk metadata when there is no dictionary, so a column that asked for ALP but fell back to PLAIN is not labelled ALP. - Point the docs at per-column ALP selection, make ALPEncodingEnabled report the default like the other getters, and add ALPVectorSize. - Count vector offsets in EstimatedDataEncodedSize; fix stale comments.
- Cover V2 pages with nulls and compression, and the spaced write/read paths, which the existing tests named but did not reach. - Prove pages are ALP via data-page encoding stats, and check the many-pages test really writes several pages. - Merge the float32/float64 copy-paste round trips into one generic table test; drop tests that only restated types or duplicated the shared encoding suite; move reader-error tests to column_reader_test.go on testutils.MockPageReader; gather file-level ALP tests in alp_test.go. - Use checked allocators, and report allocations in the benchmarks.
EstimatedDataEncodedSize counted buffered values at their raw width, so a vector of NaNs or infinities (each stored with a 2-byte position, plus the vector header and offset) was underestimated and pages could overshoot the page size.
A vector can pack every slot at full width and also store exceptions, so sizing the pending vector as all exceptions still undercounted mixed vectors of NaNs and wide integers. Count both.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open issues
2 files not reviewed
parquet/file/column_writer_types.gen.go: Generated fileparquet/internal/gen-go/parquet/parquet.go: Generated file
What changed in this PR
Adds Adaptive Lossless floating-Point (ALP) encoding support for Parquet FLOAT/DOUBLE, including writer opt-in controls, reader integration, metadata correctness, and extensive tests/benchmarks.
Changes:
- Implement ALP encoder/decoder and scaling/parameter selection logic for float32/float64.
- Integrate ALP into writer/reader plumbing (properties, typed encoder/decoder selection, column writer vector sizing, metadata encodings list).
- Add comprehensive unit, integration, conformance, and benchmark coverage for ALP.
| File | Description |
|---|---|
| parquet/writer_properties.go | Adds ALP opt-in + vector size writer properties, validation, and getters. |
| parquet/types.go | Exposes parquet.Encodings.ALP. |
| parquet/reader_writer_properties_test.go | Tests ALP opt-in and vector size validation behavior. |
| parquet/pqarrow/alp_test.go | End-to-end Arrow write/read roundtrip tests using ALP. |
| parquet/metadata/column_chunk.go | Makes footer encodings reflect actual data page encodings (important for ALP/dict fallback). |
| parquet/internal/encoding/typed_encoder.go | Wires ALP into float typed encoder/decoder traits. |
| parquet/internal/encoding/encoding_test.go | Adds ALP to generic encoding roundtrip test suite. |
| parquet/internal/encoding/alp_scaling.go | Implements normative ALP scaling/round-trip logic. |
| parquet/internal/encoding/alp_scaling_test.go | Validates scaling arithmetic, exceptions, and power tables. |
| parquet/internal/encoding/alp_params.go | Implements exponent/factor selection logic (sampling + presets). |
| parquet/internal/encoding/alp_params_test.go | Tests parameter search optimality and edge cases. |
| parquet/internal/encoding/alp_encoding.go | Implements ALP page/vector layout, packing, encoder/decoder. |
| parquet/internal/encoding/alp_encoding_test.go | Extensive correctness, corruption-resistance, and edge-case tests. |
| parquet/internal/encoding/alp_encoding_benchmark_test.go | Adds ALP encode/decode benchmarks, including batched decoding. |
| parquet/file/file_reader_test.go | Adds conformance test against parquet-testing alp_extended fixture. |
| parquet/file/column_writer_types.gen.go.tmpl | Propagates per-column ALP vector size into ALP encoders. |
| parquet/file/column_writer_test.go | Enables ALP opt-in in writer tests and adds ALP required-column coverage. |
| parquet/file/column_reader_test.go | Adds reader-side rejection/mismatch tests for ALP on non-float and bad headers. |
| parquet/file/column_reader.go | Rejects ALP early for non-float physical types to avoid trait panics. |
| parquet/file/alp_test.go | File-level ALP roundtrip, null handling, page versions, vector sizing, and dict-drop behavior tests. |
| parquet/doc.go | Documents ALP encoding support and opt-in requirements. |
Review effort: Lite
Also quote the column path in the ALP opt-in panic message.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open issues
This usesfor i := range <int>(integer range loops), which requires Go 1.22+. If the repo’s… · New The decoder currently trusts the vector offset table without validating that offsets actually point… · New The panic message for per-column vector sizes concatenates the rawpathwithout quoting/escaping… · New
3 resolved since last review
2 files not reviewed
parquet/file/column_writer_types.gen.go: Generated fileparquet/internal/gen-go/parquet/parquet.go: Generated file
Review effort: Lite
Also quote the column path in the ALP vector size panic message.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
2 open issues
3 resolved since last review
2 files not reviewed
parquet/file/column_writer_types.gen.go: Generated fileparquet/internal/gen-go/parquet/parquet.go: Generated file
Review effort: Lite
| for b.Loop() { | ||
| encoder.Put(values) | ||
| buf, _ := encoder.FlushValues() | ||
| buf.Release() | ||
| } |
There was a problem hiding this comment.
No change needed: testing.B.Loop was added in Go 1.24, and go.mod declares go 1.26.0. The benchmarks compile and run.
| // List what the data pages used rather than what was asked for: a | ||
| // writer whose dictionary was dropped before its first page writes | ||
| // PLAIN pages whatever encoding the properties name. | ||
| for _, e := range slices.Sorted(maps.Keys(encStats.DataEncodingStats)) { |
There was a problem hiding this comment.
No change needed: slices.Sorted (with maps.Keys) has been in the standard library since Go 1.23, and go.mod declares go 1.26.0.
…ded values Offsets must start right after the offset array and each vector must end where the next begins, so a corrupt offset can't decode another vector's values. Discard now reports a page that encodes fewer values than requested, as Decode does.
…e changes Sample a different position in each stride-wide block, so data that repeats with the stride, such as a NaN every 128 values, can't fill the whole sample. When the shortlist does no better than PLAIN, search again in case the data changed scale, backing off while nothing compresses. Make the benchmark fixtures match their exception rates.
|
I did an independent review of this and pushed some fixes directly to the branch, so please pull before making further changes:
Other things I checked that look good:
|



Co-Author: @arnav.balyan
Supersedes #704 (this PR started from it and addresses the review there).
Rationale for this change
ALP (Adaptive Lossless floating-Point) is the
ALP = 10encoding for FLOAT and DOUBLE columns in parquet-format (in Preview since 2026-08-01). It scales values by powers of ten into integers, then stores them frame-of-reference + bit-packed, keeping values that don't round-trip as exceptions. Arrow C++ already implements it.What changes are included in this PR?
Encoding (
parquet/internal/encoding)alp_encoding.go: genericalpEncoder[T]/alpDecoder[T]for float32 and float64, following the spec's page layout (7-byte header, offset array, self-describing vectors).FlushValues.log_vector_size(8–32768), and bounds-checks every size and offset read from the page.alp_scaling.go: the normative decodeencoded * 10^f * 10^-ewith the spec's literal power-of-ten tables.alpScaling[T]looks the constants up once per vector instead of once per value.alp_params.go: picks(exponent, factor)by estimated encoded size, using the paper's sampling scheme: 256 samples per vector, a full search over the first 8 vectors, then the 5 best combinations.utils.BitWriter/BitReaderfor bit packing.Writer/reader integration
parquet.Encodings.ALP, andEncoding_ALPin the generated thrift code.WithALPEncoding/WithALPEncodingFor/WithALPEncodingPathallow ALP, andWithEncoding*(Encodings.ALP)selects it.NewWriterProperties.WithALPVectorSize/WithALPVectorSizeFor/WithALPVectorSizePathset the vector size: a power of two from 8 to 32768, default 1024, matching thealp_vector_sizeproperty of the Arrow C++ implementation.Are these changes tested?
Yes.
TestReadALPConformanceFilereadsalp_extended.zstd.parquetfrom apache/parquet-testing, written by Arrow C++ with 32-, 1024- and 4096-value vectors. It bit-compares every ALP column against its PLAIN copy. Theparquet-testingsubmodule is bumped to include that file.BenchmarkALP{En,De}codingFloat{32,64}, plus batched decoding.Are there any user-facing changes?
Yes, all opt-in:
parquet.Encodings.ALPWithALPEncoding*andWithALPVectorSize*writer properties, with the matching gettersDefaultALPEnabledandDefaultALPVectorSizeReading ALP needs no option.