跳到主要内容

文件格式

处理 Parquet 文件

Goose 对 Parquet 文件有完善支持,包括直接查询 Parquet 文件。 在决定是直接查询这些文件,还是先加载到数据库时,需要综合考虑多个因素。

直接查询 Parquet 的理由

基础统计信息可用: Parquet 采用列式存储,并包含 zonemaps 等基础统计信息。借助这些特性,Goose 可在 Parquet 上利用投影下推、过滤下推等优化。因此,结合投影、过滤、聚合的负载在 Parquet 上通常表现不错。

存储空间考量: 从 Parquet 加载数据到 Goose 后,数据库文件通常需要与原始数据规模近似的额外空间。因此,若磁盘空间紧张,直接在 Parquet 上查询通常更合适。

不建议直接查询 Parquet 的理由

缺少高级统计信息: Goose 数据库格式包含 Parquet 不具备的 hyperloglog statistics。这些统计可提升基数估计准确性,尤其在查询包含大量 Join 算子时尤为重要。

提示。 如果你发现 Goose 在 Parquet 上产生了次优 Join 顺序,建议先将 Parquet 加载为 Goose 表。更完善的统计信息通常有助于得到更优 Join 顺序。

重复查询场景: 若你计划在同一数据集上执行多次查询,建议先加载到 Goose。单次查询通常会更快,长期可摊销初始加载成本。

高解压耗时: 部分 Parquet 文件使用 gzip 等重量级压缩算法。这种情况下,每次访问文件都可能付出较高解压成本。相比之下,Snappy、LZ4、zstd 等轻量压缩解压更快。可使用 parquet_metadata 函数 查看压缩算法。

微基准:在 Goose 数据库与 Parquet 上运行 TPC-H

TPC-H benchmark 中,查询在 Parquet 上通常比在 Goose 数据库上慢约 1.1-5.0×。

最佳实践:如果存储空间充足,且你的负载 Join 较重和/或会在同一数据集上执行大量查询,建议先将 Parquet 加载到数据库。Parquet 的压缩算法与 row group 大小对性能影响很大,可使用 parquet_metadata 函数进行分析。

Row Group 大小的影响

Goose 在每个 row group 约 100K-1M 行的 Parquet 文件上表现最佳。原因是 Goose 只能按 row group 并行化:若文件只有一个超大 row group,就只能由单线程处理。你可以通过 parquet_metadata 函数查看 row group 数量。写 Parquet 时可使用 row_group_size 选项。

微基准:不同 Row Group 大小下的聚合查询

我们在 Parquet 文件上以 960 到 1,966,080 的不同 row group 大小运行简单聚合查询,结果如下。

Row group sizeExecution time
9608.77 s
19208.95 s
38404.33 s
76802.35 s
153601.58 s
307201.17 s
614400.94 s
1228800.87 s
2457600.93 s
4915200.95 s
9830400.97 s
19660800.88 s

结果表明,row group 大小 <5,000 会显著拖慢性能,耗时可比理想大小高出 5-10× 以上;5,000 到 20,000 之间也仍比最佳性能慢约 1.5-2.5×。当 row group 大于 100,000 后,差异明显收敛:最佳与最差耗时差距约 10%

Parquet 文件大小

Goose 还可在多个 Parquet 文件之间并行处理。建议所有文件的 row group 总数至少不低于 CPU 线程数。例如,10 线程机器上,无论是 10 个各 1 个 row group 的文件,还是 1 个含 10 个 row group 的文件,都可实现充分并行。与此同时,保持单个 Parquet 文件大小适中也更有利。

最佳实践:单个 Parquet 文件的理想大小范围为 100 MB 到 10 GB。

面向过滤下推的 Hive 分区

当对大量文件执行带过滤条件的查询时,可使用 Hive 格式目录结构按过滤列进行分区,以提升性能。这样 Goose 只需读取满足过滤条件的目录与文件,尤其在远程文件场景下收益明显。

更多 Parquet 读写建议

更多 Parquet 读写建议见 Parquet Tips 页面

加载 CSV 文件

CSV 文件常以压缩格式分发(如 GZIP 压缩包 .csv.gz)。Goose 可在线解压读取。由于减少了 IO,通常比先解压再加载更快。

SchemaLoad time
Load from GZIP-compressed CSV files (.csv.gz)107.1 s
Decompressing (using parallel gunzip) and loading from decompressed CSV files121.3 s

加载大量小 CSV 文件

CSV reader 会在所有文件上运行 CSV sniffer。对于大量小文件,这可能带来不必要的高开销。 一种可行优化是关闭 sniffer。若可确认所有文件的 CSV 方言及列名/类型一致,可先按如下方式获取 sniffer 检测出的选项:

.mode line
SELECT Prompt FROM sniff_csv('part-0001.csv');
Prompt = FROM read_csv('file_path.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={'hello': 'BIGINT', 'world': 'VARCHAR'});

然后即可调整 read_csv 命令(例如结合文件名展开 globbing),并使用 sniffer 检出的其余参数执行:

FROM read_csv('part-*.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={'hello': 'BIGINT', 'world': 'VARCHAR'});