PAYATHON 2026

DuckDB 聚合 NDJSON 时如何设置 memory_limit?

数据小陈

结论

memory_limit='13GB' 能让这次查询跑通,只能说明这个设置为当前查询提供了足够的 DuckDB 可用内存,并不适用于所有数据集。查询需要多少内存,主要取决于不同 url 的数量、每组最终保留的 html 大小、数据类型和并行线程数。只知道输入文件约 16GB,无法推算合适的 memory_limit。

还需要核对去重方式:描述中说要按 html 去重,但示例 SQL 使用的是 GROUP BY url。下面按代码实际执行的逻辑说明,也就是按 url 分组,并取 dateModified 最新记录对应的 html。如果要按 HTML 内容去重,需要另外确认分组键。

为什么这个查询会占用较多内存

GROUP BY url 会执行聚合。DuckDB 需要为各个分组维护聚合状态;arg_max(html, dateModified) 还要记录每组当前的最大日期及对应的 html。因此,不同 url 的数量以及 HTML 内容的大小,都会明显影响内存压力。输入文件压缩后或存储在磁盘上的大小,并不等于聚合过程中需要的内存。

DuckDB 的部分阻塞型操作可以在内存不足时使用临时磁盘空间,但不能假定查询中的所有内存分配都能溢写。算子状态、字符串值、并行执行所需的中间数据,以及 DuckDB 进程之外的内存占用,都会影响实际表现。可溢写的数据量和触发时机,也取决于 DuckDB 版本、数据形态和查询执行路径。现有材料没有提供版本或执行计划,因此无法准确判断这次失败发生在哪个分配阶段。

报错中的 11.6 GiB/11.6 GiB used 表示这次运行已经用尽内存上限,并不代表输入数据只有或需要 11.6 GiB。12.5GB 和日志中的 GiB 使用不同单位。将上限调到 13GB 后,额外空间可能刚好让查询完成,但不能保证更大的全量数据也能运行。

建议的调优步骤

  1. 核对分组键和数据规模。 确认实际要按 url 还是 html 去重,并估算不同分组键的数量及 HTML 大小分布。分组键基数较高,或少数 HTML 特别大,都可能增加聚合状态所需的内存。
  2. 留出系统余量,再逐步调整。 32GB RAM 不应全部分配给 DuckDB;系统、Python 进程和其他应用也需要内存。可以从当前失败的设置开始,小幅提高 memory_limit,同时观察查询能否完成,以及操作系统是否出现内存压力。memory_limit 是约束和调优参数,并不保证整台机器有相应的可用内存。
  3. 内存压力明显时,降低线程数试跑。 例如将 threads 从 5 降到 2 或 3,比较查询的稳定性和耗时。降低并行度可能减少同时处理的工作量,但也会影响速度,且不保证解决所有内存问题。
  4. 确认临时磁盘空间充足。 需要溢写的操作必须有可用的临时磁盘空间。如果查询运行时磁盘空间不足,只增加内存上限未必能解决问题。
  5. 分别用子集和完整数据验证。 16GB 子集能通过,不代表接近 800GB 的数据也能通过;某个子集失败,也可能与它的分组键分布或 HTML 大小有关。记录 DuckDB 版本、数据量、分组数、线程数、内存上限和耗时,才能比较不同调参结果。

可以先按下面的设置进行受控试跑:

con.execute("SET threads=2")
con.execute("SET memory_limit='13GB'")
con.execute("SET preserve_insertion_order=false")

如果 13GB 仍为系统留有足够余量,且查询运行稳定,可以继续小幅调整。若降低线程数后查询能够完成,再根据耗时和内存占用选择合适的值。无需把 memory_limit 直接设为 32GB。

注意事项

示例 SQL 实际上是按 url 分组,并返回最新日期对应的 html,不是按 html 分组。确认去重方式后,再评估分组状态的规模。现有信息没有提供 DuckDB 版本、唯一 url 数量或 HTML 大小分布,因此无法据此给出普遍适用的精确内存值。

备注:内容仅供参考。