sql不适合计算qps峰值,需用时间窗口切片、瞬时速率推导和按秒对齐的滑动计数;group by time_bucket比floor(unix_timestamp())更可靠,因自动处理时区与边界;count(*)非qps,须用窗口函数如max(qps) over (order by sec rows between 2 preceding and current row)求连续秒内峰值;sql无法原生实现irate语义,易因空秒或精度丢失导致偏差;结果须与监控系统对齐,注意过滤干扰请求。

直接结论:SQL本身不适合计算QPS峰值,尤其在高并发场景下。你真正需要的不是SUM或AVG,而是时间窗口切片 + 瞬时速率推导 + 按秒对齐的滑动计数。
为什么GROUP BY time_bucket比GROUP BY FLOOR(UNIX_TIMESTAMP())更可靠
高并发请求日志(如Nginx access log导入ClickHouse/PostgreSQL)中,原始时间戳精度常达毫秒甚至微秒,但QPS必须按“秒”对齐才有业务意义。用FLOOR(UNIX_TIMESTAMP(ts))看似简单,实际会因时区、夏令时、浮点截断导致同一秒内数据被拆到两个桶里。
-
time_bucket('1s', ts)(TimescaleDB / ClickHouse)是原子操作,自动处理时区偏移和边界对齐 - PostgreSQL需配合
date_trunc('second', ts),不能用to_char(ts, 'YYYY-MM-DD HH24:MI:SS')——后者是字符串转换,无法走索引 - 若原始表无
ts字段而只有log_time VARCHAR,先用to_timestamp(log_time, 'YYYY-MM-DD HH24:MI:SS.US')转类型,否则隐式转换会让GROUP BY失效
count(*)不是QPS,max(count(*)) OVER (ORDER BY sec ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)才是峰值线索
单靠GROUP BY sec只能得到每秒请求数,但QPS峰值本质是“连续多秒中的最大瞬时吞吐”,比如突发流量持续3秒,每秒分别是980、1020、990——真实峰值应取1020,而非平均值1000。
- 直接
SELECT max(qps) FROM (SELECT count(*) AS qps FROM logs GROUP BY date_trunc('second', ts))会丢失时间连续性,把凌晨3点的100和大促零点的10000混在一起比 - 必须用窗口函数做滑动窗口聚合:
max(qps) OVER (ORDER BY sec ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)表示“当前秒及前两秒内的最高QPS” - ClickHouse中优先用
runningAccumulate或neighbor()替代标准窗口函数,性能高3–5倍
别在SQL里算irate,那是Prometheus的事
看到irate(http_requests_total[1m])这种写法就该警觉:SQL没有原生的“基于最近两个采样点推导瞬时速率”能力。强行模拟会导致严重偏差:
- 假设日志里某秒有1200条记录,但实际是前200ms集中到达(真实峰值1200÷0.2=6000 QPS),SQL按秒分组只看到1200
- 若用
LAG()取上一秒计数再相减,遇到空秒(如第5秒无日志)就会得到负值或NULL,irate能自动跳过不完整窗口,SQL做不到 - 真正要复现
irate语义,得先用物化视图预计算每100ms的计数,再用窗口函数找相邻非空桶的差值——成本远超收益
最易被忽略的一点:QPS峰值计算结果必须和监控系统对齐。如果你用SQL算出的峰值是10500,而Prometheus里max(irate(http_requests_total[1s])[1h:])显示9800,大概率是SQL没处理好采样对齐或丢弃了重试请求——这时候该查的不是聚合函数,而是WHERE条件里是否漏掉了status NOT IN (499, 503)这类干扰项。











