TimescaleDB时序数据库初识

注:本文翻译自https://legacy-docs.timescale.com/v1.7/introduction

TimescaleDB是一个开源时间序列数据库,针对快速摄取和复杂查询进行了优化。它说的是“完整的SQL”,因此像传统的关系数据库一样易于使用,并且以以前为NoSQL数据库保留的方式进行扩展。
与这两种选择(关系型与NoSQL)所需要的权衡相比,TimescaleDB为时间序列数据提供了两种方法的最佳选择:

易于使用

  • 完整的SQL接口,支持PostgreSQL本地支持的所有SQL(包括二级索引,非基于时间的聚合,子查询,join,窗口函数)。
  • 连接到任何使用PostgreSQL的客户端或工具,无需更改。
  • 面向时间的特性、API函数和优化。
  • 对数据保留策略的强大支持。

可伸缩性

  • 透明的时间/空间分区,支持向上扩展(单节点)和向外扩展(即将推出)。
  • 高数据写速率(包括批处理提交、内存索引、事务支持、数据回填支持)。
  • 在单个节点上适当大小的块(二维数据分区),以确保即使在大数据大小时也能快速摄取。
  • 跨块和服务器的并行操作。

可靠性

  • 从PostgreSQL设计而来,打包为extension。
  • 经过20多年PostgreSQL研究的可靠基础(包括流复制,备份)。
  • 灵活的管理选项(与现有PostgreSQL生态系统和工具兼容)。

什么是时间序列数据?

时间序列数据是总体上表示系统、流程或行为如何随时间变化的数据。
以时间为中心:数据记录总是有一个时间戳。
仅追加:数据几乎完全是仅追加的(insert)。
最近的:新数据通常是关于最近的时间间隔,我们很少更新或回填关于旧时间间隔的缺失数据。
然而,数据的频率或规律性并不那么重要;它可以每毫秒或每小时收集一次。它也可以定期或不定期收集(例如,当某些事件发生时,而不是在预定义的时间)。
与其他数据(如标准关系“业务”数据)相比,时间序列数据(以及支持它们的数据库)之间的一个关键区别是,对数据的更改是插入,而不是覆盖

时序数据应用场景

监控计算机系统:虚拟机、服务器、容器指标(CPU、空闲内存、网络/磁盘IOPs)、服务和应用程序指标(请求速率、请求延迟)。
金融交易系统:经典证券、新型加密货币、支付、交易事件。
物联网:来自工业机器和设备、可穿戴设备、车辆、物理容器、托盘、智能家居消费设备等传感器的数据。
事件应用:用户/客户交互数据,如点击流、页面浏览量、登录、注册等。
商业智能:跟踪关键指标和业务的整体健康状况。
环境监测:温度、湿度、压力、pH值、花粉计数、空气流量、一氧化碳(CO)、二氧化氮(NO2)、颗粒物(PM10)。

数据模型

作为一个支持完整SQL的关系数据库,TimescaleDB支持灵活的数据模型,可以针对不同的用例进行优化。这使得TimescaleDB与大多数其他时间序列数据库有些不同,后者通常使用“窄表”模型。
具体来说,TimescaleDB既支持宽表模型,也支持窄表模型。在这里,我们将使用一个物联网(IoT)示例讨论这两种模型的不同性能权衡和影响。
想象一下,一个由1000个物联网设备组成的分布式组,旨在以不同的间隔收集环境数据。这些数据可以包括:
Identifiers: device_id, timestamp
Metadata: location_id, dev_type, firmware_version, customer_id
Device metrics: cpu_1m_avg, free_mem, used_mem, net_rssi, net_loss, battery
Sensor metrics: temperature, humidity, pressure, CO, NO2, PM10
例如,您的传入数据可能看起来像这样:

timestamp device_id cpu_1m_avg free_mem temperature location_id dev_type
2017-01-01 01:02:00 abc123 80 500MB 72 335 field
2017-01-01 01:02:23 def456 90 400MB 64 335 roof
2017-01-01 01:02:30 ghi789 120 0MB 56 77 roof
2017-01-01 01:03:12 abc123 80 500MB 72 335 field
2017-01-01 01:03:35 def456 95 350MB 64 335 roof
2017-01-01 01:03:42 ghi789 100 100MB 56 77 roof

现在,让我们看看对这些数据建模的各种方法。

窄表模型

大多数时间序列数据库将以下列方式表示这些数据:
将每个指标表示为一个单独的实体(例如,将cpu_1m_avg和free_mem表示为两个不同的东西)
为该指标存储一系列“时间”、“值”对
将元数据值表示为与该度量/标记集组合相关联的“标记集”
在此模型中,每个度量/标记集组合被认为是包含时间/值对序列的单个“时间序列”。
使用我们上面的例子,这种方法会产生9个不同的“时间序列”,每一个都由一组唯一的标签定义。

1. {name:  cpu_1m_avg,  device_id: abc123,  location_id: 335,  dev_type: field}
2. {name:  cpu_1m_avg,  device_id: def456,  location_id: 335,  dev_type: roof}
3. {name:  cpu_1m_avg,  device_id: ghi789,  location_id:  77,  dev_type: roof}
4. {name:    free_mem,  device_id: abc123,  location_id: 335,  dev_type: field}
5. {name:    free_mem,  device_id: def456,  location_id: 335,  dev_type: roof}
6. {name:    free_mem,  device_id: ghi789,  location_id:  77,  dev_type: roof}
7. {name: temperature,  device_id: abc123,  location_id: 335,  dev_type: field}
8. {name: temperature,  device_id: def456,  location_id: 335,  dev_type: roof}
9. {name: temperature,  device_id: ghi789,  location_id:  77,  dev_type: roof}

这样的时间序列的数量以每个标签的基数的交叉积为尺度,即(# names) × (# device id) × (# location id) ×(设备类型)。一些时间序列数据库随着基数的增加而挣扎,最终限制了可以存储在单个数据库中的设备类型和设备的数量。
TimescaleDB支持窄模型,并且不像其他时间序列数据库那样受到基数限制。如果您独立地收集每个指标,则狭窄的模型是有意义的。它允许您在添加新标记时添加新指标,而不需要正式的模式更改。
但是,如果使用相同的时间戳收集许多指标,那么窄模型的性能就不那么好了,因为它需要为每个指标编写时间戳。这最终导致更高的存储和摄取需求。此外,关联不同指标的查询也更复杂,因为要关联的每个额外指标都需要另一个JOIN。如果您通常同时查询多个指标,那么将它们存储在一个宽表格式中既快又容易,我们将在下一节中介绍这一点。

宽表模型

TimescaleDB很容易支持宽表模型。在这个模型中,跨多个指标的查询更容易,因为它们不需要join。此外,摄取速度更快,因为只需要为多个指标编写一个时间戳。
一个典型的宽表模型将匹配一个典型的数据流,其中在给定的时间戳中收集多个指标:

timestamp device_id cpu_1m_avg free_mem temperature location_id dev_type
2017-01-01 01:02:00 abc123 80 500MB 72 335 field
2017-01-01 01:02:23 def456 90 400MB 64 335 roof
2017-01-01 01:02:30 ghi789 120 0MB 56 77 roof
2017-01-01 01:03:12 abc123 80 500MB 72 335 field
2017-01-01 01:03:35 def456 95 350MB 64 335 roof
2017-01-01 01:03:42 ghi789 100 100MB 56 77 roof

在这里,每一行都是一个新的读数,具有给定时间的一组测量值和元数据。这使我们能够保留数据中的关系,并提出比以前更有趣或探索性的问题。
当然,这不是一种新格式:这是在关系数据库中常见的格式。

关系数据的JOIN操作

TimescaleDB的数据模型与关系数据库还有另一个相似之处:它支持join。具体来说,可以在辅助表中存储额外的元数据,然后在查询时使用该数据。
在我们的示例中,可以有一个单独的位置表,将location_id映射到该位置的其他元数据。例如:

location_id name latitude longitude zip_code region
42 Grand Central Terminal 40.7527° N 73.9772° W 10017 NYC
77 Lobby 7 42.3593° N 71.0935° W 02139 Massachusetts

然后在查询时,通过连接我们的两个表,可以问这样的问题:zip_code 10017中设备的平均free_mem是多少?
如果没有连接,就需要对数据进行非规范化,并将所有元数据存储在每个测量行中。这会导致数据膨胀,并使数据管理变得更加困难。
通过连接,可以独立存储元数据,并且更容易更新映射。
例如,如果我们想要更新location_id 77的“区域”(例如,从“Massachusetts”到“Boston”),我们可以进行此更改,而无需返回并覆盖历史数据。

架构及概念

TimescaleDB是作为PostgreSQL上的扩展实现的,这意味着它在整个PostgreSQL实例中运行。扩展模型允许数据库利用PostgreSQL的许多属性,如可靠性、安全性以及与广泛的第三方工具的连接性。同时,TimescaleDB通过在PostgreSQL的查询规划器、数据模型和执行引擎中添加钩子,充分利用了扩展的高度定制性。
从用户的角度来看,TimescaleDB公开了看似单一的表(称为hypertables),它实际上是保存数据的许多单独表(称为chunks)的抽象或虚拟视图。
chunks是通过将hypertables的数据划分为一个或多个维度来创建的:所有hypertables都按时间间隔进行分区,还可以按设备ID、位置、用户ID等键进行分区。我们有时把这称为“时间和空间”的划分。

Hypertables

与数据交互的主要点是一个超表,它是跨所有空间和时间间隔的单个连续表的抽象,因此可以通过标准SQL查询它。
实际上,与TimescaleDB的所有用户交互都是使用超级表。创建表和索引、修改表、插入数据、选择数据等都可以(也应该)在超级表上执行。[跳转到基本的SQL操作]
超级表由具有列名和类型的标准模式定义,其中至少有一个列指定时间值,一个(可选)列指定额外的分区键。
单个TimescaleDB部署可以存储多个超级表,每个超级表具有不同的模式。
在TimescaleDB中创建超级表需要两个简单的SQL命令:
CREATE TABLE(使用标准SQL语法),然后是SELECT create_hypertable()。
在超级表上自动创建关于时间和分区键的索引,但是也可以创建额外的索引(TimescaleDB支持所有的PostgreSQL索引类型)。

Chunks

在内部,TimescaleDB自动将每个超表分割成块,每个块对应于特定的时间间隔和分区键空间的一个区域(使用散列)。这些分区是不相交的(不重叠的),这有助于查询规划器最小化它在解析查询时必须接触的块集。
每个块都是使用标准数据库表实现的。(在PostgreSQL内部,块实际上是“父”超表的“子表”。)
块的大小合适,确保表索引的所有b树都可以在插入期间驻留在内存中。这避免了在修改这些树中的任意位置时产生震荡。
此外,通过避免过大的块,我们可以在根据自动保留策略删除已删除的数据时避免昂贵的“真空”操作。运行时可以通过简单地删除块(内部表)来执行此类操作,而不是删除单个行。

本地压缩

压缩由TimescaleDB的内置作业调度器框架提供支持。我们利用它来跨超表异步地将单个块从未压缩的基于行的形式转换为压缩的列形式:一旦块足够老,该块将以事务方式从行形式转换为列形式。
使用本机压缩,即使TimescaleDB中的单个超表将以行和列两种形式存储数据,用户也不需要担心这一点:在查询数据时,他们将继续看到标准的基于行的模式。这类似于在解压的列数据上构建视图。
TimescaleDB通过(1)透明地将以标准行格式存储的数据附加到从列格式解压缩的数据中,以及(2)在查询时透明地从选定的行解压缩各个列来实现此功能。
在查询期间,未压缩的块将被正常处理,而来自压缩块的数据将首先被解压缩并在查询时转换为标准行格式,然后再添加或合并到其他数据中。这种方法与您期望从TimescaleDB获得的一切兼容,例如关系连接和分析查询,以及避免处理块的主动约束排除。

单节点vs集群

TimescaleDB在单节点部署和集群部署(开发中)上执行广泛的分区。虽然分区传统上只用于跨多台机器的扩展,但它也允许我们在单台机器上扩展到高写速率(和改进的并行查询)。
TimescaleDB的当前开源版本仅支持单节点部署。值得注意的是,TimescaleDB的单节点版本已经在普通机器上对超过100亿行超级表进行了基准测试,而插入性能没有任何损失。

单节点分区的优势

在单台机器上扩展数据库性能的一个常见问题是内存和磁盘之间的成本/性能权衡。最终,我们的整个数据集将无法装入内存,我们需要将数据和索引写入磁盘。
一旦数据足够大,我们无法在内存中容纳索引的所有页面(例如,b树),那么更新树的随机部分可能涉及从磁盘交换数据。像PostgreSQL这样的数据库为每个表索引保留一个b树(或其他数据结构),以便有效地找到该索引中的值。因此,当您索引更多的列时,问题会变得更加复杂。
但是,由于TimescaleDB创建的每个块本身都存储为一个单独的数据库表,因此它的所有索引只能在这些小得多的表上构建,而不是在一个表示整个数据集的表上构建。因此,如果我们适当地调整这些块的大小,我们可以将最新的表(及其b树)完全放入内存中,并避免这种交换到磁盘的问题,同时保持对多个索引的支持。

你可能感兴趣的:(数据库,时序数据库,数据库)