生态实践

OOM 故障复盘:KaiwuDB 大跨度聚合查询拖垮节点?根因方案一文讲清

原创momopeach2026-10-24
2

内存告警出现后,最显眼的通常是那条查了几十天数据的聚合 SQL。这条 SQL 跑得久,节点内存也在持续上涨,所以很容易得出结论:时间范围太大。

这个判断不一定错,但不够。对滑动窗口查询来说,时间范围只是其中一个因素。窗口有多宽、多久滑动一次、要按多少台设备分别聚合,以及同时跑了几条类似查询,都会改变执行规模。排查时,与其先把内存上限调大,不如先算清楚这条 SQL 到底要做多少组计算。

下面用一个可复现的查询场景说明排查过程。窗口数量按 SQL 参数推导,可以估算工作量,但不能代替现场的内存和耗时记录。

一、先看 SQL 产生了多少个窗口

假设时序表 meter_readings 保存设备电压数据,包含时间戳 ts、设备标识 device_id 和测量值 voltage。报表要查 30 天数据,计算每台设备每小时的平均电压,并每 10 秒更新一个窗口:

SELECT
    device_id,
    COUNT(ts) AS sample_count,
    AVG(voltage) AS avg_voltage
FROM meter_readings
WHERE ts >= '2026-09-01 00:00:00'
  AND ts <  '2026-10-01 00:00:00'
GROUP BY device_id, TIME_WINDOW(ts, '1h', '10s');

在 KaiwuDB 的 TIME_WINDOW 语法里,'1h' 是窗口宽度,'10s' 是相邻窗口起点的间隔。窗口宽度是 1 小时,滑动步长却只有 10 秒,所以相邻窗口高度重叠。同一条采样记录可能被多个窗口使用。

先做一个粗略估算。30 天是 2,592,000 秒,按 1 小时窗口、10 秒步长计算:

窗口数 ≈ floor((查询跨度 - 窗口宽度) / 滑动步长) + 1
       = floor((2,592,000 - 3,600) / 10) + 1
       = 258,841

如果按 1000 台设备分别聚合,并假设它们在这 30 天里都有连续数据,理论上会对应约 2.59 亿个“设备—窗口”组合。这个数字不是数据库实际分配的对象数,也不是内存字节数。数据稀疏程度、设备活跃时间、执行计划和引擎实现都会影响实际开销。它只是提醒我们:10 秒的步长放到 30 天范围里,窗口数量并不小。

查询跨度 窗口宽度 滑动步长 理论窗口数 1000 台设备的组合上界
30 天 1 小时 10 秒 258,841 258,841,000
30 天 1 小时 5 分钟 8,629 8,629,000
30 天 1 小时 10 分钟 4,315 4,315,000

只把步长从 10 秒改成 5 分钟,理论窗口数就少了约 30 倍。前提是业务能接受 5 分钟的采样密度;如果告警依赖秒级变化,这样改会改变查询结果,不能只看资源曲线。

KaiwuDB 时序 SELECT 文档提醒,时间间隔和滑动偏移间隔不宜相差太大,窗口过多可能造成内存不足。这为排查提供了方向,但不能单凭这一条就认定 OOM 一定由窗口过密导致。

二、用查询、会话和节点指标对时间

窗口数量只是估算。接下来要确认目标 SQL 是否真的与内存上涨同时发生。

SHOW CLUSTER QUERIES;

从结果里记下目标 SQL 的 query_id、开始时间、阶段和进度。需要止损时,可按 ID 取消正在运行的查询:

CANCEL QUERY '<query_id>';

取消适合止损,不是根因分析。如果查询即将完成,取消请求发出后系统仍可能让它执行完。

然后把查询时间和节点监控放到同一条时间线上。KaiwuDB 监控平台可以查看节点内存、RSS、Go/CGo 内存、Kmalloc、CPU、SQL 和慢查询指标。若目标查询开始后内存上扬,查询结束或取消后压力回落,两者就有时间相关性。还要看当时是否有其他重查询、批量写入或节点级任务,避免把并发负载都算到单条 SQL 头上。

会话和语句统计能补上另一部分证据。kwdb_internal 的会话信息包含 active_queries、alloc_bytes 和 max_alloc_bytes;节点语句统计记录匿名化 SQL、运行延迟、读取行数和字节数、失败次数等。前者可观察会话分配,后者适合比较语句的历史表现。不过,统计信息不是逐秒的事故录像,最好和监控采样、日志时间戳放在一起看。

执行计划也要检查,但 EXPLAIN ANALYZE 会实际执行查询。已知可能压垮节点的语句,不要为了取计划就在生产上原样重跑。先用 EXPLAIN 看计划;确实需要运行时数据时,再选隔离环境、缩短时间范围或使用代表性数据。

三、先减查询工作量,再考虑参数

按这个顺序处理,先动业务查询,再看系统参数:

顺序 调整项 做法与影响
1 滑动步长 若每 5 分钟看一次小时均值,把 10 秒改为 5 分钟,理论窗口数少约 30 倍;结果更新频率也会降低。
2 分组字段 GROUP BY device_id, region, model 会按组合展开。报表只按设备统计时,可评估是否去掉其他维度,前提是不改变业务口径。
3 分批处理 按设备集合或时间段拆分。时间切分需为跨段窗口留重叠,并按窗口起点合并,避免边界漏算或重复;长期报表可改为定期汇总。
4 并发与并行度 给离线报表设并发上限,避开写入高峰。ts.parallel_degree 控制时序查询并行数,不是用户并发上限,也不保证内存峰值下降;调整前核对 KWDB 3.2.2 文档。

参数能提供边界,但代替不了查询治理:

项目 实际作用 不能解决什么
sql.auto_limit.quantity 限制返回结果行数 不代表聚合中间状态也受此行数限制
--max-sql-memory 设置 SQL 临时数据可使用的最大内存 调大不会减少窗口数,可能放大进程内存压力
alert.mem.threshold 在内存越过阈值时告警 不会自动限流或取消查询

因此先根据证据改窗口、分组或并发,再评估是否需要调整内存参数。

四、改完以后,比较同一条查询

验证时固定数据、时间范围、设备集合、节点规格和并发,只改变一个条件,例如滑动步长。每轮记录查询耗时、峰值内存、读取行数和失败情况。若缓存状态或背景写入差异很大,这组对比就不可靠,应重跑并标注环境变化。

方案 查询跨度 窗口宽度 滑动步长 理论窗口数 实际耗时 / 峰值内存
基线 30 天 1 小时 10 秒 258,841 用同环境实测记录
调整步长 30 天 1 小时 5 分钟 8,629 用同环境实测记录

表里的窗口数可以按参数计算,耗时和内存则要从目标环境采集。优化是否成立,看资源峰值有没有下降,以及输出粒度是否仍满足业务要求。查询变快了,不代表 OOM 风险已经消失。

五、把这次排查变成上线前检查

大跨度查询上线前,先列出时间范围、窗口宽度、滑动步长和分组维度,估算窗口与分组规模。再在受控环境检查计划和资源曲线,给离线任务设并发上限,并准备按 query ID 止损。需要调参数时,先核对 KWDB 3.2.2 的说明,再做单变量对照。

这类 OOM 不能只用“数据太多”解释。查询跨度决定覆盖多久,滑动步长决定窗口数量,分组维度决定每个窗口还要拆成多少组,并发会把多条查询的开销叠加。把这些量算清楚,再对照现场指标,才能判断该改 SQL、控并发,还是继续查执行计划。

-- 坚持原创,转载请注明出处 --

评论(0)

Me