博客/工程

我们如何将新的Prometheus TSDB Grafana Mimir扩展到10亿活跃系列

2022年4月8日16分钟

上周,我们宣布了我们新的开源TSDBGrafana米密尔,它可以让您将指标监控扩展到10亿个活动系列甚至更多。这一消息引起了很多人的兴奋和兴趣,当然也有一些问题。换句话说:真的吗,10亿?

是的,真的!

在Grbob电竞频道afana实验室,我们看到越来越多的客户收集了数亿个活动时间序列,需要一个解决方案来可靠地存储和查询如此大量的数据。我们想确保运行在Mimir上的Grafana Cloud在为我们的客户解锁之前能够轻松地支持这个规模,所以在2021年,我们用10亿个活动序列在Mimir上进行了广泛的负载测试。在测试过程中,我们发现了几个问题,我们逐一调查并解决了这些问题。

这篇博文讲述了我们是如何将Mimir扩展到10亿活跃系列的,实现这一目标的过程,我们面临的挑战,以及在向公众发布Grafana Mimir之前我们所取得的优化结果。

总结

2021年,我们测试了一个Grafana Mimir集群,其中一个租户拥有10亿个活动序列。自从运行这个测试以来,我们已经对Grafana Mimir进行了实质性的改进。对于商业客户,我们能够在更高的范围内进行测试,并明确地打算随后发布我们的结果。

的跨集群查询特性Grafana企业指标这增加了整体的可伸缩性,这超出了本文的范围。

你可以通过跑步来复制我们的结果这个Grafana k6脚本针对一个跨越1500个副本、约7000个CPU核和30 TiB RAM的Grafana Mimir集群。

“10亿活跃系列”是什么意思?

我们提到,我们用一个租户测试了Grafana Mimir,该租户拥有10亿个活动系列,但“活动系列”究竟是什么?

Grafana Mimir使用以下命名法:

  • 一个价值是测量的结果。例如,服务器自启动以来收到了100个HTTP请求。
  • 一个样本是一个值以及测量该值的特定时间。例如,服务器自启动以来收到的100个HTTP请求,在UTC时间2022-03-30 13:00进行测量。
  • 一个度规被测量资源的唯一标识符。它是由带有标签的度规族名称组成的。例如,http_requests_total{pod= " app-1 "}和http_requests_total{pod= " app-2 "}是两个不同于指标家族http_requests_total的指标。
  • 一个时间序列(或“系列”)是度量标识符和与之相关的任意数量的样本的组合。例如,http_requests_total{pod= " app-1 "}带有示例100@13:00:00 UTC, 105@13:00:15 UTC,等等。
  • 一个活动时间序列(或“活动系列”)是在过去20分钟内至少记录了一个样本的度量。

对象存储能够持久保存几乎任意数量的静态数据。如果您每15秒存储一个样本,一年存储100万个指标,那么您将得到20亿个样本,准确地说,是2,102,400,000,000个。虽然这是一个很大的数字,但任何笔记本电脑都可以做到这一点,如果它能够访问对象存储,就更不用说了。

如果你的用户流失率很高,情况也类似。存储中的许多旧时间序列对您的摄入路径或整体管理没有任何影响。只有在查询时,它们才变得短暂重要。

在这篇博文中,我们谈论的是10亿年活跃的系列在一个Grafana Mimir簇中:10亿时间序列已收到样本在过去20分钟内

由于Mimir的用户可配置冗余,默认情况下,每个样本都有三个摄取器。对于10亿个活动序列,将摄取30亿个时间序列,然后在压缩过程中减少到10亿个。

在与其他系统进行比较时,请记住这一点。

用Grafana k6进行负载测试

为了识别和修复在如此高的规模上运行Mimir时的任何可伸缩性和性能问题,我们建立了一个测试集群并使用Grafana转k6在系统上运行持续的负载测试。我们的k6脚本两者都持续地编写系列并运行随机查询,从低基数到高基数,从短到大时间范围,以及范围查询和即时查询。

我们从一个相对较小的负载(2亿个活动系列)开始测试,并在几周内逐步扩大负载,一直扩大到10亿个活动系列。

怎样才能将Grafana Mimir的活性系列扩大到10亿?

运行Mimir需要10亿个活跃系列,这是相当大的规模。

Mimir以3种方式复制接收序列,因此收到了10亿个活跃的系列换算成30亿时间序列在系统中,它们被重复数据删除为10亿活跃的系列还是在仓库里。在这篇博文中显示的仪表板显示了活跃的系列

此外,假设刮取间隔为20秒,集群每秒接收约10亿/ 20秒= 5000万个样本。

将Grafana Mimir扩展到10亿活性系列

提升Grafana Mimir:挑战和改进

在本节中,我们将描述出现的挑战以及我们为提高可伸缩性和性能所做的修复。

我们为改进Mimir所做的工作使我们能够将其扩展到10亿个活动序列,并满足我们在测试集群中定义的SLOs:

  • 99.9%的写请求在10秒内成功。
  • 99.9%的读请求成功,平均查询时间< 2秒。

我们引入的增强功能包括:

散列环

米密尔使用散列环在特定组件的多个副本之间共享工作。例如,集群中的所有接入点副本都会构建一个共享接入点哈希环,该哈希环用于对接收到的时间序列进行分片和复制。

散列环是使用键值存储在Mimir副本之间共享的数据结构。Mimir支持Consul、etcd或成员列表(基于Gossip协议)。

当哈希环存储在Consul或etcd中时,整个哈希环存储在单个键中,并且Mimir副本更新环的速度受到限制,因为比较和交换操作不能在键-值存储中并发运行。这些操作被有效地序列化。

Memberlist克服了这个限制,它是运行Mimir的推荐方法。然而,在如此大规模地测试Mimir时,我们发现成员列表实现存在几个问题,这些问题有效地限制了它的可伸缩性。

我们发现,当运行一个由所有微服务组件的大约1500个副本组成的Mimir集群时,每个副本使用1.6到1.9个CPU内核来通过成员列表传播环更改。这意味着总共大约有2500个CPU核用于使用memberlist传播消息。

我们对相关的代码进行了调查、分析和基准测试,并提出了一组大幅度降低CPU利用率的更改。这些改进包括:

例如,下面的截图显示了当我们推出这些优化之一时,分发器和查询器的CPU利用率降低。整个优化工作持续了几个星期将成员列表哈希环的总CPU利用率降低了90%以上。

Grafana Mimir: hash ring CPU利用率

在如此大规模地对成员列表运行散列环时,我们也经历了进程被随机oomkill的情况。

我们发现,当通过成员列表发送大量消息和/或大量消息时,一些包被损坏。当接收端试图解析这些损坏的包时,它错误地解码了包内容长度,并导致它分配非常大的缓冲区(以gb为单位)来存储解码的包内容。因此,进程将耗尽内存。

我们修复了此错误(https://github.com/grafana/memberlist/pull/1)在一个分叉库中进行测试,然后打开https://github.com/hashicorp/memberlist/pull/239而且https://github.com/hashicorp/memberlist/pull/260上游修复。

压实机

米密尔压实机是负责将摄取者上传的多个TSDB块压缩成一个更大的块的组件。

当在一个拥有数亿个活动系列的租户上运行压缩器时,我们遇到了一些关键问题。

首先,压实机无法跟上压实工作负载。最初,我们运行最初的Grafana Enterprise Metrics压缩器,它能够并发地压缩覆盖不同时间范围的块,但是压缩相同时间范围的块需要非常长的时间。同时,在查询非压缩块时,我们遇到了非常糟糕的性能。

第二,也是最重要的,即使压实机能够完成压实,产生的压实块也会被破坏。原因是TSDB有一些限制,比如64GB的最大索引大小或某些TSDB索引段的4GB的最大大小,因为每个索引段的大小都存储在索引中的32位整数

为了克服这些限制,我们构建了一种新的压缩算法,称为分裂合并压缩器,它支持对相同租户和相同时间范围的块进行分片和水平缩放压缩。

Grafana Mimir:拆分合并压实器图

拆分合并压缩器首先将源块分组到多个组,将每个组压缩,并在输出中产生N个碎片。属于同一个碎片的块被进一步压缩在一起,每个碎片和时间范围都有一个最终的块。

对于每个拆分组和合并碎片,压缩作业可以并行运行,分布在多台机器上。例如,下面的截图显示了150个压缩器副本的CPU利用率,用于处理具有10亿个活动系列的单个租户的压缩:

Grafana Mimir:跨150个压缩副本的CPU利用率

运行分裂合并压缩器,我们已经能够成功地为具有10亿个活动系列的测试租户分片和压缩块。所有的夯实都在12h内完成,以保证没有未夯实的块被查询

要了解更多关于拆分合并夯实器的信息,请参考我们的博客文章Mimir压实机是如何工作的,并浏览Mimir压缩器文档

高聚合度查询

使用10亿个活动系列运行Mimir不仅要能够吸收如此大量的指标,还要能够有效地查询它们。

Mimir使用Prometheus PromQL引擎,它是单线程的。单个查询的执行受到单个CPU核心速度的限制。对于高基数或cpu密集型查询,这可能会对查询性能产生负面影响。

为了克服这一限制,Mimir结合了两种主要策略来跨多个CPU内核和机器扩展单个查询的执行:

  • 按时间对查询进行分片(时间分割)
  • 按一系列分片对查询进行分片(查询分片)

时间分割将远程查询分解为多个查询。如果一个查询跨越了多天,它将被拆分为多个一天的查询,并且每个查询并行运行。

查询分片建立在时间分割的基础上,并将系列数据集分解为更小的片段。这些较小的碎片称为碎片。然后在部分查询中查询每个碎片,这些部分查询分布在多台机器上。然后聚合这些部分查询的结果,并将完整的查询结果返回给客户机。

我们广泛地测试了查询分片。我们观察到,对于高基数和cpu密集型查询,执行时间平均减少了10倍

例如,下面的截图显示了相同的高基数查询,在相同的Mimir集群上运行,启用和不启用查询分片。(每个测试都是用空缓存运行的。)执行时间从37.7秒减少到3.9秒

Grafana Mimir:分片执行时间缩短

我们正在努力支持更多查询类型的分片。目前,在Grafana Cloud上运行的60%的客户查询都是分片的。

Grafana Mimir:在Grafana Cloud客户之间进行分片

要了解更多,请参考Mimir查询分片文档

输入磁盘I/O操作分布

我们在写路径上遇到的最具挑战性的问题之一与TSDB头数据块如何写入磁盘有关。

一个数据块是包含单个时间序列最多120个样本的数据片。块一直保存在内存中,直到满为止,然后将它们写入磁盘。由于一个块不能跨越多个块,所以在每个块的两个小时边界上将块写入磁盘(即使它们没有满)。

在测试环境中,我们使用了20秒的抓取间隔,因此需要40分钟才能填满一个系列的块。在这种情况下,所有非搅拌系列几乎同时填满它们的大块,精确地说,间隔是20秒。

在TSDB中,将块写入磁盘的I/O操作是同步的,因此阻塞给定的序列直到完成。在负载适中的集群中,由于OS内核写缓冲区,这通常不是问题,但在高负载下可能会出现问题。在我们的测试中,每个输入器每40分钟向磁盘写入约7GB,在几秒钟的时间内,所有块几乎同时写入。

下面的截图显示了我们所经历的以秒为单位的写延迟。每隔40分钟,第99个百分位数的写延迟从约10ms上升到20 - 60 ms之间。

Grafana Mimir:写时延

为了缓解这个问题,我们引入的第一个更改是对块的最大大小进行抖动。与其将其硬编码为120个样本,我们使配置抖动成为可能表示为%方差。其想法是,通过引入一种方差,每个块将在稍微不同的时间被填满,有效地将写入的块分散到很长一段时间内。

目前仍在使用的抖动部分解决了这个问题。下面的截图显示,在引入抖动(大约在10:30推出)后,峰值的数量从每2小时3个减少到1个。

Grafana Mimir:介绍抖动

它只是部分地解决了这个问题,因为一个块不能跨越多个块。每两个小时,在每个块时间范围的边界处,强制关闭所有块并将其写入磁盘。

为了进一步缓解这一问题,我们努力将TSDB中的块写入逻辑更改为基于队列的方法,并使用后台进程异步地将块写入磁盘。我们做过这个工作米密尔第然后提出将其上游到普罗米修斯

为了测试在将TSDB块写入磁盘时对写延迟的影响,我们只将异步写队列部署到zone-a中的吸入器,并让旧的代码在zone-b和zone-c中运行。如下截图所示异步写队列将第99百分位峰值从45s减少到3s:

Grafana Mimir:异步写队列仪表板

Prometheus TSDB增强

在将Mimir扩展到10亿个活动系列的过程中,我们遇到了其他一些TSDB性能瓶颈和问题,我们对此进行了调查和修复。

我们在TSDB WAL重放过程中发现了一个竞态条件,它导致从TSDB头压缩的后续块包含无序的块。在内部测试修复后,我们打开了一个上游的公关去修理它。

我们介绍了一些优化标签正则表达式匹配器的技巧在这里而且在这里对于一些常用正则表达式,可减少高达90%的CPU我们在Grafana云中看到的。我们还引入了对压缩块打开和写入并行化的支持在这里而且在这里.我们已经提出了一些变更的升级,目前正在讨论它们是否适合Prometheus用例(例如,在这里)。

最后,我们在Mimir摄取器中禁用了TSDB隔离。TSDB隔离是Mimir中没有使用的一个特性,但由于TSDB隔离锁上的高锁争用,该特性对写延迟产生了严重的负面影响。禁用TSDB隔离在我们的10亿活动系列测试集群中减少了99%的延迟90%。

分段限时发售

运行大型Mimir集群会带来严重的挑战,甚至连推出更改都是如此。当Mimir被部署在microservices模式、入口器和存储网关应该按顺序推出,以确保在推出期间写和读路径上不发生故障。

在具有600个接收器的Mimir集群中,假设每个接收器展开需要5分钟(大部分时间都花在重放WAL上),那么将需要50个小时将更改展开到所有接收器。显然,这是不可扩展的。

为了加速推出,我们使用了杠杆Mimir区域感知复制.我们不是单独处理吞食者,而是按区域分组。在区域级别上,扩展仍然需要按顺序进行。在区域内,可以同时推出任意数量的接收器。

为了协调跨多个区域的推出,我们构建并开放了一个通用的Kubernetes运营商它支持同一区域中的多个StatefulSet吊舱的并发推出,并保证不同区域中的吊舱不会同时推出。米密尔jsonnet还支持使用滚出操作符进行多区域部署。

当使用Grafana展开操作符在多个区域中部署Mimir时,展开时间从副本数量的函数减少为区域数量的函数。这将吸入剂的使用时间从50小时减少到不到30分钟。

为自己利用Grafana Mimir

要了解关于Mimir的更多信息以及如何自己部署它:

如果你有兴趣帮我们把米米尔的规模扩大到下一个数量级,我们正在招聘