优化 InfluxDB 在高速度数据下的性能
作者:Sam Dillard / 产品, 用例, 开发者
2019年10月24日
导航至
无论您是以纳秒级分辨率跟踪大脑中的神经元行为,监控支持关键业务应用的大型且不断变化的基础设施,跟踪自动驾驶汽车的实时行为,还是监控股票及其交易所中股票价格的高频变化,对高分辨率数据的业务需求都变得至关重要。为了满足这一需求,需要一个高速数据存储和查询引擎。
InfluxDB 的设计目标正是如此。本文的目的在于帮助您理解如何最大限度地利用它。
前言几点说明
- 要从本文中获益,前提是需要对 InfluxDB 数据模型(数据库、保留策略、测量、标签、字段)有基本的了解 – 参考文档、教程以及下文的入门指南。
- 为了结构清晰,下面介绍的优化将按照典型的时间序列数据管道的顺序编写。也就是说,第一个优化将位于最上游(数据源/客户端),最后一个优化将关于数据写入后如何使用(查询)。
- 最后,就本文的范围而言,我假设使用 TSI 索引。
优化写入
首先,您需要确定如何检测您正在监控的资产。InfluxData 是一个非常流行的开源数据收集代理 Telegraf 的创建者和维护者。然而,许多用户仍然使用其他检测方法,例如客户端库。
如果可以,请使用 Telegraf。Telegraf 不仅提供了开箱即用的、由插件驱动的与 200 多种服务的集成,而且它是由与 InfluxDB 相同的工程团队设计的,使其成为 InfluxDB 最佳实践的框架。
Telegraf 为您处理以下事项
- 标签和时间戳排序 – 为了更快地扫描文件块,InfluxDB 按字典顺序对键进行排序,然后按降序对时间戳进行排序。为了让 InfluxDB 不必担心这些,请在上游按字典顺序对标签进行排序,并按升序时间顺序(最早的在前)写入批次。
- 重试 – 如果写入目标(InfluxDB、负载均衡器、消息队列等)备份或关闭,客户端(在本例中为 Telegraf)可能会写入失败。重要的是,假设您需要每次写入的数据完整性,您的客户端可以重试失败的写入,直到成功为止。
- 可修改的批处理大小
- 抖动
- 有助于分散工作负载,从而分散数据节点上的资源利用率。
- 如果您有许多写入器直接写入 InfluxDB,这可以减少您达到打开连接限制的机会。
- 预处理
- 聚合(平均值、最小值、最大值、计数等)
- 转换 – 即,字段到标签;标签到字段(当您需要对架构进行此类更改以向前发展时,这很重要(请参阅架构部分)。
数据模型、架构和行协议
鉴于本文的结构,您可能会认为架构部分应该放在首位。然而,InfluxDB 具有基于其接收的写入的自自定义架构。您不需要提前定义架构。这使其非常灵活,但也可能导致“恶意”用户或标签在未来引起性能问题。InfluxDB 组织数据的方式由行协议的记录(或“行”)的写入方式定义。
行协议简要入门
数据模型
- 数据库 – 顶层数据结构;在单个 Influx 实例中可以有多个
- 保留策略 – 此策略中包含的数据在被清除之前将保留多长时间。
- 测量 – 类似指标的逻辑分组。示例:CPU 使用率可以通过多种方式测量。用于测量 CPU 的 Telegraf 插件将 usage_user、usage_system、usage_idle 等指标都写入到一个名为 cpu 的测量中。测量在功能上可以被认为类似于关系数据库中的表。
- 标签集(元数据)
- 一个或多个键值对的组合,充当您正在写入的测量和关联字段(实际指标)的元数据。
- 标签键用于描述正在监控的“资产”,例如,主机名、IP、区域、股票代码、交易所。
- 标签用于 GROUP'ing;您希望在单个查询中区分资产的数据。
- 字段集(指标)
- 一个或多个键值对的组合,用于描述资产的实际可测量项,例如,cpu_usage_user、memory_free、disk_available、water_pressure、turbine_rpm、open_price 等。
格式化行协议
在许多数据格式(包括其他 TSDB 的数据格式)中,数据记录的结构使得只有一个值(或标量)与其描述符元数据(定义从中采样值的“资产”的键)一起写入。这是必需的,因为指标键信息编码在元数据中。行协议避免了这个问题,并允许您为每个记录写入多个(许多)值 – 在这种情况下,每个“行”。让我们看一些比较
Graphite 格式
host.metadata1.metadata2.<measureable>.<specific field> value=<value>
或者,更具体地说
指标 1:0001.us-west-1.a.cpu.usage_system value=45.0
指标 2:0001.us-west-2.a.cpu.usage_user value=35.0
在上面的示例中,value 之前的所有内容都是描述 value 来自何处以及它实际是什么的键。这意味着,为了知道每个 value 代表什么,您必须再次写入大部分元数据,才能简单地写入关于同一资产的另一个 CPU 指标。
Prometheus 格式
measureable_metric,metadata1,metadata2, gauge=<value>
或者,更具体地说
cpu_usage_system,region=us-west-1,host=0001,az=a gauge=1.5
cpu_usage_user,region=us-west-1,host=0001,az=a gauge=4.5
虽然这种格式分离了元数据的各个部分(在 InfluxDB 中有所帮助 – 稍后讨论),但它与 Graphite 格式存在相同的问题。
Influx 行协议格式
measureable,metadata1,metadata2 <specific_field>=<value>
在 Influx 术语中
measurement,tag,tag field,field,field,field,field
更具体地说
cpu,region=us-west-1,host=0001,az=a usage_user=35.0,usage_system=45.0,usage_guest=0.0,usage_guest_nice=0.0,usage_nice=10.0,usage_steal=5.0
请注意标点符号;空格分隔标签和字段。
这样做有几个优点
- 通过网络传输的负载变得更小。这有助于提高效率......和预算。
- 数据更易于探索,如下所示。
注意:拥有数据库、测量、标签和字段的多个实例可以更轻松地在 Chronograf Data Explorer 中查看您的数据,并且通常使运行此探索的元查询更有效率。
- 写入磁盘的速度稍快。
如何将您的数据转换为这种格式?
- Telegraf 有许多不同的解析器来帮助您实现这一点。
- Telegraf 还有一个 透视插件,可以与即将推出的 聚合器插件结合使用,该插件将在 v1.13 中提供。
- 前面提到的客户端库,专为创建和批处理序列而设计。
优化读取
重要的是要理解,InfluxDB 的底层文件格式(时间结构合并树,由 InfluxData 创建)是列式的,而不是您可能更熟悉的(例如,Postgres)关系数据库使用的面向行的文件格式。
- 缓存重复响应数据
- 时间序列数据库不仅针对时间序列写入进行了优化,而且还针对时间序列查询进行了优化。时间范围限制、选择特定字段以及尽可能按序列进行 WHERE 过滤
- 列式存储期望受到列式风格查询的冲击;即
SELECT
(或过滤)特定列的查询。这与常见的 [SQL](https://influxdb.org.cn/glossary/sql/) 查询SELECT * FROM
形成对比。 - 过滤
- 按时间过滤是查询最明显的过滤形式,但许多刚接触时间序列数据库世界的用户却忽略了这一点。正如我之前所说,TSDB 做的两个设计假设是 a) 数据是高速的,b) 数据越旧,其重要性就越低。鉴于此,有一个假设是查询将处理时间片段,并且通常这些片段是最近的。
- 按指标/资产/序列过滤同样重要。时间序列数据库(列式存储)不像关系数据库那样针对从每个“列”中读取数据进行优化。这意味着重要的是 – 如果可能 –
SELECT
仅选择您想要数据的字段,并使用WHERE
过滤您的标签,以便查询引擎仅扫描包含您想要信息的序列键。
- 列式存储期望受到列式风格查询的冲击;即
- 与返回原始、未处理数据的查询相比,聚合的计算速度要快得多。
- 批处理函数需要在处理之前将所有数据读取到内存中,而流式函数则不需要
- 批处理:
percentile()
、holt_winters()
、median()
、mode()
、spread()
、stddev()
- 流式:
mean()
、min()
、max()
、first()
、last()
、top()
、sum()
、elapsed()
、moving_average()
- 批处理:
- 增加分片持续时间(伴随着写入性能和存储/压缩权衡)
- 当查询搜索存储以检索数据时,它必须为每个分片分配新内存。许多分片意味着更多的内存使用。
- 创建一个游标来引用每个分片中的每个序列。
- 如果您运行一个查询,评估 3 个分片中 1,000 个序列的单个字段,则至少会生成 3,000 个游标......这会影响 CPU 和内存。
- 在大多数情况下,“高查询负载”由访问的分片数量定义,无论是一个查询访问多个分片,还是多个查询各自访问一个唯一的分片。
- 当查询搜索存储以检索数据时,它必须为每个分片分配新内存。许多分片意味着更多的内存使用。
- 如果您正在使用 Grafana 或 Chronograf 等 UI 工具,您可以利用仪表板模板变量。在适当的情况下,这是一个启用过滤和防止过度获取数据的好工具。
分片持续时间
- 更长
- 由于活动/“热”分片,整体读取和写入性能更好(分片持续时间越长,分片“热”或未压缩的时间越长,查询效果越好)
- 更高效的压缩
- 更少的压缩
- 更短
- 成本更低的压缩
- 更易于管理
EXPLAIN ANALYZE
这是一个您可以用来分析您认为性能不佳的查询的工具。
运行它
EXPLAIN ANALYZE <query>
其输出将在下面解释
游标
- 一个为单个序列遍历分片的指针
- 每个分片中的每个序列创建一个游标(在 3 个分片中搜索 1,000 个序列的 1 个字段的查询将至少产生 3,000 个游标)
迭代器
- 存储和查询之间的主要接口
- 迭代器节点(和嵌套迭代器)是查询引擎的主力,执行排序、合并、聚合/缩减、表达式评估等操作。
执行时间
- 游标读取数据的时间
- 查询流经每个迭代器并由查询执行器耗尽时执行转换的时间
- 查询执行器将结果序列化为 JSON、CSV 或其他指定格式。
- 分析时,输出数据将被丢弃。
计划时间
- 在“选择节点”下,表示总查询计划时间
- 在“create_iterator”下,表示特定迭代器的计划时间
计划中采取的步骤
- 确定查询类型(原始查询或带有表达式的查询)
- 确定并分离时间范围和条件表达式以进行过滤
- 确定查询的有效时间范围,并根据该时间范围和测量的索引列表选择合适的分片(可能包括集群中的远程节点)
- 对于每个分片及其中的每个测量
- 从索引中选择所有匹配的序列键,并按
WHERE
子句中的任何标签谓词进行过滤 - 根据
GROUP BY
维度将过滤后的序列整理成标签集 - 枚举每个集合并创建:每个序列的游标和迭代器以读取 TSM 数据,以及每个组的迭代器集以执行所需的转换
- 合并所有迭代器并将合并结果返回给查询执行器
- 从索引中选择所有匹配的序列键,并按
序列键的数量决定了查询计划的速度以及所需的内存量。
除了本指南中列出的性能优化策略外,您始终可以联系我们,了解针对您的用例可以做的更具体的事情。如果您是企业客户,我们的支持团队、销售工程团队和客户成功团队随时为您提供帮助。对于社区用户,我们有一个社区网站和一个 社区 Slack。
任何为灵活性而设计的平台都意味着有很多方法可以改进人们对该平台的使用。虽然本指南的调整旋钮很多,但并非所有旋钮都可能与每个用户相关。我希望每位读者都能从中学到至少一些可以提高其整体性能的方法。