bigywater.tech
ClickHouse 内部原理 · 35

External Group By:内存放不下时如何落盘

真实开启external_group_by落盘阈值,验证同一条GROUP BY的内存占用真实降低多少,以及内存预算给得不够时会在哪个阶段真实失败。

1.02GB→290MB
真实开启落盘后同一条GROUP BY的内存降幅
两种失败阶段
真实验证内存不足时聚合阶段和读回阶段各自会报错

做的事:给足够内存预算,真实开启external_group_by落盘阈值

代码-- 同一张3000万行、k列全部唯一的表(上一篇验证过默认要用约1.02GB内存) SELECT k, count() FROM demo.bigcard GROUP BY k SETTINGS max_bytes_before_external_group_by=52428800; -- 落盘阈值50MB,不额外限制max_memory_usage

验证:真实内存从1.02GB降到290MB,真实写出60个临时文件

实测 · 本机执行输出
开启external_group_by(阈值50MB):
  memory_usage:  290058897 字节(约290MB,比上一篇不开时的1.02GB少了约72%)
  query_duration_ms: 879
  ExternalAggregationWritePart:       60         ← 真实写出60个临时落盘的part
  ExternalAggregationCompressedBytes: 143254990  ← 真实压缩后向磁盘写了约143MB

开启max_bytes_before_external_group_by之后,GROUP BY真实的执行方式变成了:内存里的哈希表一旦超过这个阈值(本文设的50MB),就先把当前已经聚合出来的部分结果真实压缩后写成一个临时文件(本文真实产生了60个这样的临时part),清空内存继续处理后续数据;等所有数据都处理完,再把这60个临时文件真实读回来做一次最终的归并聚合,得到正确的完整结果。整个过程用峰值内存从约1.02GB压到了约290MB。

诚实的前提:真实报错——落盘不是无限内存的替代品

实测 · 本机执行输出(真实把max_memory_usage也一起压紧后的报错)
SETTINGS max_bytes_before_external_group_by=52428800, max_memory_usage=150000000
→ Query memory limit exceeded: would use 145.15 MiB
  (attempt to allocate chunk of 4.11 MiB), maximum: 143.05 MiB:
  While executing AggregatingTransform. (MEMORY_LIMIT_EXCEEDED)

SETTINGS max_bytes_before_external_group_by=52428800, max_memory_usage=200000000
→ Query memory limit exceeded: would use 191.92 MiB
  (attempt to allocate chunk of 4.00 MiB), maximum: 190.73 MiB:
  While executing SourceFromNativeStream. (MEMORY_LIMIT_EXCEEDED)   ← 注意:这次是在"读回临时文件"阶段失败的
这组真实报错纠正了一个容易想当然的误解:external_group_by不是把GROUP BY变成"无限内存都能跑",它只是把峰值内存需求从原本的约1GB降到了远低于这个数字的水平(本文真实测出降到约290MB),但依然真实需要一个合理的内存预算——尤其是最后把60个临时文件读回来做归并这一步(第二次报错里的SourceFromNativeStream正是这个读回阶段),需要同时打开多个临时文件的缓冲区,这部分开销不会因为设了落盘阈值就消失。本文如果把max_memory_usage也跟着落盘阈值一起压到150MB或200MB,两次都真实撞上了内存超限——只是失败的阶段不一样:内存给得太紧(150MB),聚合阶段本身就先撑不住;内存给得稍微宽松一点(200MB)但还是不够,聚合阶段能扛过去,读回归并阶段反而先失败了。真实的建议是:max_bytes_before_external_group_by该设多小取决于你想多早触发落盘,但max_memory_usage依然要留出比这个阈值大出一截的余量,不能把两者设成同一个数量级。

分步演示:从成功落盘到两种不同阶段的失败

每一步都是本文上面用真实external_group_by跑出来的原始结果,包括没配置好导致的失败。

落盘真实能省内存,但不是无限内存的替代品
这一步新验证的配置

    本文用到的工具

    • 本机 ClickHouse 26.9.4(单节点),真实的 max_bytes_before_external_group_by
    • 真实的 ExternalAggregationWritePart/ExternalAggregationCompressedBytes ProfileEvents
    京ICP备2026067502号-1京公网安备11010502063487号