原文地址:https://github.com/danielbeach/duckdbAndDaftEat1TB
在单节点上处理 1TB Parquet 数据
本仓库展示了现代数据处理引擎(如 DuckDB 和 Daft )如何在单节点 上高效处理存储在 S3 中的 1TB Parquet 数据,即使数据集大于可用内存。
数据集
测试数据集使用 rustGenerate1TB 生成,该工具创建了 1TB 的银行交易数据作为 Parquet 文件并上传到 AWS S3。数据集包含:
- 位置 :
s3://confessions-of-a-data-guy/transactions/ - 格式:Parquet 文件(每个约 256MB)
- 模式 :
transaction_id(字符串)datetime(日期时间)customer_id(Int64)order_qty(Int32)order_amount(Float64)
为什么单节点处理很重要
传统的数据处理通常需要分布式系统(如 Spark 集群)来处理大型数据集。然而,现代分析引擎可以在单台机器上处理大于可用内存的数据集,通过以下方式实现:
- 流式/分块处理:以小块读取和处理数据,而不是将所有数据加载到内存中
- 惰性求值:构建查询计划以优化数据访问模式
- 溢出到磁盘:在内存受限时自动将中间结果溢出到磁盘
- 列式格式:利用 Parquet 的列式存储进行高效的列剪裁和谓词下推
DuckDB 实现
文件 :duckdb_main.py
DuckDB 使用其 httpfs 扩展直接从 S3 读取 Parquet 文件,并使用以下方式进行处理:
- 自动溢出到磁盘 :通过
temp_directory配置,在内存受限时将中间结果溢出到磁盘 - 内存管理:可配置的内存限制允许 DuckDB 处理大于 RAM 的数据
- SQL 接口:使用标准 SQL 查询,并自动进行查询优化
- 直接 S3 访问:无需在本地下载文件
主要特点:
- 读取匹配 S3 glob 模式的所有 Parquet 文件
- 按日期对交易进行分组
- 聚合:交易计数、客户计数、订单金额和数量
- 直接将结果写入 CSV,而无需在内存中物化
内存配置:
python
con.execute("SET memory_limit='50GB';")
con.execute("SET temp_directory=?;", [str(spill_dir)])
即使在 16GB 的机器上,DuckDB 也可以通过自动溢出到磁盘来处理 1TB 数据。
Daft 实现
文件 :daft_main.py
Daft 是一个分布式 DataFrame 库,可以在单节点上运行并高效处理大于内存的数据集:
- 批量处理:以可配置的批次大小(20万行)处理数据
- 惰性执行:构建执行计划,在物化之前进行优化
- 内存高效:通过流式执行自动管理内存
- 原生 S3 支持:直接访问 S3,自动处理凭据
主要特点:
- 使用 glob 模式从 S3 读取 Parquet 文件
- 将 datetime 转换为 date 以便分组
- 执行聚合操作,自动进行内存管理
- 将结果转换为 Arrow 格式,以便高效写入 CSV
内存管理:
python
df = df.into_batches(200_000) # 以 20 万行为批次进行处理
使用方法
前提条件
-
AWS 凭证:配置 AWS 凭证以访问 S3:
bashaws configure # 或设置环境变量: export AWS_ACCESS_KEY_ID=your_key export AWS_SECRET_ACCESS_KEY=your_secret export AWS_DEFAULT_REGION=us-east-1 -
Python 依赖项:安装所需软件包:
bashpip install duckdb daft pyarrow boto3
运行 DuckDB
bash
python duckdb_main.py
这将:
- 从 S3 读取所有 Parquet 文件
- 按日期聚合交易
- 将结果写入
daily_transactions_summary.csv - 显示执行时间
运行 Daft
bash
python daft_main.py
这将:
- 分批读取 Parquet 文件
- 执行相同的聚合操作
- 将结果写入 CSV
- 显示执行时间
性能特点
这两个引擎都证明了单节点可以处理大于内存的数据集:
- 内存使用:通过流式处理和溢出到磁盘,保持在可用 RAM 内
- I/O 效率:利用 Parquet 的列式格式进行选择性列读取
- 网络优化:仅从 S3 读取必要的数据
- 可扩展性:可以处理比可用内存大很多倍的数据集
查询示例
两种实现执行相同的聚合操作:
sql
SELECT
CAST(datetime AS DATE) AS date,
COUNT(transaction_id) AS transaction_count,
COUNT(customer_id) AS customer_count,
SUM(order_amount) AS total_order_amount,
SUM(order_qty) AS total_order_qty
FROM read_parquet('s3://confessions-of-a-data-guy/transactions/**/*.parquet')
GROUP BY 1
ORDER BY total_order_amount DESC
输出
两个脚本都会生成 daily_transactions_summary.csv,其中包含按日期聚合的交易数据,并按总订单金额降序排列。
这为什么重要
这证明了现代分析引擎已经发展到不再需要分布式系统来处理大型数据集。单节点处理提供了:
- 更简单的架构:无需集群管理开销
- 成本效益:单机 vs. 多节点集群
- 更快的开发:更易于设置和调试
- 足够的性能:通常对于分析工作负载来说足够快
关键在于使用专为分析工作负载设计的引擎(列式处理、向量化、查询优化),而不是逐行处理。
参考资料
- 数据集生成器:rustGenerate1TB
- DuckDB 文档
- Daft 文档