用DuckDB的列式执行引擎替代Pandas的 eager loading,解决大数据集分析时容器OOM问题,显著降低服务器资源消耗。
每个 Python 数据工程师都会遇到同样的瓶颈。你用 Pandas 写了一个处理数据集的脚本,在本地 500MB 的样本上跑得完美无缺。部署到生产环境后,数据集膨胀到 15GB,容器内存耗尽,内核悄无声息地终止了你的进程。
我们花了数年时间试图通过堆硬件来解决这个问题——给 EC2 实例配上 64GB RAM,或者把全部代码重写成 PySpark。两种方案都代价高昂且维护痛苦。Coding Macaw 最终决定不再和内存限制较劲,而是彻底更换了执行引擎。
Pandas 的核心问题在于 eager 加载。当调用 pd.read_parquet() 时,Python 会在执行任何计算之前,试图将整个未压缩文件一次性加载到内存中。
如果你只需要按某一列分组后求和,把其他 50 列全部加载进 Pandas DataFrame 就是对服务器资源的巨大浪费。你的脚本在做那些本应由存储层来完成的脏活累活。
如下图所示,这种模型会导致 RAM 使用量出现巨大峰值,在生产规模处理数据时不可避免地引发容器不稳定。

DuckDB 是一个进程内 SQL OLAP 数据库。它像 SQLite 一样运行在你的 Python 脚本内部。区别在于 DuckDB 是列式存储,专门为分析查询构建。它可以直接从磁盘查询 Parquet 文件,而无需将整个文件加载到 RAM 中。

你可以在 Python 容器内获得数据仓库的全部能力。
来看看这段代码带来了怎样的变化。我们需要处理一个包含数十个 Parquet 文件的目录,这些文件代表了 50GB 的用户事件数据。
import duckdb
# Connect to a temporary in-memory database instance
con = duckdb.connect()
# Query the Parquet files directly from disk using SQL
query = """
SELECT
user_id,
count(*) as total_events,
sum(purchase_amount) as total_spent
FROM read_parquet('s3://my-production-bucket/events_2025/*.parquet')
WHERE event_type = 'checkout'
GROUP BY user_id
HAVING total_spent > 1000
ORDER BY total_spent DESC
LIMIT 100
"""
# Execute the query and return the result as a PyArrow table
result = con.execute(query).arrow()
print(result)
仔细看上面的代码。我们没有先把原始 Parquet 文件加载到 DataFrame 里,而是直接把文件路径传递给 SQL 语句内部的 read_parquet 函数。DuckDB 将过滤条件(只检查 'checkout' 事件类型)和聚合操作下推到存储层。它扫描文件、忽略我们不需要的列,然后通过执行引擎以小批次流式传输相关数据。
最终结果是一个微小的 PyArrow 表,只包含恰好 100 行。在整个操作过程中,你的峰值内存使用量保持平稳。此外,PyArrow 与 Python 生态系统其他部分的集成非常完美——如果真的需要绘图,你可以在后面将这 100 行轻松转换为 Pandas DataFrame。
处理大数据并不总是需要分布式集群。一个进程内列式引擎可以在标准服务器上处理数百 GB 的数据。它让你的基础设施保持简单,部署速度飞快,AWS 账单低得令人难以置信。
如果你的团队正遇到内存瓶颈,卸下那些重量级框架,给 DuckDB 一个机会吧。
你现在是如何处理分析管道中的内存溢出错误的?欢迎在评论区留言。