Databend SQL nom Parser 性能优化

nom 简介
nom 是 Rust 生态中非常受欢迎的解析框架:性能优秀、组合灵活,并且能很好地利用 Rust 的类型系统。Databend 在 SQL 表达式和语句解析上大量使用 nom,开发体验不错,可读性也高。
不过,组合式 parser 容易在不经意间埋下性能隐患——尤其是当多个分支结构相似、再加上递归嵌套时,回溯成本会指数级膨胀。
问题案例:function 嵌套拖慢解析
一次用户反馈里,我们收到了一条解析 20 分钟都跑不完的 SQL。火焰图清楚地显示:函数解析反复尝试、层层回溯。
当时的函数解析写法大致如下:
这段代码对阅读者非常友好,但也有两个特征:
所有分支都以
function(...)起手;深度优先的
alt每次匹配失败都会回溯到下一个分支。
在上面这种五层嵌套、每层模式数量为 5 的场景里,最常见的 “纯函数调用” 分支放在最后,实际要尝试 5^5 = 3125 次才能命中。复杂度飙升到 O(m^n),性能立刻崩掉。
优化方案一:折叠相似分支,避免指数级回溯
问题根源是“结构高度相似 + 递归 + 深度优先回溯”。我们把多个分支折叠成一次解析,再根据匹配到的后缀来决定具体的函数类型,相当于把流程变成了“先整体匹配,再分类处理”的广度优先思路:
一次解析完成所有结构匹配,再根据分支类型装配结果,直接消除了指数级回溯。该优化落地后,原先需要几十分钟的 SQL 如今只要几十毫秒。
优化方案二:高频 Token 解析直接 hard code
function 回溯问题解决后,我们又在表达式解析上抓到了第二个热点:Binary/Unary/Json Operator 等简单 token 被频繁命中,而原先的实现是 alt + value + rule! 的组合。这个组合每次调用都要:
构造闭包;
包装错误信息;
构建返回值;
再进入下一层 parser。
对于几乎只包含单个 token 的场景,直接手写匹配会快得多。Databend 的 expr 有 49 个分支,热度非常高,把这些分支 hard code 掉收益极可观。以下是 json_op 替换前后的实现:
Benchmark
hard code 版本能带来约 10 倍的收益。
ASM 分析:硬件视角的差异
借助 cargo asm -p databend-common-ast --lib databend_common_ast::parser::expr::json_op,我们对比了两种实现的汇编:
经验总结
合并结构相似的 parser,避免深度优先 + 回溯导致的指数级爆炸。
高频、简单 token 的解析直接 hard code,省掉闭包、错误包装等额外成本。
及时查看火焰图,能发现异常深的解析栈。
必要时对热点路径做汇编级分析,更容易验证优化方向。
这两项优化落地后,Databend 的函数调用解析从分钟级降到毫秒级,表达式解析也获得了量级上的性能提升。
关于 Databend
Databend 是一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式湖仓。期待您的关注,一起探索云原生数仓解决方案,打造新一代开源 Data Cloud。
👨💻 Databend Cloud:databend.cn
📖 Databend 文档:docs.databend.cn
💻 Wechat:Databend
✨ GitHub:github.com/databendlab…
版权声明: 本文为 InfoQ 作者【Databend】的原创文章。
原文链接:【http://xie.infoq.cn/article/354f19122f19af4c04f111370】。文章转载请联系作者。







评论