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