← 返回文章列表
产品动态 4 分钟阅读 炬鲸团队

用 SQL 查日志:OBSERVE 统一查询引擎实践

介绍 OBSERVE 统一查询引擎如何用类 SQL 语法同时检索日志、指标与链路,给出常用查询语句、全文检索陷阱与性能优化建议,帮助运维快速定位线上问题。

为什么用 SQL 而不是 grep

运维排查日志最常见的动作是 grep -E "pattern" app.log,接着 tail -fawk '{print $NF}'sort | uniq -c。这套命令在单机、单文件时代够用,一旦日志集中到平台、分散在几十个索引和几百个服务里,grep 就开始吃力:跨服务关联难、聚合统计慢、字段过滤得靠正则硬扛。更麻烦的是,很多团队指标放在 Prometheus、日志放在 Elasticsearch、链路放在 Jaeger,三套系统的查询语言互不相通,排查一个跨服务问题要在三个界面之间反复切换。

OBSERVE 的统一查询引擎把日志、指标、链路三者的查询接口收敛成一套类 SQL 语法。你不用记三种 DSL,一个 SELECT ... FROM logs WHERE ... 就能同时完成过滤、聚合、排序,结果还可以直接下钻到原始日志。语法基于标准 SQL 扩展了时间窗口函数,学过 SQL 的人十分钟就能上手,团队里不需要专门的查询语言专家。

常用查询语句

查最近 5 分钟返回 502 最多的上游服务:

SELECT upstream, COUNT(*) AS cnt
FROM logs
WHERE status = 502 AND time > now() - 5m
GROUP BY upstream
ORDER BY cnt DESC
LIMIT 10

按分钟统计 QPS 与 P99 延迟:

SELECT time_bucket(1m, ts) AS bucket,
       COUNT(*) AS qps,
       quantile(0.99, latency_ms) AS p99
FROM logs
WHERE service = 'order-api'
GROUP BY bucket
ORDER BY bucket

统计各服务 15 分钟内的错误率,找出最先出问题的那个:

SELECT service,
       SUM(CASE WHEN status >= 500 THEN 1 ELSE 0 END) / COUNT(*) AS err_rate
FROM logs
WHERE time > now() - 15m
GROUP BY service
HAVING err_rate > 0.01
ORDER BY err_rate DESC

用窗口函数看每个服务的慢请求随时间的变化趋势:

SELECT service, time_bucket(1m, ts) AS bucket,
       RANK() OVER (PARTITION BY service ORDER BY COUNT(*) DESC) AS rk
FROM logs
WHERE latency_ms > 500 AND time > now() - 1h
GROUP BY service, bucket

日志全文检索有个坑:WHERE message LIKE '%timeout%' 会退化成全表扫描。改用 WHERE message MATCH 'timeout' 走倒排索引,百万级日志下能快一到两个数量级。数字字段也别包引号,WHERE latency_ms > 500 走数值索引,WHERE latency_ms > '500' 反而退化成字符串比较。

三类数据如何打通

链路数据的 trace_id 是日志与指标的关联键。排查流程变成:先按 trace_id 查出完整调用链,找到耗时最长的那个 Span,再 WHERE trace_id = 'xxx' 拉出该请求跨服务产生的全部日志,最后叠加对应时间段的指标曲线,看 CPU、GC、连接池有没有同步抖动。三个动作在同一套 SQL 里完成,不用在三个页面间来回切换。这是统一查询最大的价值:把「先猜是哪一类数据的问题」这一步省掉,直接在同一份语句里把三类数据串起来看。

引擎为什么能快

统一查询不是简单地在三个引擎上面套一层语法糖。底层日志走列式存储 + 倒排索引,指标走预聚合 + 降采样,链路走图索引;同一份 SQL 会被自动拆解、路由到对应的存储,再合并结果。这样 SELECT COUNT(*) FROM logs WHERE status=502 不用把整页日志都读出来计数,而是直接读索引统计。这也是为什么它能扛住大索引下的交互式查询,而不是像 grep 一样线性扫描。

性能优化建议

  • 高频过滤字段建索引,WHERE 尽量落在索引列上,避免全扫描。执行前看查询计划,出现 full scan 提示就先改写。
  • 对高基数字段(如 user_id)做 GROUP BY 时先加时间窗口或 LIMIT,防止聚合内存暴涨;引擎对超限聚合会自动截断并提示。
  • 生产环境给查询设超时和结果行数上限,别让一条慢查询拖垮整个集群,建议默认超时 30 秒、结果 1 万行。
  • 能用 time_bucket 就不要用 GROUP BY ts,后者会产生海量小分组,速度慢很多。

一个完整的排查片段

一次用户反馈「下单偶尔卡 3 秒」。先查错误率没异常,接着按 service 分组的 P99 里发现 order-api 在几个时间点冲到 3s;再用 WHERE service='order-api' AND latency_ms > 2000 抽出慢请求的 trace_id;最后 WHERE trace_id IN (...) 拉出完整调用链,定位到支付回调里有个同步查账的 SQL 走了全表。整个过程没有切过页面,也没有换过查询语言,从看到告警到定位根因大约十分钟。

最后提醒一句:把排查时反复用到的查询存成收藏或固定到大盘,下次同类问题直接点开,不用现写 SQL。团队里沉淀的标准排查查询越多,新人接手故障时上手越快,这也是统一查询平台用得越久越顺手的原因。