This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch production
in repository https://gitbox.apache.org/repos/asf/parquet-site.git
The following commit(s) were added to refs/heads/production by this push:
new 1b223ca6 Add ALP Encoding blog (#195)
1b223ca6 is described below
commit 1b223ca6b6d5ec709a5f457919525604ee18a99d
Author: Kosta Tarasov <[email protected]>
AuthorDate: Tue Sep 22 06:41:05 2026 -0400
Add ALP Encoding blog (#195)
* 0.1 draft
* second pass
* review pass
* update bench results + technical overview rewrite
* mostly typos
* Add more encoding/decoding diagrams
* Apply suggestions from code review
Co-authored-by: prtkgaur <[email protected]>
Co-authored-by: Andrew Lamb <[email protected]>
* more prose changes + full bench results movement
* color the examples differently
* title name fix
* Take a pass over intro, add performance result charts
* pass over intro
* More tweaking intro
* update charts
* wording
* formatting
* update charts resolution + fix decoding one being encoding
* two tiny fixes
* another pass
* Update intuition introduction
* Incorporate random access and FOR examples
* Initial algorithm and decode example
* reformat
* fill out details
* fix exmaple
* fix other example
* obsession
* wordsmith
* wordsmith
* nits
* Remove commented out original example walkthrough
* shorten
* mgrate pictire
* fix image width
* hone end
* tweak
* Wordsmith
* Wordsmith
* more
* fix diagram formatting
* improve wording
* obsess
* correctness pass plus suggestion
* consistency
* nits
* flow
* change the blog desc
* Add BSS + ZSTD encoding to charts
* Explain diagrams better
* add link to try it out
* Clarify explanation
* Update examples diagrams
* Add ALP implementation in go
* Use log scale for random access speed
* Add acknowledgement section, update the implementation status
* Add acknowlegement names
* remove unecesary section headers
* SImplify
* cleanups
* Word wrap and add C++ measrements
* Apply batched suggestions from @sdf-jkl's code review
Co-authored-by: Kosta Tarasov <[email protected]>
* Update publish date
* copy edits
---------
Co-authored-by: Andrew Lamb <[email protected]>
Co-authored-by: prtkgaur <[email protected]>
---
content/en/blog/features/alp_encoding.md | 326 ++++++++++++++++++++++++++++
static/blog/alp/alp_decoding_example.png | Bin 0 -> 184658 bytes
static/blog/alp/alp_encoding_example.png | Bin 0 -> 245167 bytes
static/blog/alp/alp_vector_layout.png | Bin 0 -> 118280 bytes
static/blog/alp/avg_compression_ratio.png | Bin 0 -> 64064 bytes
static/blog/alp/avg_compression_speed.png | Bin 0 -> 57853 bytes
static/blog/alp/avg_decompression_speed.png | Bin 0 -> 61129 bytes
static/blog/alp/avg_random_access.png | Bin 0 -> 77784 bytes
8 files changed, 326 insertions(+)
diff --git a/content/en/blog/features/alp_encoding.md
b/content/en/blog/features/alp_encoding.md
new file mode 100644
index 00000000..d275e851
--- /dev/null
+++ b/content/en/blog/features/alp_encoding.md
@@ -0,0 +1,326 @@
+---
+title: "ALP: Adaptive Lossless Floating-Point Encoding in Apache Parquet"
+date: 2026-09-22
+description: "A technical overview of ALP's design, performance, and adoption
across the Apache Parquet ecosystem."
+author: "[Kosta Tarasov](https://github.com/sdf-jkl), [Andrew
Lamb](https://github.com/alamb), [Prateek Gaur](https://github.com/prtkgaur)"
+categories: ["features"]
+---
+
+Apache Parquet has added the [Adaptive Lossless floating-Point (ALP) Encoding]
-- a new lightweight floating-point encoding with compression ratios similar to
[`zstd`], much faster decompression, random-access support, and GPU- and
SIMD-friendly decoding.
+
+----
+[`zstd`]: https://github.com/facebook/zstd
+[Adaptive Lossless floating-Point (ALP) Encoding]:
https://parquet.apache.org/docs/file-format/data-pages/alpencoding/
+
+ALP works best for decimal values stored as floating-point types (32-bit
`FLOAT` and 64-bit `DOUBLE`), such as
+
+- Monetary values (exchange rates, public funds, stocks, prices, etc.) --
e.g., `1.2345` or `22.03`
+- Geographic coordinates (longitude/latitude) -- e.g., `42.3584`, `-71.0598`
+- Scientific measurements (temperature, pressure, speed, degrees, etc.) --
e.g., `-273.15`, `9.81`, `3.14159`
+
+ALP is not suitable for data that uses a wide range of exponents or a large
+number of significant digits, such as vector embeddings, which typically span
+the full floating-point range. Such data can continue to use existing Parquet
+features such as `PLAIN` or [`BYTE_STREAM_SPLIT`] encoding followed by
+general-purpose compression like `ZSTD`.
+
+Decimal values can be stored with Parquet's `DECIMAL` logical type, but that
+type requires the precision and scale to be known and declared up front and
+cannot store values outside that range. For this reason, systems commonly
+store decimal values as `FLOAT` or `DOUBLE` when the exact shape of their data
+is not known beforehand. For example,
+JavaScript's only* [number type is `DOUBLE`], common data science tools such as
+pandas [infer `float64` for decimal-looking values], and NumPy has [no decimal
dtype at all].
+
+[number type is `DOUBLE`]:
https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type
+[infer `float64` for decimal-looking values]:
https://pandas.pydata.org/docs/reference/api/pandas.to_numeric.html
+[no decimal dtype at all]:
https://numpy.org/doc/stable/reference/arrays.dtypes.html
+[`BigInt`]:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
+
+<small>\* JavaScript also has [`BigInt`], but it can only represent
integers.</small>
+
+## Why ALP?
+
+Encoding floating-point data is a complicated engineering problem due to the
+nature of floating-point values. They do not exactly represent most real
+numbers. This leads to rounding errors that prevent the use of existing
lightweight
+encodings like Delta and Frame of Reference.
+
+Prior to ALP, [`BYTE_STREAM_SPLIT`] was the only non-dictionary alternative to
+`PLAIN` for `FLOAT`/`DOUBLE` values in Parquet. It does not reduce the size of
the
+data but *can* improve the compression ratio and speed when a heavyweight
+compressor is used afterwards.
+
+Heavyweight compression effectively decreases the data size, but at the cost
of:
+ - Decode speed -- decompression speed is often the bottleneck in data
access.
+ - Random access -- reading one value requires decoding an entire data page
containing potentially thousands of other values.
+ - Data dependence -- variable-length compression means that decoding a
value requires decoding previous values, making it hard to parallelize with
modern hardware such as [SIMD instructions] and [GPU]s.
+
+[SIMD instructions]: https://en.wikipedia.org/wiki/SIMD
+[GPU]: https://en.wikipedia.org/wiki/Graphics_processing_unit
+[`BYTE_STREAM_SPLIT`]:
https://parquet.apache.org/docs/file-format/data-pages/encodings/#BYTESTREAMSPLIT
+
+ALP is designed to solve all three of these problems for common data patterns,
while achieving a similar compression ratio to heavyweight compression.
+
+Parquet applies an encoding first, then an optional compression codec as a
+separate step. The charts below compare the `PLAIN` and `BYTE_STREAM_SPLIT`
+encodings followed by `ZSTD` compression with the `ALP` encoding and no
+additional compression. Users can expect ALP to decode `10x` faster and
+retrieve individual values thousands of times faster, with a slightly lower
+compression ratio and slightly faster compression.[^benchmark]
+
+[^benchmark]: The code and instructions to reproduce these results and try ALP
+ on your own Parquet datasets are in the
+ [alp_benchmark](https://github.com/alamb/alp_benchmark) repository, which
+ uses the Rust Parquet implementation.
+
+<div class="row g-3 td-max-width-on-larger-screens">
+ <div class="col-12 col-md-6">
+ <img src="/blog/alp/avg_compression_ratio.png" alt="Average compression
ratio benchmark" class="img-fluid">
+ </div>
+ <div class="col-12 col-md-6">
+ <img src="/blog/alp/avg_compression_speed.png" alt="Average compression
speed benchmark" class="img-fluid">
+ </div>
+ <div class="col-12 col-md-6">
+ <img src="/blog/alp/avg_decompression_speed.png" alt="Average
decompression speed benchmark" class="img-fluid">
+ </div>
+ <div class="col-12 col-md-6">
+ <img src="/blog/alp/avg_random_access.png" alt="Average random-access
benchmark" class="img-fluid">
+ </div>
+ <div>
+ <b>Figure 1</b>: Average compression ratio, compression speed,
decompression speed, and random-access speed of <code>PLAIN+ZSTD</code> and
<code>BYTE_STREAM_SPLIT+ZSTD</code> (each encoding followed by per-page
<code>ZSTD</code> compression), and <code>ALP</code> (no compression codec),
across <code>30</code> datasets on <code>3</code> machines. Higher is better.
+ Random-access speed is measured by decoding <code>100</code>
deterministic, uniformly distributed rows from <code>city_temperature_f</code>.
+ </div>
+ <p/>
+</div>
+
+Note that these numbers are for the pre-release Rust implementation of ALP, and
+we expect performance to improve as implementations are optimized and tuned.
+Even so, ALP is already faster than `zstd` in many cases, despite years of
+optimization work on `zstd` implementations. We also measured similar
+[improvements for the C++ implementation].
+
+[improvements for the C++ implementation]:
https://docs.google.com/spreadsheets/d/1NmCg0WZKeZUc6vNXXD8M3GIyNqF_H3goj6mVbT8at7A/
+
+## Technical Overview
+
+ALP takes advantage of a common pattern: many values stored as `FLOAT` or
+`DOUBLE` originated as decimal numbers with relatively few digits, such as
+prices or measurements. This section explains the intuition behind ALP and
+then covers the encoding and decoding pipelines in more detail.
+
+ALP encodes floating-point values in batches called "vectors", ranging in size
from `8` to `32K` values (e.g., `1024`). Each value in a vector is encoded as an
+integer, and the vector stores two integer parameters shared by all its
+values: an "exponent" (`e`) and a "factor" (`f`). Each vector can use a
+different exponent and factor. How they are chosen is explained below. The
+original value is recovered by computing
+
+<pre>
+value = encoded × 10<sup>f</sup> × 10<sup>-e</sup>
+</pre>
+
+This calculation uses floating-point arithmetic, which rounds to the nearest
+representable value and thus may not reproduce the original value exactly. When
+that happens, ALP stores the original full-precision value separately as an
+"exception", keeping the encoding lossless. Special values such as `NaN`,
+`±Infinity`, and `-0.0` are also stored as exceptions.
+
+Within each vector, the encoded values are stored by subtracting the lowest
+value (the frame of reference) and then bit-packing to a fixed width.
Exceptions
+are stored directly after the encoded array. The layout of each ALP vector is
+shown below.
+
+
+<!-- Diagrams source:
https://docs.google.com/presentation/d/1NeYAGKV2wZZMSme5rVgUGGMkfOTEnxw8oCDxid5UouM
-->
+<div class="row g-3 td-max-width-on-larger-screens">
+ <div class="col-12">
+ <img src="/blog/alp/alp_vector_layout.png" alt="ALP serialized vector
layout" class="img-fluid">
+ </div>
+ <div>
+ <b>Figure 2</b>: Layout of a serialized ALP vector: a fixed-size Vector
Header followed by a variable-size Data Section.
+ </div>
+ <p/>
+</div>
+
+
+Since each value is stored as a bit-packed integer of a fixed width, locating
+an arbitrary row requires only computing the offset of its encoded bits.
+Applying the frame of reference, exponent, and factor to that integer recovers
+the original floating-point value. Finally, the exception indices are checked
+for the target row, and if an exception is present, its value is returned
+instead.
+
+{{% alert title="Example" color="info" %}}
+
+<!--
+Rust playground with demo
+https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5c731c344f4161e06057f5059e4598b7
+-->
+
+Consider encoding the value `8.0605`, which cannot be exactly
+represented in [IEEE 754](https://ieeexplore.ieee.org/document/8766229). It is
stored as
+the 32-bit floating-point number `8.06050014495849609375`. It can also be
+encoded as `80605` with exponent `e = 8` and factor `f = 4`. Applying
+the recovery formula with 32-bit floating-point arithmetic, which rounds after
+each multiplication, yields
+
+<pre>
+80605 × 10<sup>4</sup> × 10<sup>-8</sup> → 8.06050014495849609375
(<code>FLOAT</code>)
+</pre>
+
+This is the nearest representable `FLOAT` to `8.0605` and matches the original
+stored floating-point value exactly. However, if the original value had been
+`8.0605123` (stored as the 32-bit value `8.060512542724609375`), the encoded
+value would still be `80605` and the decoded value still
+`8.06050014495849609375`, which differs from the original. That value would
+therefore be stored as an exception.
+
+{{% /alert %}}
+
+Picking the exponent and factor well is key to ALP's performance. Each Parquet
+writer is free to choose them for each vector using any algorithm. The Parquet
specification provides an example sampling-based
+algorithm that aims to minimize the encoded size. Typically, the exponent is
chosen to
+capture most decimal digits in the vector while minimizing exceptions, and the
+factor is chosen to remove as many trailing zeros as possible.
+
+{{% alert title="Example" color="info" %}}
+
+Assuming some value in the vector requires `e = 8`, it is valid to encode
+`0.0123`, `0.0245`, and `0.0201` with multiple factor choices:
+
+- `e = 8, f = 0`: `1230000`, `2450000`, `2010000`
+- `e = 8, f = 4`: `123`, `245`, `201`
+
+The second choice is better: it yields smaller encoded values, which require
+fewer bits to store.
+{{% /alert %}}
+
+Finally, ALP subtracts the minimum encoded value (the frame of reference) from
+every encoded value before bit-packing, further reducing the bits required.
+
+{{% alert title="Example" color="info" %}}
+
+The values above require only `7` bits each after subtracting the frame of
reference:
+
+- Input values: `123`, `245`, and `201` (`8` bits per value)
+- Minimum value (frame of reference): `123`
+- Final bit-packed values: `0`, `122`, and `78` (`7` bits per value)
+
+{{% /alert %}}
+
+The encoding pipeline is straightforward, as shown in the following example of
+encoding a vector:
+
+<!-- Diagrams source:
https://docs.google.com/presentation/d/1NeYAGKV2wZZMSme5rVgUGGMkfOTEnxw8oCDxid5UouM
-->
+<div class="row g-3 td-max-width-on-larger-screens">
+ <div class="col-12">
+ <img src="/blog/alp/alp_encoding_example.png" alt="ALP encoding pipeline
example" class="img-fluid">
+ </div>
+ <div>
+ <b>Figure 3</b>: Encoding a vector of <code>1024</code> 64-bit
floating-point values using ALP.
+ </div>
+ <p/>
+</div>
+
+To encode this vector, the parameters <code>e = 4</code> and <code>f =
+3</code> are chosen first. Then the values are transformed to integers using
the
+formula <code>encoded = round(value × 10<sup>4</sup> ×
10<sup>-3</sup>)</code>. Each integer is
+checked by reversing the transformation with <code>decoded = encoded ×
10<sup>3</sup> × 10<sup>-4</sup></code>.
+Values that do not round-trip, such as `8.0605123` (which decodes to `8.1`),
are
+stored in the exception array. The minimum value across the vector, `3335`,
+becomes the frame of reference and is subtracted from each integer, and the
+resulting deltas are bit-packed using `15` bits.
+In this example, ALP uses `1920` bytes for the bit-packed deltas, plus a
+`13`-byte vector header and space for exceptions. `PLAIN` uses `8192` bytes for
+the same `1024` values. This comparison excludes page-level metadata for both
+encodings. See [the ALP Encoding specification] for more details on how the
+parameters are chosen and how rounding and exception handling work.
+
+[the ALP Encoding specification]:
https://parquet.apache.org/docs/file-format/data-pages/alpencoding/
+
+Decoding a vector requires similar steps, but in reverse, as shown below.
+
+
+<!-- Diagrams source:
https://docs.google.com/presentation/d/1NeYAGKV2wZZMSme5rVgUGGMkfOTEnxw8oCDxid5UouM
-->
+<div class="row g-3 td-max-width-on-larger-screens">
+ <div class="col-12">
+ <img src="/blog/alp/alp_decoding_example.png" alt="ALP decoding pipeline
example" class="img-fluid">
+ </div>
+ <div>
+ <b>Figure 4</b>: Decoding a vector of <code>1024</code> values back to
floating-point values using ALP.
+ </div>
+ <p/>
+</div>
+
+First, the bit-packed deltas are unpacked, and the original values are computed
+by <code>original = (3335 + delta) × 10<sup>3</sup> ×
+10<sup>-4</sup></code>. Then any exceptions are "patched" by overwriting the
+output array at the exception positions with the exception values.
+
+## Acknowledgements
+
+ALP was first published in a [SIGMOD 2024 paper] by Azim
+Afroozeh, Leonardo Kuffó, and Peter Boncz from the [Database Architectures
Group
+at CWI]. The [Vortex] and [Lance] formats adopted ALP early, demonstrating its
+benefits in industrial applications. In late 2025, the community began the
standardization process. Along with the
+authors of this blog post, many community members contributed, including
Divjot Arora,
+Arnav Balyan, Devan Benz, Ryan Blue, Alkis Evlogimenos, Vinoo Ganesh, Adrian
+Garcia Badaracco, Curt Hagenlocher, Amogh Jahagirdar, Micah Kornfield, Robert
+Kruszewski, Julien Le Dem, Kevin Liu, Steve Loughran, Ismaël Mejía, mwish,
+Antoine Pitrou, Adam Reeve, Ed Seidl, Russell Spitzer, Matt Topol, Jeffrey Vo,
+Daniel Weeks, Gang Wu, and Zehua Zou.
+
+<!-- The list of people came from
+Mailing list threads
+https://lists.apache.org/thread/tjtln1mmjqfoql1ls2dw9xpdk91r1909
+https://lists.apache.org/thread/nkfowy04f73cfo7g43p2v0wl79spqkpz
+https://lists.apache.org/thread/hgmd58wrv9yoopcrf61m1bg211l65tbt
+https://lists.apache.org/thread/ld025dzycrhm6dgh8p6157to7d9x8pon
+https://lists.apache.org/thread/4h75ww5h0z1hx2yk2b6z2tpt0wfh3nzq
+https://lists.apache.org/thread/gfodxyzx27pzbpkvns6zvfrm55y41sdt
+https://lists.apache.org/thread/1q84qhkj9ofjsgrj798ftl3vgww067z6
+Rust PR: https://github.com/apache/arrow-rs/pull/9372
+Java PR: https://github.com/apache/parquet-java/pull/3397
+C++ PR: https://github.com/apache/arrow/pull/48345/changes
+Spec PR: https://github.com/apache/parquet-format/pull/557
+Google Doc Spec (including all comments):
https://docs.google.com/document/d/1PlyUSfqCqPVwNt8XA-CfRqsbc0NKRG0Kk1FigEm3JOg/edit?tab=t.0#heading=h.5xf60mx6q7xk
+-->
+
+
+[SIGMOD 2024 paper]: https://dl.acm.org/doi/10.1145/3626717
+[Database Architectures Group at CWI]:
https://www.cwi.nl/en/research/database-architectures/
+[Vortex]: https://vortex.dev/
+[Lance]: https://lance.org/
+
+## Ecosystem Adoption
+
+The encoding was released as part of [parquet-format 2.14.0] in September 2026.
+ALP is already supported in at least one major open-source implementation (the
+[`parquet` 60.0.0][arrow-rs-60] Rust crate), and we expect other Parquet
+implementations to add support in the coming months. Please check the
+[Implementation Status] page for the current state of support.
+You can also try it today on your own datasets using the [tool in the ALP
benchmark
repository](https://github.com/alamb/alp_benchmark#run-on-your-own-parquet-files).
+
+
+[parquet-format 2.14.0]: /blog/2026/09/11/2.14.0/
+[arrow-rs-60]: https://crates.io/crates/parquet/60.0.0
+[Implementation Status]:
https://parquet.apache.org/docs/file-format/implementationstatus/
+
+
+## Conclusion
+
+ALP brings fast, parallelizable decoding and practical random access to
+floating-point data in a standard form that any Parquet implementation can
+read once it adds support for the encoding. Its addition is one more example
of Apache Parquet evolving to meet the needs of modern data systems.
+
+As with all additions to Parquet, this was a community endeavor, with many
+individuals and vendors working together to agree on a common standard and
+produce a well-documented specification and multiple reference
implementations. We
+expect ALP to be widely adopted in the Parquet ecosystem over the coming
+years.
+
+## Resources
+
+- [**ALP Encoding
Specification**](https://parquet.apache.org/docs/file-format/data-pages/alpencoding/)
+- [**Apache Parquet Format
Specification**](https://github.com/apache/parquet-format)
+- [**Implementation Status
Page**](https://parquet.apache.org/docs/file-format/implementationstatus/)
diff --git a/static/blog/alp/alp_decoding_example.png
b/static/blog/alp/alp_decoding_example.png
new file mode 100644
index 00000000..40b9f93c
Binary files /dev/null and b/static/blog/alp/alp_decoding_example.png differ
diff --git a/static/blog/alp/alp_encoding_example.png
b/static/blog/alp/alp_encoding_example.png
new file mode 100644
index 00000000..9a4fbf1c
Binary files /dev/null and b/static/blog/alp/alp_encoding_example.png differ
diff --git a/static/blog/alp/alp_vector_layout.png
b/static/blog/alp/alp_vector_layout.png
new file mode 100644
index 00000000..c6c5b693
Binary files /dev/null and b/static/blog/alp/alp_vector_layout.png differ
diff --git a/static/blog/alp/avg_compression_ratio.png
b/static/blog/alp/avg_compression_ratio.png
new file mode 100644
index 00000000..03c7fbf7
Binary files /dev/null and b/static/blog/alp/avg_compression_ratio.png differ
diff --git a/static/blog/alp/avg_compression_speed.png
b/static/blog/alp/avg_compression_speed.png
new file mode 100644
index 00000000..e758eade
Binary files /dev/null and b/static/blog/alp/avg_compression_speed.png differ
diff --git a/static/blog/alp/avg_decompression_speed.png
b/static/blog/alp/avg_decompression_speed.png
new file mode 100644
index 00000000..d90f583a
Binary files /dev/null and b/static/blog/alp/avg_decompression_speed.png differ
diff --git a/static/blog/alp/avg_random_access.png
b/static/blog/alp/avg_random_access.png
new file mode 100644
index 00000000..b46c0d44
Binary files /dev/null and b/static/blog/alp/avg_random_access.png differ