github万星Loki 项目 OOM 问题的一次实战


修复 Loki 项目 OOM 问题的一次实战

最近在浏览 GitHub 的时候,Loki 项目中的一个 issue 引起了我的注意:Issue #13277。该问题描述的是在执行查询时由于语法树构建异常,导致了内存泄漏甚至 OOM(Out of Memory)的问题。作为一个热爱挑战的技术人,我决定深入一探究竟。

🔍 Loki 简介

Loki 是由 Grafana 开发的日志聚合系统,与 Prometheus 的理念类似,强调对日志的索引最小化。它支持通过 LogQL 查询语法对日志进行高效查询,广泛应用于云原生监控场景中。

💥 问题背景

Issue #13277 中,用户报告执行了如下类似的查询语句会引发内存异常:

ini 复制代码
{job="my-job"} |= "p" |= "p"

这类查询语句在语法上是合法的,但实际执行时会在 AST(抽象语法树)中出现 子树错误地引用父树 的情况。这种错误会在执行阶段引发无限递归,最终导致程序崩溃或 OOM。

关键代码在 Loki 的 ingester.go 文件中:


🌳 什么是 AST(抽象语法树)?

AST(Abstract Syntax Tree) 是源代码的一种结构化表示,它以树状结构的形式,描述了程序的语法结构。

每一个节点代表了源代码中的一个语言结构(如表达式、语句、操作符等),而树的结构则体现了代码中各部分的嵌套和依赖关系。

📦 例子:LogQL 表达式

例如,LogQL 中的查询语句:

ini 复制代码
{job="my-job"} |= "p" |= "q"

其对应的 AST 结构大致如下:

ini 复制代码
          |=
         /  \
      |=   "q"
     /  \
 {job="..."} "p"

在这个例子中:

  • 根节点是 |=
  • 它的左子树又是一个 |= 操作
  • 最底部是实际的 log selector 和匹配表达式

每一个操作符都被解析成一个节点,树的形状反映了执行的顺序。


🧩 AST 的用途

  1. 代码分析与编译:编译器通过 AST 理解源代码的语义。
  2. 代码优化:比如合并冗余节点、消除无用表达式。
  3. 静态检查:查找代码错误、检测重复逻辑。
  4. 代码转换:如代码格式化、代码生成或转换为其他语言。
  5. 查询执行:Loki、PromQL、SQL 等查询语言都会使用 AST 来构建执行计划。

🧠 为什么 AST 会导致 OOM?

如果构建 AST 时存在"子节点引用父节点 "的情况,会形成循环引用或"递归结构",如:

css 复制代码
node
 └── child
       └── (back to node)

在执行或遍历这棵树时,就会进入无限递归,导致内存持续增长,最终 OOM(内存溢出)。


🧠 AST 与 Equal 判断陷阱

起初我尝试使用 reflect.DeepEqual 来判断 AST 节点之间是否相等,但很快意识到这在 Loki 这样一个对性能极为敏感的项目中是不现实的。DeepEqual 开销大,且在处理循环引用时存在风险。

因此,我需要一种轻量、高效、可控的方式来判断 AST 中是否存在 "子树引用父树" 的问题。

🔧 修复方案

我修改了语法树处理逻辑,在构建语法树时添加了一个父子结构检查。如果某个节点被自己的子节点引用,就会立即报错,避免形成循环依赖。

修复步骤如下:

  1. 添加 AST 节点构建时的检测逻辑,防止自引用。
  2. 避免构建等价但共享引用的子树结构。
  3. 封装简化了逻辑,使其不会影响正常性能路径。

同时,为确保逻辑的正确性,我补充了一系列单元测试,覆盖以下场景:

  • 多次 |= "p" 查询;
  • 子树共享问题;
  • 循环依赖模拟;
  • 查询执行和 AST 结构解析验证。

并增加了部分单测确保不再次出现这种问题:

🧪 压测与验证

为了确保这次修复不会引入新的性能回退,我对 Loki 进行了不同查询组合下的压力测试。最终结果表明:

  • 查询正确性保持不变;
  • 内存占用稳定;
  • 无 OOM 或崩溃现象。

✅ PR 合并

经过多轮 review 和测试后,我的修复方案被合入 Loki 主仓库:

👉 PR 地址:Fix AST cyclic reference causing OOM


✨ 总结

这次修复让我更加深入地理解了 Loki 的查询引擎实现,尤其是 AST 构建的细节。它也提醒我,在做系统底层优化时, "结构正确性" 是性能优化的前提

希望这篇文章能给你带来一些启发。如果你也对开源项目中的性能问题感兴趣,不妨也去贡献一份力量!


相关推荐
无限大68 分钟前
🎯 算法精讲:二分查找(一)—— 基础原理与实现 🔍
后端
Re2758 分钟前
为什么ThreadLocal内存泄露:从原理到实践
后端
玄妙尽在颠倒间9 分钟前
雪花算法:从 64 位到 128 位 —— 超大规模分布式 ID 生成器的设计与实现
后端·算法
谦行22 分钟前
前端视角 Java Web 入门手册 5.10:真实世界 Web 开发—— 单元测试
java·spring boot·后端
SimonKing1 小时前
甩掉手动赋值!MyBatis-Plus 自动填充实战秘籍
java·后端·程序员
小鱼人爱编程1 小时前
当上组长一年里,我保住了俩下属
android·前端·后端
德育处主任2 小时前
『OpenCV-Python』配合 Matplotlib 显示图像
后端·python·opencv
hrrrrb2 小时前
【Spring Boot 快速入门】一、入门
java·spring boot·后端
程序员爱钓鱼2 小时前
Go语言实战案例-深度优先遍历DFS
后端·google·go