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 构建的细节。它也提醒我,在做系统底层优化时, "结构正确性" 是性能优化的前提

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


相关推荐
掘金码甲哥4 小时前
两张大图一次性讲清楚k8s调度器工作原理
后端
间彧4 小时前
Stream flatMap详解与应用实战
后端
间彧5 小时前
Java Stream流两大实战陷阱:并行流Parallel误用、List转Map时重复键异常
后端
tan180°6 小时前
Linux网络UDP(10)
linux·网络·后端·udp·1024程序员节
正经教主7 小时前
【Trae+AI】和Trae学习搭建App_03:后端API开发原理与实践(已了解相关知识的可跳过)
后端·express
shepherd1267 小时前
破局延时任务(上):为什么选择Spring Boot + DelayQueue来自研分布式延时队列组件?
java·spring boot·后端·1024程序员节
开心-开心急了7 小时前
Flask入门教程——李辉 第5章: 数据库 关键知识梳理
笔记·后端·python·flask·1024程序员节
雨夜之寂7 小时前
第一章-第三节-Java开发环境配置
java·后端
郑清7 小时前
Spring AI Alibaba 10分钟快速入门
java·人工智能·后端·ai·1024程序员节·springaialibaba
zl9798997 小时前
SpringBoot-Web开发之数据响应
java·spring boot·后端