汇总和预聚合

尽管 TSDB 旨在存储原始的全分辨率数据(只要有空间),但查询宽泛的时间范围或许多标签组合可能会非常麻烦。这样的查询可能要花费很长时间才能完成,或者在最坏的情况下,会用内存不足异常杀死 TSD。从 OpenTSDB 2.4 开始,一组新的 API 允许存储和查询分辨率较低的数据,从而更快地回答此类查询。该页面将概述什么是汇总和预聚合,它们在 TSDB 中的工作方式以及如何最好地使用它们。在 API 部分中查找具体的实现详细信息。

Note

OpenTSDB 本身并不计算和存储汇总或预聚合的数据。有多种计算结果的方法,但是根据规模和精度要求,它们都有优点和缺点。请参阅生成汇总和预聚合部分,讨论如何创建此数据。

Example Data

为了帮助描述较低分辨率的数据,让我们看一些全分辨率(也称为“原始”数据)示例数据。第一个表使用快捷方式标识符定义了时间序列。

Series IDMetricTag 1Tag 2Tag 3
ts1system.if.bytes.outhost=web01colo=lgainterface=eth0
ts2system.if.bytes.outhost=web02colo=lgainterface=eth0
ts3system.if.bytes.outhost=web03colo=sjcinterface=eth0
ts4system.if.bytes.outhost=web04colo=sjcinterface=eth0

请注意,它们都具有相同的metricinterface标签,但具有不同的hostcolo标签。

接下来以 15 分钟为间隔写一些数据:

Series ID12:0012:1512:3012:4513:0013:1513:3013:45
ts114-382-452
ts2728-94 11
ts393-2-16382
ts4 25285-47

请注意,缺少一些数据点。使用这些数据集,让我们先来看一下汇总。

Rollups

在 OpenTSDB 中,“汇总”定义为随时间汇总的“单个”时间序列。也可以称为“基于时间的聚合”。汇总有助于解决查看较长时间 Span 的问题。例如,如果您每 60 秒写入一个数据点并查询一年的数据,则时间序列将返回超过 525k 的单个数据点。画出很多点可能会很混乱。取而代之的是,您可能希望查看较低分辨率的数据,例如 1 小时的数据,此时您只能绘制约 8k 的值。然后,您可以识别异常并向下钻取更高分辨率的数据。

如果您已经使用 OpenTSDB 来查询数据,则您可能熟悉 downsamplers ,它们将每个时间序列汇总为一个较小或较低分辨率的值。汇总本质上是存储在系统中并随意调用的下采样器的结果。每个汇总(或下采样器)都需要两条信息:

  • 间隔 -将多少时间“累积”到新值中。例如,1h表示一小时的数据,或1d表示一天的数据。

  • 汇总函数 -对基础值执行了哪种算术运算以得出新值。例如。 sum添加所有值,或max存储最大值。

Warning

存储汇总时,最好避免使用诸如 averagemediandeviation 之类的功能。当执行进一步的下采样或分组聚合时,这些值变得毫无意义。取而代之的是,始终存储 sumcount 更好,至少可以在查询时计算 average 。有关更多信息,请参见以下部分。

汇总数据点的时间戳应对齐到汇总间隔的顶部。例如。如果汇总间隔为1h,则其中包含 1 个小时的数据,并且应紧靠该小时的顶部。 (由于所有时间戳均以 Unix Epoch 格式(定义为 UTC 时区)编写,因此这将是一个小时的 UTC 时间的开始)。

Rollup Example

给定上述系列,让我们以1h的间隔存储sumcount

Series ID12:0013:00
ts1 SUM105
ts1 COUNT44
ts2 SUM86
ts2 COUNT43
ts3 SUM919
ts3 COUNT44
ts4 SUM916
ts4 COUNT34

请注意,无论时间间隔“存储桶”中的第一个数据点何时出现,所有时间戳都与小时的顶部对齐。还要注意,如果在某个时间间隔内不存在数据点,则计数会降低。

通常,存储汇总时,您应该针对每个时间序列计算并存储MAXMINSUMCOUNT

平均汇总示例

启用汇总功能后,如果您从 OpenTSDB 请求具有avg功能的下采样器,则 TSD 将扫描存储中的SUMCOUNT值。然后,在遍历数据时,它将准确计算平均值。

计数和总和值的时间戳必须匹配。但是,如果缺少总和的期望计数值,则总和将从结果中扣除。从上面的示例集中,现在我们缺少ts2中的计数数据点。

Series ID12:0013:00
ts1 SUM105
ts1 COUNT44
ts2 SUM86
ts2 COUNT4

2h降采样查询的结果avg看起来像这样:

Series ID12:00
ts1 AVG1.875
ts2 AVG2

Pre-Aggregates

虽然汇总有助于进行宽泛的时间 Span 查询,但如果度量标准具有高基数(即给定度量标准的唯一时间序列数),您仍然会遇到范围较小的查询性能问题。在上面的示例中,我们有 4 个 Web 服务器。但是,可以说我们有 10,000 台服务器。获取接口流量的总和或平均值可能很慢。如果用户经常通过像这样的大集合来获取组(或将其视为空间集合),则有必要存储集合并进行查询,取而代之的是获取少得多的数据。

与汇总不同,预聚合只需要一条额外的信息:

  • 汇总函数 -对基础值执行了哪种算术运算以得出新值。例如。 sum添加所有时间序列,或max存储最大时间序列。

在 OpenTSDB 中,预聚合通过特殊标记与其他时间序列区分开。默认标签密钥为_aggregate(可通过tsd.rollups.agg_tag_key配置)。然后,用于生成数据的“聚合函数”以大写形式存储在标签值中。让我们看一个例子:

Pre-Aggregate Example

给定顶部的示例,我们可能希望按 colo(数据中心)查看总接口流量。在这种情况下,我们可以像汇总一样按SUMCOUNT进行汇总。结果将是四个具有元数据的 new 时间序列,例如:

Series IDMetricTag 1Tag 2
ts1'system.if.bytes.outcolo=lga_aggregate=SUM
ts2'system.if.bytes.outcolo=lga_aggregate=COUNT
ts3'system.if.bytes.outcolo=sjc_aggregate=SUM
ts4'system.if.bytes.outcolo=sjc_aggregate=SUM

请注意,这些时间序列已删除hostinterface的标签。这是因为在聚合过程中,hostinterface的多个不同值被包装到了这个新系列中,因此将它们作为标签不再有意义。还要注意,我们在存储的数据中注入了新的_aggregate标签。查询现在可以通过指定_aggregate值来访问此数据。

Note

在启用汇总的情况下,如果您打算使用预聚合,则可能希望通过让 TSDB 自动注入_aggregate=RAW来帮助区分原始数据和预聚合。只需将tsd.rollups.tag_raw设置配置为 true。

现在获取结果数据:

Series ID12:0012:1512:3012:4513:0013:1513:3013:45
ts1'865-16-463
ts2'22222122
ts3'953114849
ts4'12222222

由于我们正在按聚合进行分组(按colo分组),因此对于原始数据集的每个时间戳都有一个值。在这种情况下,我们缩减采样或进行汇总。

Warning

与汇总一样,编写预聚合时,最好避免使用诸如 averagemediandeviation 之类的函数。只需存储 sumcount

Rolled-up Pre-Aggregates

尽管预聚合当然可以帮助实现高基数 Metrics,但用户可能仍想请求较长的时间 Span,但会遇到查询缓慢的问题。幸运的是,您可以采用与原始数据相同的方式汇总预聚合。只需生成预聚合,然后使用上面的信息将其汇总即可。

生成汇总和预聚合

当前,TSD 不会为您生成汇总或预先汇总的数据。这样做的主要原因是 OpenTSDB 旨在处理大量时间序列数据,因此各个 TSD 专注于尽快将其数据存储到存储中。

Problems

由于 TSD(基本上)是 Stateless 的,因此它们可能没有完整的数据集来执行预聚合。例如,我们的 samplests1数据可以写入TSD_A,而ts2则写入TSD_B。如果不从存储中读取数据,任何一方都无法执行适当的分组。我们也不知道应该在什么时候执行预聚合。我们可以 await1 分钟,然后预先汇总数据,但是会错过该分钟之后 Importing 的所有内容。或者,我们可以 await 一个小时,而对预聚合的查询将不会获得最近一小时的数据。如果数据迟到了怎么办?

此外,对于汇总,取决于用户将数据写入 TSD 的方式,对于ts1,我们可能会在TSD_A上收到12:15数据点,但是12:30的值会在TSD_B上到达,因此两个小时都没有所需的数据。时间窗口约束也适用于汇总。

Solutions

使用汇总和预聚合需要进行一些分析并在各种折衷之间进行选择。由于某些 OpenTSDB 用户已经拥有用于计算此类数据的方法,因此我们只需提供用于存储和查询的 API。但是,这里有一些有关如何自行计算的提示。

Batch Processing

其他时间序列数据库通常使用的一种方法是在延迟一段时间后从数据库中读取数据,计算预聚合和汇总,然后将其写入。这是解决问题的最简单方法,并且在小规模时效果很好。但是,仍然存在许多问题:

  • 随着数据的增长,用于生成汇总的查询也将增长到查询负载影响写入和用户查询性能的程度。在 HBase 下启用数据压缩时,OpenTSDB 也会遇到同样的问题。

  • 同样,随着数据的增长,更多的数据意味着批处理时间会更长,并且必须在多个工作人员之间进行分片,这可能会给协调和故障排除带来痛苦。

  • 除非有某种跟踪方法可以触发对旧数据的新批处理,否则可能无法汇总最新或历史数据。

改进批处理的一些方法包括:

  • 从复制的系统中读取,例如如果设置 HBase 复制,则可以让用户查询主系统,并从复制的存储中读取聚合。

  • 从备用 Store 读取。一个示例是将所有数据镜像到另一存储(例如 HDFS),然后对该数据运行批处理作业。

在 TSD 上排队

一些数据库使用的另一种选择是将所有数据排队在进程中的内存中,并在经过配置的时间窗口后写入结果。但是因为 TSD 是 Stateless 的,并且通常用户将负载平衡器放在其 TSD 的前面,所以单个 TSD 可能无法获得要计算的汇总或预汇总的完整图片(如上所述)。为了使此方法起作用,上游收集器必须将计算所需的所有数据路由到特定的 TSD。这不是一项艰巨的任务,但是面临的问题包括:

  • 有足够的 RAM 或磁盘空间来为每个 TSD 本地缓存数据。

  • 如果 TSD 进程终止,则将丢失用于聚合的数据,或者必须从存储中引导数据。

  • 每当进行聚合计算时,原始数据的整体写入吞吐量都会受到影响。

  • 您仍然有后期/历史数据问题。

  • 由于 TSDB 是基于 JVM 的,因此将所有这些数据保留在 RAM 中然后运行 GC 会很麻烦。很多。 (缓冲到磁盘比较好,但是会遇到 IO 问题)

通常,对 Writer 排队是个坏主意。避免痛苦。

Stream Processing

处理汇总和预聚合的更好方法是将数据路由到流处理系统中,在该系统中可以近实时地处理数据并将其写入 TSD。它类似于“在 TSD 上排队”选项,但使用多种流处理框架之一(Storm,Flink,Spark 等)来处理消息路由和内存中存储。然后,您只需编写一些代码来计算聚合并在经过窗口后将数据吐出。

这是许多下一代监视解决方案(例如 Yahoo!的解决方案)使用的解决方案。雅虎正在努力为需要大规模监视的其他人开放其流处理系统的源代码,并将其巧妙地插入 TSDB。

虽然流处理更好,但是您仍然需要解决以下问题:

  • 足够的资源供流工作者完成工作。

  • 死流工作者需要从存储进行引导。

  • 后期/历史数据必须得到处理。

Share

如果您有用于计算聚合的工作代码,请与 OpenTSDB 组共享。如果您的解决方案是开源的,我们也许可以将其合并到 OpenTSDB 生态系统中。

Configuration

对于 Opentsdb 2.4,汇总配置由 opentsdb.conf 键tsd.rollups.config引用。该键的值必须是带引号的 JSON 字符串(不含换行符),或者最好是包含配置的 JSON 文件的路径。文件名必须以_结尾,如rollup_config.json一样。

JSON 配置应如下所示:

{
      "aggregationIds": {
              "sum": 0,
              "count": 1,
              "min": 2,
              "max": 3
      },
      "intervals": [{
              "table": "tsdb",
              "preAggregationTable": "tsdb-preagg",
              "interval": "1m",
              "rowSpan": "1h",
              "defaultInterval": true
      }, {
              "table": "tsdb-rollup-1h",
              "preAggregationTable": "tsdb-rollup-preagg-1h",
              "interval": "1h",
              "rowSpan": "1d"
      }]
}

两个顶级字段包括:

  • aggregationIds **-OpenTSDB 聚合函数名称到用于压缩存储的数字标识符的 Map。

  • intervals -一个或多个间隔定义的列表,其中包含表名称和间隔定义。

aggregationIds

聚合 idMap 用于通过在每种类型的汇总数据之前添加数字 ID 来减少存储量,而不用说明完整的聚合功能。例如。如果我们为每列加上COUNT:前缀,即每个 ID(可压缩的值)可以保存的 6 个字节。

ID 必须是 0 到 127 之间的整数。这意味着每个间隔最多可以存储 128 个不同的汇总。Map 中只能提供每个数值的一个 ID,并且只能提供每种类型的一个聚合函数。如果函数名称未 Map 到 OpenTSDB 支持的聚合函数,则在启动时将引发异常。同样,必须至少提供一种聚合才能启动 TSD。

Warning

开始写入数据后,将无法更改聚合 ID。如果更改 Map,则可能会返回不正确的数据,或者查询和写入可能会失败。您将来总是可以添加功能,但是永远都不能更改 Map。

intervals

每个时间间隔对象都定义了表路由,用于汇总和预聚合数据应写入和查询的位置。间隔有两种类型:

  • Default -这是由"defaultInterval":true定义的默认* raw 数据 OpenTSDB 表。对于现有安装,这将是tsdb表或tsd.storage.hbase.data_table中定义的内容。间隔和 Span 将被忽略,默认为 OpenTSDB 1 小时行宽,并以给定的分辨率和时间戳存储数据。每个 TSD 和配置一次只能配置一个

  • 汇总间隔 -带有"defaultInterval":false的任何间隔或未设置默认间隔。这些是汇总表,其中的值被捕捉到间隔边界。

应定义以下字段:

NameData TypeRequiredDescriptionExample
tableStringRequired非预聚集数据的基础表或汇总表。对于默认表,此表应为tsdb或表已写入的原始数据。对于汇总数据,它必须是与原始数据不同的表。tsdb-rollup-1h
preAggregationTableStringRequired预写和(可选)汇总数据的表应写入其中。该表可能与table值相同。tsdb-rollup-preagg-1h
intervalStringRequired数据点之间的预期间隔为<interval><units>格式。例如。如果汇总每小时计算一次,则间隔应为1h。如果每 10 分钟计算一次,请将其设置为10m。对于默认表,此值将被忽略。1h
rowSpanStringRequired存储中每一行的宽度。该值必须大于interval,并定义每行中可容纳的interval的数量。例如。如果间隔为1hrowSpan1d,则每行有 24 个值。1d
defaultIntervalBooleanOptional原始的非汇总数据的配置间隔是否为默认间隔。true

在存储中,类似于原始数据,汇总被写入,因为每一行都有一个基本时间戳,并且每个数据点都是相对于该基本时间的偏移量。每个偏移量都是基准时间的增量,而不是实际偏移量。例如,如果一行存储 1 天 1 小时数据,则最多会有 24 个偏移量。偏移0将 Map 到该行的午夜,偏移 5 将 Map 到上午 6 点。因为汇总偏移量是在 14 位上编码的,所以如果一行中存储的间隔太多而无法容纳 14 位,则启动 TSD 时将引发错误。

Warning

将数据写入 TSD 之后,请 不要 更改间隔宽度或汇总间隔的行 Span。这将导致垃圾数据并可能导致查询失败。