如何存储时间序列数据


22

我有一个时间序列数据集(如果我错了,请纠正我),该数据集具有许多关联值。

一个示例是对汽车进行建模并在旅途中跟踪其各种属性。例如:

时间戳| 速度 行驶距离| 温度| 等等

什么是存储此数据的最佳方法,以便Web应用程序可以有效地查询字段以查找最大值,最小值并绘制随时间变化的每个数据集?

我开始分析数据转储并缓存结果,这样就永远不必存储它们了。但是,经过一番尝试之后,由于内存限制,此解决方案似乎无法长期扩展,如果要清除缓存,则需要重新解析并重新缓存所有数据。

另外,假设每秒跟踪数据的可能性极低,可能超过10小时,那么通常建议通过每N秒采样一次来截断数据集吗?

Answers:


31

实际上,没有任何一种“最佳方法”来存储时间序列数据,并且坦白地说,这取决于许多因素。但是,我将主要关注两个因素,它们是:

(1)这个项目对您进行优化架构的认真程度如何?

(2)您的查询访问模式实际上是什么样的?

考虑到这些问题,让我们讨论一些模式选项。

平面桌

使用平面表的选项与问题(1)的关系更大,在这里,如果这不是一个严肃的或大规模的项目,那么您会发现不去过多考虑架构就容易得多,并且只需使用一个平板,如:

CREATE flat_table(
  trip_id integer,
  tstamp timestamptz,
  speed float,
  distance float,
  temperature float,
  ,...);

仅在这是一个很小的项目并且不需要您花费大量时间的情况下,我很少推荐本课程。

尺寸和事实

因此,如果您已经解决了问题(1)的障碍,并且想要一个更好的性能模式,那么这是要考虑的第一个选择。它包括一些基本的规范化,但从测得的“事实”量中提取“维”量。

本质上,您需要一个表格来记录有关行程的信息,

CREATE trips(
  trip_id integer,
  other_info text);

和一个记录时间戳的表格,

CREATE tstamps(
  tstamp_id integer,
  tstamp timestamptz);

最后是所有测得的事实,其中包含对维表的外键引用(即meas_facts(trip_id)reference trips(trip_id)meas_facts(tstamp_id)reference tstamps(tstamp_id)

CREATE meas_facts(
  trip_id integer,
  tstamp_id integer,
  speed float,
  distance float,
  temperature float,
  ,...);

乍一看似乎并没有什么用,但是例如,如果您有成千上万的并发行程,则它们可能每秒都进行一次测量,第二次进行。在这种情况下,您每次每次旅行都必须重新记录时间戳,而不仅仅是在tstamps表中使用单个条目。

用例:如果您要记录许多并发行程,并且您不介意同时访问所有测量类型,则这种情况会很好。

由于Postgres会按行读取数据,因此,例如,在speed给定时间范围内的任何时间,您都必须从meas_facts表中读取整行,这肯定会减慢查询速度,即使您使用的数据集是不太大,那么您甚至不会注意到差异。

分解实测事实

为了进一步扩展最后一部分,您可以将测量结果分成单独的表格,例如,我将在表格中显示速度和距离:

CREATE speed_facts(
  trip_id integer,
  tstamp_id integer,
  speed float);

CREATE distance_facts(
  trip_id integer,
  tstamp_id integer,
  distance float);

当然,您可以看到如何将其扩展到其他度量。

用例:因此,这不会为您提供极大的查询速度,在查询一种测量类型时,可能只会线性增加速度。这是因为当您要查找有关速度的信息时,只需要从speed_facts表中读取行,而无需在表的一行中显示所有多余的,不需要的信息meas_facts

因此,您只需要读取有关一种测量类型的大量数据,就可以获得一些好处。提议的情况是每隔一秒钟间隔10个小时的数据,那么您只需要读取36,000行,因此您永远不会从中真正受益匪浅。但是,如果要查看大约10个小时内的5,000次旅行的速度测量数据,那么现在您正在看的是读取1.8亿行。只要您一次只需要访问一种或两种测量类型,这种查询速度的线性增加就可以带来一些好处。

数组/ HStore /&TOAST

您可能不需要担心这部分,但是我知道在某些情况下确实很重要。如果您需要访问的巨大数额的时间序列数据,而你知道你需要访问一个巨大的块中的所有的话,你可以使用一个结构,它会利用的TOAST表,基本上存储在大数据,压缩段。只要您的目标是访问所有数据,就可以更快地访问数据。

一种示例实现可以是

CREATE uber_table(
  trip_id integer,
  tstart timestamptz,
  speed float[],
  distance float[],
  temperature float[],
  ,...);

在此表中,tstart它将存储数组中第一个条目的时间戳,每个后续条目将是下一秒的读数值。这要求您在一块应用程序软件中管理每个数组值的相关时间戳。

另一种可能性是

CREATE uber_table(
  trip_id integer,
  speed hstore,
  distance hstore,
  temperature hstore,
  ,...);

在其中您将测量值添加为(时间戳,测量)的(键,值)对。

用例:仅当您确定访问模式需要为批量访问模式时,这才是更适合PostgreSQL的人实施的实现。

结论?

哇,这比我预期的要长得多,对不起。:)

从本质上讲,有很多选择,但是使用第二个或第三个选项可能会为您带来最大的收益,因为它们更适合一般情况。

PS:您最初的问题暗示您将在收集所有数据后批量加载数据。如果要将数据流式传输到PostgreSQL实例中,则需要做一些进一步的工作来处理数据摄取和查询工作量,但是我们将把它再留一遍。;)


哇,谢谢你的详细回答,克里斯!我会考虑使用选项2或3
guest82

祝你好运!
克里斯

哇,如果可以的话,我会投票1000次。感谢您的详细解释。
kikocorreoso

1

它的2019年和这个问题值得更新的答案。

  • 无论哪种方法最好,我都会让您进行基准测试,但这是一种方法。
  • 使用名为timescaledb的数据库扩展
  • 这是安装在标准PostgreSQL上的扩展,可以很好地处理在存储时间序列时遇到的一些问题

以您的示例为例,首先在PostgreSQL中创建一个简单表

步骤1

CREATE TABLE IF NOT EXISTS trip (
    ts TIMESTAMPTZ NOT NULL PRIMARY KEY,
    speed REAL NOT NULL,
    distance REAL NOT NULL,
    temperature REAL NOT NULL
) 

第2步

  • 这变成所谓的Hypertable的在timescaledb的世界。
  • 简而言之,它是一个大表,该表被连续细分为某个时间间隔的较小表,例如每天将其中的每个小型表称为块
  • 尽管可以在查询中包含或排除该迷你表,但在运行查询时该迷你表并不明显

    选择create_hypertable('trip','ts',chunk_time_interval => interval'1 hour',if_not_exists => TRUE);

  • 上面我们要做的是获取行程表,每小时在“ ts”列的基础上将其划分为迷你表。如果您将时间戳记从10:00到10:59添加到1个块中,但是11:00将被插入到一个新块中,这将无限期进行。

  • 如果您不想无限地存储数据,还可以使用以下命令删除3个月以上的数据块

    SELECT drop_chunks(间隔'3个月','旅行');

  • 您还可以使用类似以下的查询来获取到现在为止创建的所有块的列表

    SELECT chunk_table,table_bytes,index_bytes,total_bytes FROM chunk_relation_size('trip');

  • 这将为您提供迄今为止创建的所有小型表的列表,并且如果您希望从该列表中进行查询,则可以对最后一个小型表运行查询

  • 您可以优化查询以包括,排除块或仅对最后N个块进行操作,依此类推

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.