内存告警出现后,最显眼的通常是那条查了几十天数据的聚合 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)