处理Parquet文件
DuckDB 对 Parquet 文件有高级支持,包括直接查询 Parquet 文件。 在决定是直接查询这些文件还是先将它们加载到数据库时,您需要考虑几个因素。
查询Parquet文件的原因
基本统计信息的可用性: Parquet 文件使用列式存储格式,并包含基本统计信息,如zonemaps。得益于这些特性,DuckDB 可以在 Parquet 文件上利用投影和过滤下推等优化。因此,结合投影、过滤和聚合的工作负载在 Parquet 文件上运行时往往表现良好。
存储注意事项: 从Parquet文件加载数据将需要大约相同数量的空间用于DuckDB数据库文件。因此,如果可用磁盘空间有限,直接在Parquet文件上运行查询是值得的。
反对查询Parquet文件的原因
缺乏高级统计信息: DuckDB 数据库格式具有 hyperloglog 统计信息,而 Parquet 文件则没有。这些统计信息提高了基数估计的准确性,尤其是在查询包含大量连接操作符时尤为重要。
提示。 如果您发现DuckDB在Parquet文件上生成的连接顺序不理想,请尝试将Parquet文件加载到DuckDB表中。改进的统计信息可能有助于获得更好的连接顺序。
重复查询: 如果您计划在相同的数据集上运行多个查询,将数据加载到DuckDB中是值得的。查询总是会更快一些,随着时间的推移,这会分摊初始加载时间。
高解压时间: 一些Parquet文件使用重量级压缩算法(如gzip)进行压缩。在这些情况下,每次访问文件时查询Parquet文件都需要昂贵的解压时间。同时,像Snappy、LZ4和zstd这样的轻量级压缩方法解压速度更快。您可以使用parquet_metadata
函数来查找使用的压缩算法。
微基准测试:在DuckDB数据库与Parquet上运行TPC-H
在TPC-H基准测试上的查询在Parquet文件上运行的速度大约比在DuckDB数据库上慢1.1-5.0倍。
最佳实践 如果您有可用的存储空间,并且有大量连接的工作负载和/或计划在同一数据集上运行许多查询,请首先将Parquet文件加载到数据库中。Parquet文件中的压缩算法和行组大小对性能有很大影响:使用
parquet_metadata
函数来研究这些。
行组大小的影响
DuckDB 在每行组包含 100K-1M 行的 Parquet 文件上表现最佳。原因是 DuckDB 只能在行组之间进行并行化——因此,如果 Parquet 文件只有一个巨大的行组,它只能由单个线程处理。你可以使用 parquet_metadata
函数来查看 Parquet 文件有多少个行组。在写入 Parquet 文件时,使用 row_group_size
选项。
微基准测试:在不同行组大小下运行聚合查询
我们使用不同的行组大小(在960到1,966,080之间选择)对Parquet文件运行了一个简单的聚合查询。结果如下。
行组大小 | 执行时间 |
---|---|
960 | 8.77 秒 |
1920 | 8.95 秒 |
3840 | 4.33 秒 |
7680 | 2.35 秒 |
15360 | 1.58 秒 |
30720 | 1.17 秒 |
61440 | 0.94 秒 |
122880 | 0.87 秒 |
245760 | 0.93 秒 |
491520 | 0.95 秒 |
983040 | 0.97 秒 |
1966080 | 0.88 秒 |
结果显示,行组大小小于5,000时会产生强烈的负面影响,使得运行时间比理想大小的行组大5-10倍以上,而行组大小在5,000到20,000之间时,仍然比最佳性能差1.5-2.5倍。当行组大小超过100,000时,差异较小:最佳和最差运行时间之间的差距约为10%。
Parquet 文件大小
DuckDB 还可以在多个 Parquet 文件之间进行并行化处理。建议所有文件中的总行组数至少与 CPU 线程数相同。例如,在一台有 10 个线程的机器上,10 个文件每个有 1 个行组或 1 个文件有 10 个行组都可以实现完全并行化。此外,保持单个 Parquet 文件的大小适中也是有益的。
最佳实践 理想的单个Parquet文件大小范围在100 MB到10 GB之间。
Hive分区用于过滤器下推
当使用过滤条件查询许多文件时,可以通过使用Hive格式的文件夹结构来沿过滤条件中使用的列对数据进行分区,从而提高性能。DuckDB只需要读取符合过滤条件的文件夹和文件。这在查询远程文件时尤其有用。
更多关于读写Parquet文件的技巧
有关读取和写入Parquet文件的提示,请参阅Parquet提示页面。
加载CSV文件
CSV文件通常以压缩格式分发,例如GZIP存档(.csv.gz
)。DuckDB可以在读取时解压缩这些文件。实际上,由于减少了IO操作,这通常比先解压缩文件再加载它们更快。
模式 | 加载时间 |
---|---|
从GZIP压缩的CSV文件加载 (.csv.gz ) |
107.1 秒 |
解压缩(使用并行 gunzip )并从解压缩的CSV文件中加载 |
121.3 秒 |
加载多个小型CSV文件
CSV 读取器 会对所有文件运行 CSV 嗅探器。对于许多小文件,这可能会导致不必要的高开销。 一个潜在的优化方法是关闭嗅探器。假设所有文件具有相同的 CSV 格式和列名/类型,可以按以下方式获取嗅探器选项:
.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),并使用检测器检测到的其余选项运行:
FROM read_csv('part-*.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={'hello': 'BIGINT', 'world': 'VARCHAR'});