系统表第2部分:我们是如何让它变得更快的
作者:叶春春 / 开发者
2024年10月31日
导航至
在第一篇博客中,我们介绍了系统表及其如何用于检查您的集群。在这篇后续文章中,我们将解释一些提高系统表查询速度的技术。
7. 问题
在2024年8月之前,即使在Cloud Dedicated中对系统表进行查询,尤其是对system.tables
、system.partitions
和system.partitions
进行查询,通常需要很长时间才能运行,即使应用了过滤器。在某些情况下,查询超时且未返回任何结果。
8. 为什么它这么慢
为了理解慢查询的原因,让我们首先看看系统表数据是如何生成的。
以下是InfluxDB 3.0架构的简化概述(来自博客InfluxDB 3.0:系统架构)。中心组件是1**目录**,它存储有关数据库、表、列和文件详情(如文件大小、位置、创建时间等)的元数据。在右侧,我们有**查询器**,其中包含系统表并执行查询。 图1:InfluxDB 3.0架构
目录中的元数据按照以下简化数据模型组织(见图2):图2:简化目录元数据数据模型
InfluxDB 目录作为中央集群协调器,出于性能和稳定性考虑,提供了一个不允许分析式查询的受限接口。因此,查询器必须多次调用目录以收集填充系统表所需的信息。图3展示了查询类似于 system.partitions
的系统表时的典型数据流。
system.partitions
表有一个列显示分区总大小(兆字节)。为了计算这个值,查询器首先获取数据库中的所有表,然后检索每个表的所有分区,并查找每个分区关联的每个 Parquet 文件。之后,它将文件大小相加,并将总大小转换为兆字节。这个过程涉及到查询器和目录之间的多个请求,因为它需要多次数据检索,如图3所示。图3:查询器和目录之间的数据流
8.1 性能问题
以前,在查询系统表如 system.tables
、system.partitions
或 system.compactor
时,目录会扫描所有元数据并以 gRPC 格式将其发送给查询器。然后查询器将响应转换为 Arrow record batches,并使用 DataFusion 运行查询,该查询会对数据进行过滤。
这意味着以下查询
SELECT * FROM system.partitions;
SELECT * FROM system.partitions WHERE table_name = 'foo';
这两个查询在目录和查询器中做了相同的 工作量,尽管第二个查询有一个过滤条件。系统扫描了所有内容,在查询器级别应用过滤条件,导致不必要的开销和缓慢的性能。
9. 解决方案
9.1 谓词下推
谓词是用于查询中过滤数据的条件,例如 table_name = ‘foo’
或 age > 20
。我们实现了 谓词下推,这是一种常见的数据库优化技术,将某些过滤器(谓词)尽可能移至数据源附近(即在目录内),如图4所示。这一变化减少了查询器获取和传输的数据量,从而减轻了查询器的负担。图4:谓词下推
在我们实现谓词下推之前,查询如 SELECT * FROM system.partitions WHERE table_name = 'foo'
会获取和格式化目录中所有表的分区信息,而查询引擎会立即丢弃除 foo
以外的所有内容。
谓词下推后,查询器避免了在查询执行过程中过滤掉的信息的分区信息的获取和格式化。
9.2 多个谓词下推
除了支持单个谓词下推外,我们还扩展了支持处理 多个过滤器。第9.1节中的简单示例可能是明显的,但在实际系统中,用户可以提供由 AND
、OR
、IN
等连接的任意谓词,确定要下推哪些谓词是非平凡的。例如,考虑以下查询
SELECT *
FROM system.partitions
WHERE (table_name = 'foo' OR table_name = 'bar')
AND (partition_key = '2024-10|device-101' OR partition_key = '2024-09|device-101')
我们使用了 DataFusion 的 LiteralGuarantee::analyze 来解析和简化查询谓词,然后再将其下推到目录中。这种方法为我们节省了大量工程时间和精力。向 DataFusion 社区致敬,他们使这一切变得如此简单易用!
当前实现支持对多个谓词进行下推以用于过滤器,例如将 table_name
与 partition_key
或将 table_name
与 partition_id
结合,进一步减少查询器处理的数据量。
9.3 并发数据获取
以前,查询器会对目录进行顺序的API调用,其中每个请求都必须等待前一个请求完成才能进行。这增加了显著的延迟,尤其是在查询大型数据集时。
我们通过启用 并发API请求 来改进这一点,允许查询器同时发出多个请求。这大大减少了收集所有必要元数据所需的时间。
10. 性能改进
以下是使用过滤器 WHERE table_name
时查询系统表的查询速度提高了多少
- system.tables: 17% 更快
- system.partitions: 65% 更快
- system.compactor: 60% 更快
这些改进基于一个有100张表,超过200个分区,以及3000多个Parquet文件的数据库。如果你的数据库有更多的表、分区或Parquet文件,你将看到使用过滤查询的性能提升更为显著。
此外,对于多个过滤器的平均查询延迟约为20 ms,例如
WHERE table_name = '...' AND partition_key = '...'
WHERE table_name = '...' AND partition_id = ...
11. 总结
在这篇文章中,我们解释了如何通过实现谓词下推、优化多个过滤器的使用以及启用并发数据获取来提高系统表查询的性能。这些变化大大减少了查询时间,尤其是对于具有大量元数据的数据库。
通过这些优化,您可以更有效地检索相关系统数据,并消除长时间调试等待。
参考文献
- 请注意,在我们的实际部署中,有多个缓存层在图1中没有反映。