Parquet, byte by byte · Build a Parquet reader while you read about one

Appendix BThe fixtures

Every experiment in the book runs on one of these files. Each was written by pyarrow, a production Parquet implementation, from a generator in the repository, so anyone can rebuild them and get the same bytes. The book’s reader never writes a fixture: it is tested against files it did not produce.

The rest of this page is generated by fixtures/generate.py from the same table that writes the files.

Fixtures#

Generated by fixtures/generate.py. Do not edit this file or the fixtures by hand: change the generator and run make fixtures. make check fails if a committed file differs from what the generator writes.

Every fixture is written by pyarrow, a production Parquet implementation, and never by this repository’s own code. The .json beside each file is pyarrow’s own description of it, and the Rust tests use it as an oracle.

tiny.parquet#

The smallest useful file: four rows of a sales table, one row group, no compression, no dictionary, every column required so no levels are stored. Small enough to read completely by eye. The footer laboratory starts here.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size637 bytes
Rows4
Row groups1
Footer length380 bytes
SHA-25667873fc1899b25ef…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

multiple-row-groups.parquet#

The same sales schema split into row groups of two rows. The footer now describes several row groups, each with its own column chunks and statistics, so the structure view has more than one branch.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size1517 bytes
Rows6
Row groups3
Footer length902 bytes
SHA-25631f67ae2363b4dd9…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False
row_group_size=2

types.parquet#

Three orders with a column for each physical type and the common logical types: a signed and an unsigned small integer, a decimal, a date, a UTC timestamp, a nullable string and an optional group. The same bytes mean different values depending on the logical type the schema gives them, which is ch03’s subject.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size1824 bytes
Rows3
Row groups1
Footer length1157 bytes
SHA-25605a5b091422fda61…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
is_paidBOOLEANNone00PLAIN, RLEUNCOMPRESSED
quantityINT32Int(bitWidth=8, isSigned=true)00PLAIN, RLEUNCOMPRESSED
store_idINT32Int(bitWidth=16, isSigned=false)00PLAIN, RLEUNCOMPRESSED
order_idINT64None00PLAIN, RLEUNCOMPRESSED
weight_kgFLOATNone00PLAIN, RLEUNCOMPRESSED
amountFIXED_LEN_BYTE_ARRAYDecimal(precision=9, scale=2)00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString10PLAIN, RLEUNCOMPRESSED
order_dateINT32Date00PLAIN, RLEUNCOMPRESSED
paid_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=microseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLEUNCOMPRESSED
shipping.cityBYTE_ARRAYString20PLAIN, RLEUNCOMPRESSED
shipping.postcodeBYTE_ARRAYString20PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

nested.parquet#

Four orders whose fields are missing in every way a nested value can be: a null string, a null list, an empty list, a list holding a null, and a list of structs each holding a list of its own. Every column stores repetition and definition levels, which is ch04’s subject.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size1191 bytes
Rows4
Row groups1
Footer length749 bytes
SHA-25630bedb7fb46e30b6…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
emailBYTE_ARRAYString10PLAIN, RLEUNCOMPRESSED
tags.list.elementBYTE_ARRAYString31PLAIN, RLEUNCOMPRESSED
items.list.element.skuBYTE_ARRAYString41PLAIN, RLEUNCOMPRESSED
items.list.element.discounts.list.elementINT32None62PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

dictionary.parquet#

Twelve orders written with dictionary encoding, the writer’s default. Each column chunk starts with a dictionary page of its distinct values, and its data page holds small indices into it, packed with the RLE / bit-packing hybrid.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size838 bytes
Rows12
Row groups1
Footer length415 bytes
SHA-256087accb5d17576e2…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLE, RLE_DICTIONARYUNCOMPRESSED
countryBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYUNCOMPRESSED
amount_centsINT64None00PLAIN, RLE, RLE_DICTIONARYUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=True
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

encodings.parquet#

Forty orders with one column per encoding a writer uses instead of a dictionary: DELTA_BINARY_PACKED for increasing integers, DELTA_LENGTH_BYTE_ARRAY and DELTA_BYTE_ARRAY for strings, and BYTE_STREAM_SPLIT for floats. Each column’s values were chosen to suit its encoding.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size1558 bytes
Rows40
Row groups1
Footer length604 bytes
SHA-2566a7aec2d72cd1b7c…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00DELTA_BINARY_PACKED, RLEUNCOMPRESSED
ordered_atINT64None00DELTA_BINARY_PACKED, RLEUNCOMPRESSED
skuBYTE_ARRAYString00DELTA_LENGTH_BYTE_ARRAY, RLEUNCOMPRESSED
urlBYTE_ARRAYString00DELTA_BYTE_ARRAY, RLEUNCOMPRESSED
weight_kgFLOATNone00BYTE_STREAM_SPLIT, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False
column_encoding={'order_id': 'DELTA_BINARY_PACKED', 'ordered_at': 'DELTA_BINARY_PACKED', 'sku': 'DELTA_LENGTH_BYTE_ARRAY', 'url': 'DELTA_BYTE_ARRAY', 'weight_kg': 'BYTE_STREAM_SPLIT'}

pages.parquet#

Sixty orders written in small pages, so every column chunk holds several data pages, and the country column has a dictionary page in front of them and nulls in them. Data page version 1.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size2102 bytes
Rows60
Row groups1
Footer length396 bytes
SHA-256ff3686c993b2a7d4…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString10PLAIN, RLE, RLE_DICTIONARYUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=['country']
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False
data_page_size=128
write_batch_size=16

pages-v2.parquet#

The same sixty orders in data page version 2, with a CRC-32 checksum in every page header. Compare its pages with pages.parquet’s: the levels move out of the compressible section and each header counts its rows and nulls.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size2223 bytes
Rows60
Row groups1
Footer length396 bytes
SHA-25607c512f36c0dba3f…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString10PLAIN, RLE, RLE_DICTIONARYUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=['country']
write_statistics=True
store_schema=False
data_page_version='2.0'
write_page_index=False
data_page_size=128
write_batch_size=16
write_page_checksum=True

codec-none.parquet#

256 orders, PLAIN-encoded with no dictionary and not compressed: the baseline the other codec-* files are measured against.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size15953 bytes
Rows256
Row groups1
Footer length806 bytes
SHA-256d53c6b99c3c150bd…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
skuBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED
weight_kgDOUBLENone00PLAIN, RLEUNCOMPRESSED
distance_kmDOUBLENone00PLAIN, RLEUNCOMPRESSED
noteBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

codec-snappy.parquet#

256 orders, PLAIN-encoded with no dictionary, compressed with snappy. The codec-* files hold identical pages before compression, so any difference in their sizes is the codec’s.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size6977 bytes
Rows256
Row groups1
Footer length802 bytes
SHA-256abc55ee0a7e3422f…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLESNAPPY
countryBYTE_ARRAYString00PLAIN, RLESNAPPY
skuBYTE_ARRAYString00PLAIN, RLESNAPPY
amount_centsINT64None00PLAIN, RLESNAPPY
weight_kgDOUBLENone00PLAIN, RLESNAPPY
distance_kmDOUBLENone00PLAIN, RLESNAPPY
noteBYTE_ARRAYString00PLAIN, RLESNAPPY

Writer options:

compression='snappy'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

codec-gzip.parquet#

256 orders, PLAIN-encoded with no dictionary, compressed with gzip. The codec-* files hold identical pages before compression, so any difference in their sizes is the codec’s.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size5052 bytes
Rows256
Row groups1
Footer length802 bytes
SHA-2563e1f661b4783d749…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEGZIP
countryBYTE_ARRAYString00PLAIN, RLEGZIP
skuBYTE_ARRAYString00PLAIN, RLEGZIP
amount_centsINT64None00PLAIN, RLEGZIP
weight_kgDOUBLENone00PLAIN, RLEGZIP
distance_kmDOUBLENone00PLAIN, RLEGZIP
noteBYTE_ARRAYString00PLAIN, RLEGZIP

Writer options:

compression='gzip'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

codec-lz4.parquet#

256 orders, PLAIN-encoded with no dictionary, compressed with lz4. The codec-* files hold identical pages before compression, so any difference in their sizes is the codec’s.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size6641 bytes
Rows256
Row groups1
Footer length802 bytes
SHA-2561bd91f635d0101e5…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLELZ4
countryBYTE_ARRAYString00PLAIN, RLELZ4
skuBYTE_ARRAYString00PLAIN, RLELZ4
amount_centsINT64None00PLAIN, RLELZ4
weight_kgDOUBLENone00PLAIN, RLELZ4
distance_kmDOUBLENone00PLAIN, RLELZ4
noteBYTE_ARRAYString00PLAIN, RLELZ4

Writer options:

compression='lz4'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

codec-zstd.parquet#

256 orders, PLAIN-encoded with no dictionary, compressed with zstd. The codec-* files hold identical pages before compression, so any difference in their sizes is the codec’s.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size5060 bytes
Rows256
Row groups1
Footer length802 bytes
SHA-256f9d0f560ca99b6bd…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEZSTD
countryBYTE_ARRAYString00PLAIN, RLEZSTD
skuBYTE_ARRAYString00PLAIN, RLEZSTD
amount_centsINT64None00PLAIN, RLEZSTD
weight_kgDOUBLENone00PLAIN, RLEZSTD
distance_kmDOUBLENone00PLAIN, RLEZSTD
noteBYTE_ARRAYString00PLAIN, RLEZSTD

Writer options:

compression='zstd'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

codec-brotli.parquet#

256 orders, PLAIN-encoded with no dictionary, compressed with brotli. The codec-* files hold identical pages before compression, so any difference in their sizes is the codec’s.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size4513 bytes
Rows256
Row groups1
Footer length801 bytes
SHA-25632545187a00721f9…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEBROTLI
countryBYTE_ARRAYString00PLAIN, RLEBROTLI
skuBYTE_ARRAYString00PLAIN, RLEBROTLI
amount_centsINT64None00PLAIN, RLEBROTLI
weight_kgDOUBLENone00PLAIN, RLEBROTLI
distance_kmDOUBLENone00PLAIN, RLEBROTLI
noteBYTE_ARRAYString00PLAIN, RLEBROTLI

Writer options:

compression='brotli'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

statistics.parquet#

Twelve orders in three row groups, with a column for each way a reader can misread statistics: unsigned integers above 2^31, negative small integers, strings with non-ASCII bytes, floats with NaN and signed zeros, negative decimals, a column that is null throughout one row group, and a column written without statistics. The row groups declare that they are sorted by order_id.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size3911 bytes
Rows12
Row groups3
Footer length2139 bytes
SHA-2565954be0a5dbc9bf1…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None10PLAIN, RLEUNCOMPRESSED
customer_idINT32Int(bitWidth=32, isSigned=false)10PLAIN, RLEUNCOMPRESSED
deltaINT32Int(bitWidth=8, isSigned=true)10PLAIN, RLEUNCOMPRESSED
cityBYTE_ARRAYString10PLAIN, RLEUNCOMPRESSED
temp_cDOUBLENone10PLAIN, RLEUNCOMPRESSED
amountFIXED_LEN_BYTE_ARRAYDecimal(precision=9, scale=2)10PLAIN, RLEUNCOMPRESSED
couponBYTE_ARRAYString10PLAIN, RLEUNCOMPRESSED
noteBYTE_ARRAYString10PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=['order_id', 'customer_id', 'delta', 'city', 'temp_c', 'amount', 'coupon']
store_schema=False
data_page_version='1.0'
write_page_index=False
row_group_size=4
sorting_columns=[{'column_index': 0, 'descending': False, 'nulls_first': False}]

pruning-sorted.parquet#

800 orders written in order_id order: four row groups of 200, pages of 40 rows, a page index for every column, and a Bloom filter for customer_id. Because the rows are sorted, each row group and page covers a narrow range of order_id, and a reader can skip most of the file for a query on it.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size26926 bytes
Rows800
Row groups4
Footer length1892 bytes
SHA-256007f6565c1518742…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
customer_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString10PLAIN, RLE, RLE_DICTIONARYUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=['country']
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=200
max_rows_per_page=40
bloom_filter_options={'customer_id': {'ndv': 200, 'fpp': 0.05}}

pruning-shuffled.parquet#

The same 800 orders as pruning-sorted.parquet, shuffled, and written the same way. Every row group and page now covers nearly the whole range of order_id, and the statistics can rule out almost nothing.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size26895 bytes
Rows800
Row groups4
Footer length1892 bytes
SHA-256906bc2255f0439c6…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
customer_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString10PLAIN, RLE, RLE_DICTIONARYUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=['country']
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=200
max_rows_per_page=40
bloom_filter_options={'customer_id': {'ndv': 200, 'fpp': 0.05}}

writing-baseline.parquet#

800 orders in order_id order, written the way ch11 starts from: Snappy, dictionary encoding, four row groups of 200, pages of 40 rows, and a page index. Every other writing-* file changes one of these.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size28779 bytes
Rows800
Row groups4
Footer length2935 bytes
SHA-25662e6ffef5da3611b…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
ordered_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLE, RLE_DICTIONARYSNAPPY
customer_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
countryBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
statusBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
amount_centsINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY

Writer options:

compression='snappy'
use_dictionary=True
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=200
max_rows_per_page=40

writing-one-group.parquet#

writing-baseline.parquet in one row group of 800 rows.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size26339 bytes
Rows800
Row groups1
Footer length889 bytes
SHA-25602973f9b4bf9bc24…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
ordered_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLE, RLE_DICTIONARYSNAPPY
customer_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
countryBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
statusBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
amount_centsINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY

Writer options:

compression='snappy'
use_dictionary=True
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=800
max_rows_per_page=40

writing-small-groups.parquet#

writing-baseline.parquet in row groups of 40 rows: twenty row groups, each with its own column chunks, statistics and page index.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size44190 bytes
Rows800
Row groups20
Footer length13648 bytes
SHA-2562746a47c6997008b…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
ordered_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLE, RLE_DICTIONARYSNAPPY
customer_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
countryBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
statusBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
amount_centsINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY

Writer options:

compression='snappy'
use_dictionary=True
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=40
max_rows_per_page=40

writing-by-country.parquet#

writing-baseline.parquet’s rows sorted by country, then order_id.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size28430 bytes
Rows800
Row groups4
Footer length2935 bytes
SHA-256d6f68b80f3ab65b8…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
ordered_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLE, RLE_DICTIONARYSNAPPY
customer_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
countryBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
statusBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
amount_centsINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY

Writer options:

compression='snappy'
use_dictionary=True
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=200
max_rows_per_page=40

writing-shuffled.parquet#

writing-baseline.parquet’s rows in a pseudo-random order.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size28740 bytes
Rows800
Row groups4
Footer length2935 bytes
SHA-25656aee95056cb7f35…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
ordered_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLE, RLE_DICTIONARYSNAPPY
customer_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
countryBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
statusBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
amount_centsINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY

Writer options:

compression='snappy'
use_dictionary=True
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=200
max_rows_per_page=40

writing-plain.parquet#

writing-baseline.parquet without dictionary encoding.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size28910 bytes
Rows800
Row groups4
Footer length2665 bytes
SHA-25682221f0512144758…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLESNAPPY
ordered_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLESNAPPY
customer_idINT64None00PLAIN, RLESNAPPY
countryBYTE_ARRAYString00PLAIN, RLESNAPPY
statusBYTE_ARRAYString00PLAIN, RLESNAPPY
amount_centsINT64None00PLAIN, RLESNAPPY

Writer options:

compression='snappy'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=True
row_group_size=200
max_rows_per_page=40

writing-no-index.parquet#

writing-baseline.parquet without a page index. pyarrow then writes each page’s statistics into its page header instead.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size29253 bytes
Rows800
Row groups4
Footer length2633 bytes
SHA-256a1d69a275b01b231…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
ordered_atINT64Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, is_from_converted_type=false, force_set_converted_type=false)00PLAIN, RLE, RLE_DICTIONARYSNAPPY
customer_idINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY
countryBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
statusBYTE_ARRAYString00PLAIN, RLE, RLE_DICTIONARYSNAPPY
amount_centsINT64None00PLAIN, RLE, RLE_DICTIONARYSNAPPY

Writer options:

compression='snappy'
use_dictionary=True
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False
row_group_size=200
max_rows_per_page=40

Twelve orders with modular encryption and an encrypted footer: the file ends in PARE. email is encrypted with the pii key and amount_cents with the finance key; order_id and country are not encrypted, but the footer that would say where they are is. Written once, since encryption is never byte-for-byte reproducible; the check decrypts it with the published test keys.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size2123 bytes
Rows12
Row groups1
Footer length954 bytes
SHA-2560ab7d6a98676365f…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
emailBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

The same orders and keys with a plaintext footer: the file ends in PAR1, the footer is readable and signed, and only the two encrypted columns’ pages and details are hidden.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size2192 bytes
Rows12
Row groups1
Footer length1222 bytes
SHA-2569a58c765f5254a27…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEUNCOMPRESSED
countryBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
emailBYTE_ARRAYString00PLAIN, RLEUNCOMPRESSED
amount_centsINT64None00PLAIN, RLEUNCOMPRESSED

Writer options:

compression='none'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False

pages-v2-snappy.parquet#

pages-v2.parquet compressed with Snappy. In data page version 2 only a page’s values are compressed: its levels stay as they were, so a reader can count rows and nulls without decompressing anything.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size1854 bytes
Rows60
Row groups1
Footer length396 bytes
SHA-25646e6c748a90443af…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLESNAPPY
countryBYTE_ARRAYString10PLAIN, RLE, RLE_DICTIONARYSNAPPY
amount_centsINT64None00PLAIN, RLESNAPPY

Writer options:

compression='snappy'
use_dictionary=['country']
write_statistics=True
store_schema=False
data_page_version='2.0'
write_page_index=False
data_page_size=128
write_batch_size=16
write_page_checksum=True

codec-zstd-split.parquet#

codec-zstd.parquet with its two float columns written BYTE_STREAM_SPLIT. Comparing the two files measures what splitting a float’s bytes is worth to a compressor, for decimal-looking floats and for noisy ones.

Written bypyarrow 25.0.1 (fixtures/generate.py)
Size5360 bytes
Rows256
Row groups1
Footer length802 bytes
SHA-256b916da53758901c3…

Leaf columns, as pyarrow reads the Parquet schema:

ColumnPhysical typeLogical typeMax defMax repEncodingsCodec
order_idINT64None00PLAIN, RLEZSTD
countryBYTE_ARRAYString00PLAIN, RLEZSTD
skuBYTE_ARRAYString00PLAIN, RLEZSTD
amount_centsINT64None00PLAIN, RLEZSTD
weight_kgDOUBLENone00BYTE_STREAM_SPLIT, RLEZSTD
distance_kmDOUBLENone00BYTE_STREAM_SPLIT, RLEZSTD
noteBYTE_ARRAYString00PLAIN, RLEZSTD

Writer options:

compression='zstd'
use_dictionary=False
write_statistics=True
store_schema=False
data_page_version='1.0'
write_page_index=False
column_encoding={'weight_kg': 'BYTE_STREAM_SPLIT', 'distance_km': 'BYTE_STREAM_SPLIT'}
By Chris Snow, in collaboration with Claude (Anthropic) · Prose and figures: CC BY-NC 4.0 · Code: Apache 2.0

Built from commit c32f48e.