我们毕业的时候洛基在去年发布的GA版本中,有超过137位贡献者已经为这个项目做出了超过1000个贡献。我们也加入了洛基的阵容Grafana云在它被证明为我们的运维集群内部稳定之后,每月存储40TB和5000亿日志行。
然而,有一个持久的问题不断浮出水面,特别是对于用Go编写应用程序的开发人员来说:The正则表达式包很慢.
我们的目标一直是洛基不仅性价比高。我们也希望它是高效的,所以我们决定解决这个问题。在这篇博文中,我们将剖析速度问题背后的根本原因,以及Grafana Labs团队如何找到一个解决方案来提高Loki的整体性能。bob电竞频道
这个问题
众所周知,Go中的正则表达式包是相当慢的。这个包被普罗米修斯,灭霸,以及基本上所有使用围棋和正则表达式的人使用。对于那些使用Loki的人来说,这是特别有问题的,因为如果你必须搜索你的日志,并为每一行应用正则表达式,一次一行,那么Loki的性能将不太理想。
这是因为Go团队使用线性时间创建了正则表达式算法,这意味着输入的数据量和执行函数所需的时间之间存在直接关联。因此,摄取的数据越多,应用程序处理工作负载所需的时间就越长。
但这并非没有充分的理由。Go中的正则表达式的设计以安全为优先考虑。该算法专门阻止一种称为正则表达式拒绝服务(ReDoS)攻击的安全攻击,在这种攻击中,攻击者提供了一个需要过多时间来处理的正则表达式。这反过来又会导致程序过载,速度变慢或完全关闭。
Go团队最初期望更多的工程师将这个包用于生产服务,在这种用例中,regexp性能已经足够,安全特性是必要的。但是对于那些没有在类似实例中实现它的人来说,这个包可能会很慢。Go的作者一直都意识到这个问题(和抱怨),但是权衡总是有意义的:安全性优于速度。
解决方案
我们的解决方案是受到包本身的启发。有一个语法子包它允许用户解析正则表达式并访问抽象语法树。
这个包利用了简化,它通过将正则表达式替换为另一个表达式组合来分解正则表达式。自始至终,原始的表达都是完整的。
考虑到这一概念,我们应用了自己的概念正则表达式简化在Loki中使用字节比较。这是可能的,因为我们只是进行过滤,不需要捕获匹配项。
让我们举一个洛基中过滤表达式的基本例子,比如{foo="bar"} |~ "(DEBUG|WARNING)"
.
如果你在Go中使用regex包,这将运行缓慢,因为你在日志中匹配每一行日志,如果有调试或警告标签。对于一个查询,洛基可以遍历数百万行日志。
在Loki中,字节比较将扫描日志以查看它是否包含“调试”或如果包含“警告”。我们实际上不运行正则表达式。事实上,我们绕过了它。
怎么做?首先,我们使用语法包解析用户正则表达式,并查看是否有不同的方法来运行正则表达式。(正则表达式可能非常复杂,所以我们不想自己进行解析。幸运的是,Go工程师让我们很容易做到这一点!)
如果没有改进,那么洛基将使用正常的正则表达式。但如果我们能改进它,避免正则表达式,洛基会运行字节。包含
,运行起来比正则表达式简单。
所以在上面的例子中,因为过滤后的表达式可以简化,所以我们不需要运行代价高昂的正则表达式。相反,我们只运行两个字节。包含在字符串上。
字节比较不仅提高了原始包的性能并降低了成本。我们没有破坏Go团队实现的原始安全措施,因为我们不需要替换Loki中的regex包。
结果
运行24小时查询范围时{namespace="loki-dev"} |~ "foo|bar"
,在引入regexp简化后,结果从11.5s变成了1.5s洛基v1.4.0.
一些regexp在行过滤时速度较慢,特别是备用操作,例如' {app="foo"} |~ "err|panic" '。我深入研究了golang regexp/syntax包,并找到了简化行过滤的方法。结果不言自明:pic.twitter.com/19R0r5QLVR
——西里尔·托维纳(@Kuqd)2020年2月25日
总的来说,对于我们看到的各种用例指标的改进范围从5倍到300倍以上!
接下来是什么
我们将来要做的一件事就是把所有的工作都提取出来公关并创建一个可以被整个生态系统使用的软件包。我们希望让任何正在运行利用过滤且不需要捕获值的项目的人也可以访问该工作。
我们还提出了我们的愿景洛基的未来在GrafanaCONline 2020,强调了团队的一些长期目标和非目标,我们想要添加什么特性,以及对LogQL查询语言的积极讨论和计划。你可以观看谈话的录音在这里.