InfluxDB 内部原理 101 - 第二部分
作者:Ryan Betts / 产品, 开发者
2017 年 11 月 27 日
导航至
- 查询路径:从 InfluxDB 读取数据
- 为查询索引点
- 关于 TSI(磁盘索引)的说明
- 执行查询
- 关于 IFQL 的说明
- DELETE 和 DROP - 从 InfluxDB 删除数据
- 更新点
简介
本系列第一部分介绍了 InfluxDB 的写入路径:数据库如何持久化和组织写入数据库的数据。本部分(第二部分)介绍了与数据库的另一个主要交互:查询已持久化的数据。请注意,第一部分还定义了本文中使用的 InfluxDB 术语(tagset
、fieldset
、measurement
、series
),这将对新读者有所帮助。
InfluxDB 使用名为 influxql
的 SQL 方言进行查询。关于该语言有很多文档,以及使用 influxql
进行不同查询任务的指南。本文重点介绍查询引擎的工作原理,而不是语言本身的语义。
时间序列应用程序倾向于两种查询模式。查询要么窗口化并生成每个窗口的聚合(将数据窗口化为一分钟间隔,并计算每分钟的平均值)。或者,查询搜索特定点(通常是 last()
或系列中最新的点)。两种查询模式都通过应用于一组维度的条件来过滤数据库中的点;例如,所有 region = us-east
或 measurement = 'cpu'
的数据。在 InfluxDB 中,这些维度存储为 tagsets
。
最后,在我们深入了解更多细节之前,重要的是要注意 influxql
支持 selection
和 projection
运算符,但不支持传统的关联 joins
。优化 InfluxDB 中的查询性能需要找到每个系列的初始点,然后利用列式存储来有效地扫描该初始点之后的一系列点。在灵活的 schema-on-write tagsets
与星型模式中预定义的维度表之间进行选择,是 InfluxDB 与传统 SQL 列式 OLAP 数据库之间更令人感兴趣的差异之一。
为查询索引点
第一部分介绍了传入写入填充的不同数据结构,以实现持久性和紧凑的长期存储。写入填充了一个额外的数据结构,以提高查询效率:索引。InfluxDB 自动维护索引,以提高按 tagsets
过滤的效率。
索引维护 measurement name
到 field keys
的映射、measurement name
到 series ids
(内部系列标识符)的映射、measurement name
到 tag keys
到 tag value
到 series id
的映射,以及 series id
到 shards
的映射。索引(截至 1.4 版本)还维护 series
和 measurements
的草图,用于快速基数估计。您可以在 GitHub 上阅读索引实现以获取更多详细信息。
有很多不同的映射需要考虑和理解。就我个人而言,我发现将索引视为倒排索引(也称为倒排索引)更容易,也更符合概念,倒排索引将标签键/值对映射到系列键列表。这种轻微的抽象概括了索引的主要目的:在查询时,根据 influxql
WHERE 谓词中的 tagset
过滤器,有效地识别需要扫描的所有系列。
关于 TSI(磁盘索引)的说明
当前默认索引存储在内存中。这允许快速查找以进行查询计划。但是,这也意味着高基数数据(包含大量唯一 tagsets
的数据)需要大量内存来索引。这就是为什么我们建议用户将 tagsets
用于较低基数的维度数据,并将未索引的 field values
用于高基数数据。
我们正在开发一种新的索引结构,时间序列索引 (TSI),现在作为可选预览发布。TSI 将索引存储在 SSD 上,与默认的内存索引相比,允许更高基数的数据集。
解析和计划
在描述了索引之后,就可以解释内部工作流程,该工作流程运行以解析、计划和执行示例 influxql
查询。查询引擎
- 确定查询类型(带有表达式的查询或原始数据查询)
- 确定然后分离时间范围和用于过滤数据的条件表达式
- 使用 measurement 列表和时间范围确定它需要访问哪些 shard
- 展开任何通配符
- 验证查询在语义上是否正确
- 指示存储引擎为每个 shard 创建迭代器
- 并合并 shard 迭代器输出,对数据执行任何后处理
示例查询:select user, system from cpu where time > now() - 1h and host = 'serverA'
数据库接收查询并解析出访问的 measurements、返回的字段、分组时间间隔、过滤器谓词和其他 influxql
查询组件。您可以在 influxdata/influxql GitHub 存储库中阅读 SELECT 语句的 AST 结构。
解析后,查询引擎确定生成答案所需的 series。在本例中,查询引擎使用索引查找作为 cpu measurement
一部分的所有 series
。然后,它使用索引查找具有 tag key, tag value
对 host, serverA 的所有 series
。这些集合的交集提供了需要扫描的 series
。查询中的时间范围 now() - 1h 将扫描限制为涵盖过去一小时的 shard groups
。
查询引擎为每个 shard 的每个 series 实例化一个迭代器。这些迭代器是嵌套的,形成一个树。迭代器树是自下而上执行的,读取、过滤和合并数据以生成最终结果集。
1.4 版本的 EXPLAIN
和 EXPLAIN ANALYZE
语句提供了有关创建的迭代器和作为查询执行一部分解码的 TSM 块的统计信息。在InfluxDB 1.4 新功能博客文章中提供了示例输出。
关于 IFQL 的说明
schema-on-write、tagsets
的自动索引和类似 SQL 的语法的结合,产生了一个系统,该系统使新手能够快速高效地工作,感觉熟悉,并且只需最少的设置即可开始使用。
但是,预先分配范围狭窄的迭代器意味着高基数查询和生成大量组的查询计划成本很高。最坏情况下,迭代器结构可能会消耗 GB 的 RAM。其次,计划期间的迭代器分配和其他实现细节使多查询资源管理变得困难。最后,虽然类似 SQL 的语法非常适合简单查询,但对于更复杂的分析,它变得很麻烦。时间序列查询通常是应用于过滤流分组的函数集。使用带有高级 SQL 分区和 over 子句的 select-project-join 逻辑表达这些查询需要经验丰富的 SQL 程序员,并且不再对初学者友好。
我们最近宣布了一个原型查询语言 IFQL,以探索解决这些问题的方法:更便宜的计划、更好的资源管理以及更轻松地表达复杂查询。
DELETE 和 DROP:从 InfluxDB 删除数据
InfluxDB 支持保留策略,以对数据强制执行生存时间策略。这始终是从数据库定期删除点的首选方法。但是,应用程序有时会将错误的数据写入数据库。需要删除该数据才能恢复正常运行。在这些情况下,可以使用 DELETE
和 DROP
删除不需要的点。
DELETE 和 DROP 语句通过查询层而不是写入层进行处理。这允许 DELETE 和 DROP 重用 influxql
的选择和表达式功能。
从列式数据库中删除数据成本很高。InfluxDB 将磁盘上的数据组织成 series 单列值的不可变运行。删除操作需要撤消大量针对点子集的工作。
在 InfluxDB 中,从数据库中删除一行会生成一个墓碑。墓碑包括一个 series key
和已删除范围的最小和最大时间。这允许对主要删除用例进行非常紧凑的表达:删除时间 t1 和 t2 之间无效 series 的所有数据。
当收集到足够的墓碑时,TSM 数据将重新压缩到新的不可变文件中,其中删除了已删除的数据并删除了墓碑记录。在查询时,将检查墓碑以避免处理标记为已删除的数据。
在过去六个月中,已经投入大量工作来使墓碑管理、基于累积删除的压缩以及删除后索引更新变得正确和高效。
更新点
InfluxDB 不支持 UPDATE
语句。但是,在现有时间戳处重新插入完全限定的 series key
会将旧点的 field value
替换为新的 field value
。
结论
希望这篇文章增加了您对 InfluxDB 的心智模型。它讨论了四个关键概念
series
和tagsets
已编入索引以进行查询计划。- 查询计划使用索引来识别要扫描的 series。
- 查询计划生成并执行迭代器树。
- DELETE 和 DROP 语句是
influxql
的一部分,并导致墓碑注释已删除的数据。