本文介绍如何确定慢速 PromQL 查询、如何理解查询成本以及如何优化这些查询,以便它们执行得更快并消耗更少的 CPU 和 RAM
不幸的是,仅仅通过查看它是不可能确定 PromQL 查询是慢还是快的,PromQL 查询性能取决于查询本身以外的以下因素:
如果查询选择数百万个time series,那么在执行过程中可能需要 GB 的 RAM、大量的磁盘 IOPS 和大量的 CPU 时间
该数字等于所选时间范围内所有匹配时间序列的原始样本总和。样本数量还取决于存储在数据库中的样本之间的间隔(在 Prometheus 世界中也称为 scrape_interval)——间隔越短意味着样本数量越多
更长的时间范围意味着必须从数据库中读取更多的数据。请注意,如果旧序列随着时间的推移被新序列替换(又名序列流失率),则较长的时间范围可能会增加要扫描的样本数量和匹配时间序列的数量。
更多series通常意味着匹配系列的查找速度较慢。请注意,如果旧series由于高流失率而经常被新series替换,则active time series的数量可能远小于给定时间范围内的序列总数。
例如,如果它在选定的时间范围内匹配几个具有多达几千个样本的时间序列,则看起来简单的查询可能会在眨眼之间执行。另一方面,如果在选定的时间范围内匹配数十万个具有数百亿个样本的时间序列,则相同的向上查询可能需要数十秒,并且可能会消耗 GB 的 RAM。
查询性能还取决于查询本身。例如,不同的功能有不同的表现。 MetricsQL 和 PromQL 具有以下函数类型:
标签操作函数,例如label_replace、label_join和label_set。这些是最轻量级的函数,尽管它们在查询中使用时看起来很复杂。
变换函数,例如abs、round和time。这些大多是快速函数。有一些例外,
例如:histogram_quantile、prometheus_buckets和其他与直方图相关的函数,它们在执行过程中需要更多的时间和资源。
聚合函数,例如sum、count、avg、min、max。这些函数也大多是轻量级的,除了一些非平凡的聚合函数,如count_values、quantile或histogram。
汇总函数,例如rate、increase、min_over_time和quantile_over_time。这些是最昂贵的功能。Rollup 函数处理存储在数据库中的原始样本,而其余函数类型处理从 rollup 函数返回的计算结果。每个汇总函数都接受方括号中的后视窗口(在 Prometheus 世界中也称为范围向量)。例如,rate(http_requests_total[1h])。后视窗口——1h在这个例子中——改变了每个匹配的时间序列查询需要处理的原始样本的数量。更大的窗口意味着要处理更多的原始样本。
PromQL 和 MetricsQL 有一个额外的特性,如果使用不当可能会显着降低查询速度——子查询。
如果需要频繁使用子查询,请建立record rules
VictoriaMetrics提供了以下附加选项,这些选项可能有助于确定慢速查询:
当它选择大量的时间序列时。
当它选择大量原始样本时。
当查询中使用的单个标签过滤器匹配大量时间序列时。
在查询中的系列选择器上使用count和last_over_time函数的组合。例如,如果查询看起来像:
avg(rate(http_requests_total[5m])) by (job)
,那么以下查询将返回初始查询需要选择的时间序列数:
count(last_over_time(http_requests_total[5m]))
因此,在高流失率count(last_over_time(…))下,
他们可以选择和处理比图表上显示的更多的时间序列。在这种情况下,您应该将选定的时间范围放在方括号中,以获得查询需要选择的时间序列的真实数量。例如,必须使用以下查询来选择在过去 24 小时内构建图形的原始查询涉及的系列数:
count(last_over_time(http_requests_total[24h]))
如果count(last_over_time(…))查询返回的值小于几千,那么初始查询在所选时间序列的数量上没有瓶颈。如果返回的数量超过几万,那你就麻烦了。
尝试以下方法来减少所选时间序列的数量:
在查询中的系列选择器上使用sum和count_over_time函数的组合。例如,如果查询看起来像:
avg(rate(http_requests_total[5m])) by (job)
,则以下查询将返回初始查询需要选择的样本数:
sum(count_over_time(http_requests_total[5m]))
如果您需要确定在给定时间范围内在 Grafana 中构建图形需要选择的原始样本数量,则必须将方括号中的 lookbehind 窗口更改为给定时间范围。例如,以下查询返回过去 24 小时构建图形所需的原始样本数:
sum(count_over_time(http_requests_total[24h]))
如果sum(count_over_time(…))查询返回的值小于几百万,则初始查询在要扫描的原始样本数量方面没有瓶颈。如果返回的数字超过亿,那么你可能需要通过以下技术优化查询:
以下 MetricsQL 查询可用于确定查询需要处理的样本数:
range_last(
running_sum(
sum(
count_over_time(process_resident_memory_bytes[30m])
)
)
)
如果返回的数字超过数亿,那么是时候通过减少方括号中的后视窗口来优化查询了。step另一个优化选项是通过增加选项来指示 Grafana 增加查询 arg Resolution。
在时间序列的高流失率下,查询性能可能会降低。高流失率会增加数据库中时间序列的总数。这也会增加查询中使用的每个标签过滤器的匹配时间序列数。这反过来又减慢了寻找匹配时间序列的过程。
这是频繁部署的 Kubernetes 监控的常见问题。每个新部署都会增加系列流失率,因为部署对象(pods、端点、服务)公开的指标可能具有与部署相关的标签的新值,例如 deployment id、pod id、image id 等。
解决这个问题的方法是使用Prometheus 重新标记规则删除或重写这些标签,因此它们不会在每次部署时频繁更改。可以通过页面检测具有最频繁更改值的标签/api/v1/status/tsdb。
PromQL 允许使用多个系列选择器编写查询。例如,foo + bar包含两个系列选择器 —foo和bar。这些系列选择器相互独立评估。这意味着优化过程应该针对查询中的每个系列选择器独立执行。
当二元运算符左侧和右侧的系列选择器返回具有不同标签集的不同数量的时间序列时,复杂查询存在一个常见的性能问题。PromQL 和 MetricsQL 剥离指标名称,然后将二元运算分别应用于左侧和右侧系列上具有相同标签集的系列,而其他系列则被丢弃。有关详细信息,请参阅这些文档。这意味着在二元运算符的另一侧没有相应匹配对的系列只会浪费计算资源(CPU 时间、RAM、磁盘 IO)。解决方法是在二元运算符两边的级数选择器中添加公共标签过滤器。这将减少所选时间序列的数量,从而减少资源使用。例如,查询foo{instance=“x”} + bar可以优化为foo{instance=“x”} + bar{instance=“x”},因此bar将搜索范围缩小到仅带有{instance=“x”}标签的时间序列。