Reference · 速查
Parquet 速查表
写代码时放在旁边的一页纸。命令可直接抄,参数带默认值与建议值。打印友好。
① 30 秒体检一个文件
-- 一眼看清:每列多大、什么编码、几个 row group、统计信息在不在
SELECT path_in_schema AS col,
round(sum(total_compressed_size)/1024.0/1024, 2) AS mb,
any_value(encodings) AS enc,
any_value(compression) AS codec
FROM parquet_metadata('f.parquet') GROUP BY 1 ORDER BY mb DESC;
SELECT * FROM parquet_file_metadata('f.parquet'); -- 行数/rg 数/footer 大小
SELECT * FROM parquet_schema('f.parquet'); -- 物理类型 + 逻辑类型
或直接用课程脚本:python inspect_parquet.py f.parquet
② 写入参数:默认值 vs 建议值
| pyarrow 参数 | 默认 | 建议 | 为什么 |
compression | "snappy" | "zstd" | 小 34%,写入只慢 12% |
compression_level | 库默认 | 别动 | 调到 15 只省 10%,写入慢 6.5 倍 |
row_group_size | 1,048,576 行 | 100k–1M 行 | 太大跳不掉、并行差;太小元数据占比高 |
use_dictionary | True(全列) | 列名列表 | 不收窄的话字典会抢跑,其他编码失效 |
column_encoding | 无 | 时间/自增列给 DELTA_BINARY_PACKED | 实测该列 7.85 → 0.03 MB |
write_page_index | False | True | 代价极小的页级跳过 |
write_statistics | True | 保持 | 关掉等于放弃 row group 跳过 |
sorting_columns | 无 | 与实际排序一致 | 把排序事实记进 footer |
bloom_filter_options | 无 | 按需 | 仅高基数 + 等值点查 + 非排序键 |
data_page_size | 1 MB | 保持 | 另有 max_rows_per_page=20000 同时生效 |
③ 标准写法(抄这个)
import pyarrow.parquet as pq
LOW_CARD = ["tenant_id", "building_code", "device_id", "metric"]
t = table.sort_by([("device_id", "ascending"), ("ts", "ascending")])
pq.write_table(
t, "out.parquet",
compression="zstd",
row_group_size=200_000,
use_dictionary=LOW_CARD, # 收窄,防抢跑
column_encoding={"ts": "DELTA_BINARY_PACKED"}, # 时序列
write_page_index=True,
sorting_columns=pq.SortingColumn.from_ordering(
t.schema, [("device_id", "ascending"), ("ts", "ascending")]),
)
COPY (SELECT * FROM src ORDER BY device_id, ts)
TO 'warehouse/iot'
(FORMAT parquet, PARTITION_BY (day), COMPRESSION zstd, ROW_GROUP_SIZE 200000);
④ 读取速查
| 要做的事 | DuckDB | pyarrow |
| 读单文件 | SELECT * FROM 'f.parquet' | pq.read_table("f.parquet") |
| 读一批 | read_parquet('dir/**/*.parquet') | ds.dataset("dir", format="parquet") |
| Hive 分区当列用 | read_parquet(…, hive_partitioning=true) | ds.dataset(…, partitioning="hive") |
| 列不一致时合并 | read_parquet(…, union_by_name=true) | ds.dataset(…) 自动按名 |
| 知道行来自哪个文件 | read_parquet(…, filename=true) | 加 ds.field("__filename") |
| 只读几列 | SELECT a, b FROM … | columns=["a","b"] |
| 谓词下推 | 写 WHERE 即可 | filters=[("day","==","2026-01-08")] |
| 只要行数 | SELECT count(*) FROM …(0.2 ms) | pq.ParquetFile(f).metadata.num_rows |
⑤ 编码选择决策树
这一列是什么?
├─ 重复值多(租户/楼栋/设备/枚举/状态)
│ → RLE_DICTIONARY(默认就是,别关)
├─ 递增整数(时间戳/自增 ID/序号)
│ → DELTA_BINARY_PACKED ← 收益最大,务必显式指定
│ ⚠ 同时用 use_dictionary=[低基数列…] 收窄,否则不生效
├─ 有序且共同前缀的字符串(路径/URL/层级编码)
│ → DELTA_BYTE_ARRAY
├─ 浮点物理量(温度/压力/功率)
│ → 先试 BYTE_STREAM_SPLIT,实测对比后再决定 ← 可能是负收益
└─ 其他(高基数随机字符串、UUID)
→ PLAIN + zstd,并考虑给它加 Bloom filter
⑥ Sizing 决策表
| 决定 | 规则 |
| 单文件大小 | 128 MB – 1 GB |
| row group | 100k – 1M 行(约 64–256 MB) |
| 分区粒度 | 让每个分区落在 100 MB – 1 GB。日增 < 100 MB 就按月分,别按天 |
| 第二个分区键 | 几乎总是错的。用排序键解决第二维度 |
| 排序键 | 选最常出现在 WHERE 里、且分区没覆盖的那个维度 |
| 官方 512 MB–1 GB row group 建议 | 那是 HDFS block 时代的取值,对象存储 + 单机引擎场景不适用 |
⑦ 常见坑
| 症状 | 原因 | 解法 |
| 指定了编码但文件大小没变 | 字典抢跑 | 同时收窄 use_dictionary,写完查 encodings 栏验证 |
| 查询扫了全部 row group | 排序键不匹配查询,或 row group 太大 | 换排序键;row_group_size 调到 100k–200k |
| 统计信息看起来是空的 | 读了已废弃的 stats_min/stats_max | 改用 stats_min_value/stats_max_value |
| 归档目录体积不降反增 | 分区切太细,小文件元数据开销 | 合并分区层级 + 定期 compaction |
| 分区数比天数多一个 | 分区键用了本地时区 | strftime(ts AT TIME ZONE 'UTC', …) |
| 混读新旧文件报错/错位 | schema 不一致按位置对齐 | union_by_name=true |
| compaction 后查出重复数据 | 裸目录非原子 | 临时路径 + 整体提交;反复出问题就该上表格式 |
| 换个库写出来大小差很多 | 写入器编码策略不同 | 正常现象。换写入器后重新体检,别搬结论 |
⑧ 本课程实测数字(4,320,000 行楼宇时序样本)
pyarrow 25.0.0 / DuckDB 1.5.5 / macOS ARM / 2026-08。用作量级参考,不是通用基准。
| 项目 | 数字 |
| JSONL / CSV / Parquet(zstd+排序) | 624.76 MB / 307.52 MB / 21.79 MB |
| 关掉全部编码压缩的 Parquet | 266.49 MB(≈ CSV,说明布局本身不省空间) |
排序对 device_id 列的影响 | 4.14 MB → 0.02 MB |
ts 列换 DELTA 编码 | 7.85 MB → 0.03 MB(整文件 21.79 → 13.54 MB) |
| zstd vs snappy(同编码) | 13.54 MB vs 20.45 MB,写入 0.55 s vs 0.49 s |
| zstd 默认 vs 15 级 | 13.54 MB / 0.55 s vs 12.23 MB / 3.60 s |
| 列裁剪(1 列 vs 7 列 vs count(*)) | 12.2 ms / 42.1 ms / 0.2 ms |
| 排序键选对 vs 选错(时间窗查询) | 读 1/44 个 rg · 2.8 ms vs 读 44/44 · 14.2 ms |
| 分区过细(day vs day+device) | 16 文件 14.16 MB 2.5 ms vs 3218 文件 31.42 MB 131.2 ms |
| PG → Parquet 归档 | 51 MB(432k 行)→ 3.16 MB,校验和一致 |
⑨ 环境
$ pip install pyarrow duckdb # 全课程只需这两个
$ brew install duckdb # 可选:命令行版
$ python gen_sample.py # 生成样本数据(约 1 分钟)
$ python inspect_parquet.py f.parquet # 体检任意文件