时间序列数据的不变性

导航到

本文最初发表在 The New Stack 上,并在此获得许可转载。

时间序列数据通常量大,需要谨慎处理,以便在近乎实时的情况下产生洞察。

我们不断在时间中移动。你读这句话所花费的时间现在永远成为了过去,无法改变。这导致具有时间维度的数据具有独特之处:它只能朝一个方向前进。 时间序列数据 与其他数据相比,有许多不同的原因。它通常以大量出现,需要谨慎处理才能在近乎实时的情况下产生洞察。这篇博客文章专注于时间序列数据的不可变、不变的本质。

过去已成定局

在我们这个世界上,时间是不可变的,这意味着一旦它成为过去,就无法改变,就像编程中的不可变对象一样。在理想的世界里,数据会反映这一点;你无法更改时间序列数据,就像你不能倒回时钟一样。数据应该反映现实,但有时错误的数据点会被写入数据库。错误的数据点也不会反映现实,在这种情况下删除这些点是有意义的。

删除需要谨慎处理。

  1. 你需要一个数据库,可以删除数据点而不会移动其他数据点。
  2. 你可能还需要通过添加迟到的数据点来编辑历史记录。

需要找到平衡,这样你就不需要不断重写过去,从而使你的数据失去意义,但仍然可以做出必要的改变,以增强时间呈现的上下文。在决定是否进行编辑时,考虑编辑是否使数据更接近反映现实,还是使其更远离现实。

时间永不停息

关于时间的另一件事是它永远不会停止。现在始终在不断地向前移动。因为时间始终在移动,时间序列数据也持续更新。当你想到数据库时,你可能会想到一个用于存储的地方,你可以在那里写入数据,然后在以后读取那些数据而很少更改它们。时间序列数据库始终在变化和更新,因为时间是始终在移动和变化的。

The-Immutability-of-Time-Series-Data

你不能以现实的无穷精度收集数据,但你可以选择对应用程序有意义的精度级别。例如,平均值是最常见和最有用的计算之一。如果你处理的数据不是时间序列,你可能平均计算一个州每平方英里的居民人数。对于时间序列数据,你可能每小时计算进入一栋大楼的人数。这里的区别在于,在每个时刻,上一小时的开始和结束都在变化。以下是一些此类计算的示例代码

from(bucket: "sample")
    |> range(start: 2022-01-01, stop: 2022-01-31)
    |> filter(fn: (r) => r["_measurement"] == "foot_traffic")
    |> aggregateWindow(column: "number_of_people",every: 1h, fn: mean, createEmpty: false)
    |> yield(name: "running mean")

当你做 移动平均 时,你会在指定的时间间隔内计算新的平均值,这样你可以看到你的计算是如何随时间变化的,从而得到一个新的时间序列。你需要考虑你的数据集,知道哪种间隔是有意义的。如果你选择的时间间隔太宽,你会丢失信息和上下文,但如果你选择的时间间隔太精确,你将会有没有数据点的窗口,你的结果将以不合适且没有帮助的方式降到零。

时间的上下文

无论你的数据架构多么接近实时,从数据收集到进入数据库并准备查询之间,总会有微小的延迟。如果你设置了自动查询或处理,这可能会影响你的结果。例如,如果你计算过去五分钟的平均值,过去五分钟内到达数据库的数据可能不包括在边缘上过去五分钟内收集的完整测量值。InfluxDB 允许你通过 任务偏移量 来处理这个问题。

你可以安排任务在包含一些额外缓冲时间的情况下运行计算,以确保所有数据首先到达数据库。这对于保留每个点收集的完整上下文非常重要。Telegraf,InfluxData的开源数据收集代理,也支持偏移量。

有多个原因需要进行数据下采样。有时你无法为完整的原始数据集腾出足够的存储空间。有时平均信号可以穿过噪声,为你提供更有价值的信息。当你进行平均时,一些信息会丢失,一些新信息会被添加。下采样不仅限于平均值。有时,为了保持数据的形状,可能更有意义的是计算一个指标超过设定阈值的次数。

无论你使用哪种下采样方法,你做的每一件事都应该是有意的,这样你就不会丢失后来意识到很重要的数据。如果你在下采样时不小心,并且没有正确处理所有的时间戳,下采样可能会扭曲你的时间序列。InfluxDB旨在使用各种工具和流程来处理下采样,并在InfluxDB Cloud中创建你数据的多个备份副本,这样你就不会意外删除你需要的点。为了尽可能保持上下文,InfluxDB还支持纳秒级精度。以下是在Flux中进行下采样的示例代码:

from(bucket: "sample")
    |> range(start: 2022-01-01, stop: 2022-01-31)
    |> filter(fn: (r) => r["_measurement"] == "foot_traffic")
    |> aggregateWindow(column: "number_of_people",every: 1h, fn: mean, createEmpty: false)
    |> to(bucket: "sample-downsampled")

当然,时间并不是唯一需要考虑的上下文,时间序列数据也不是唯一重要的数据类型。如客户详细信息、位置或使用的机器版本这样的信息不是时间序列数据,但记录它们很重要。幸运的是,InfluxDB 允许你将这些其他类型的数据与时间序列数据结合,以更深入地了解你的系统和流程。

时间是构成我们现实的基本构建块之一,理解其本质有助于你更好地理解世界,并从你的数据中获得更有用的信息。