使用InfluxDB解决系列卡迪纳尔性失控问题
作者:Rick Spencer / 产品,用例,开发者
2020年10月01日
导航到
在这篇文章中,您将了解在时序数据库中导致高系列卡迪纳尔性的原因,以及如何定位和消除这些原因。首先,对于那些刚开始接触这个概念的人来说,让我们先定义它:InfluxDB实例中独特的数据库、度量、标签集合和字段键组合的数量。因为高系列卡迪纳尔性是许多数据库工作负载内存使用量过高的主要原因,所以了解其成因和解决方法是重要的。
失控卡迪纳尔性的症状
人们通常有两种方式发现他们存在卡迪纳尔性问题:
- 他们在InfluxDB Cloud上达到了卡迪纳尔性限制。
- 他们注意到在InfluxDB Cloud或InfluxDB OSS 2.0上读取和有时写入的速度越来越慢。
当您向InfluxDB写入时,InfluxDB会使用您使用的度量(measurements)和标签(tags)来创建索引以加快读取速度。然而,当创建的索引太多时,读写操作实际上可能会开始变慢。
寻找失控卡迪纳尔性的源头
为每个唯一的标签值组合和所有字段名称创建索引。这在本站的其他文章《高卡迪纳尔性的红旗》和《InfluxDB数据布局和模式设计最佳实践》中有更详细的描述。卡迪纳尔性大致按以下公式计算
(tag1值数量 * tag2值数量 ... tagn值数量) * 字段名称数量
请注意,由于存在依赖标签,该公式在某些情况下可能会高估系列卡迪纳尔性,如此处所述。
寻找失控系列卡迪纳尔性的低垂果实
通常,有一个标签是导致失控卡迪纳尔性的原因。通常,这是一个几乎每个条目都具有唯一值的标签。
一些常见的罪魁祸首包括
- 使用日志消息作为标签值,但结果是日志消息具有时间戳、指针值或其他唯一字符串。
- 使用时间戳作为标签。这通常是在客户端代码中意外完成的。
- 使用独特但数值非常高的东西。例如,如果你用用户ID标记每个条目,这是可以的,除非你有数十万或数百万用户。
使用InfluxDB深入分析可疑标签
如果没有标签立即引起怀疑,那么你需要找到基数方程的值,以追踪你失控的基数来源。如果你不确定哪个桶导致失控的基数,你可能需要对多个桶执行此操作。
找到字段名称很容易。你可以使用此查询
import "influxdata/influxdb/v1"
v1.fieldKeys(bucket: "idping", start: -100y)
|> count()
不过,通常情况下,标签值是罪魁祸首。这里有一个查询,可以计算桶中每个标签的值数量
import "influxdata/influxdb/v1"
cardinalityByTag = (bucket) =>
v1.tagKeys(bucket: bucket)
|> map(fn: (r) => ({
tag: r._value,
_value: if contains(set: ["_stop","_start"], value:r._value) then
0
else
(v1.tagValues(bucket: bucket, tag: r._value)
|> count()
|> findRecord(fn: (key) => true, idx: 0))._value
}))
|> group(columns:["tag"])
|> sum()
cardinalityByTag(bucket: "my-bucket")
上述查询将显示哪些标签对基数贡献最大,其中很可能有一个比其他标签高几个数量级。然而,如果你遇到失控的基数,此查询可能在尝试完成计算时超时。如果你遇到超时,可以依次完成以下步骤。
首先,你可以使用此查询生成标签列表
import "influxdata/influxdb/v1"
v1.tagKeys(bucket: "my-bucket")
|> yield(name: "tags")
然后,对于每个标签,你可以通过类似此查询的方式找到与标签关联的值数量
import "influxdata/influxdb/v1"
v1.tagValues(bucket: "my-bucket", tag: "my-tag")
|> count()
这些查询应该能给你提供识别每个桶中基数来源所需的数据。为了确定哪些特定的标签正在增长,请在24小时后再次检查基数,看是否有标签增长显著。
修复你的模式
通常,解决失控的基数只需要将有问题的标签更改为字段。如果你担心这将对查询性能产生影响,解决方案将在下面讨论。
删除数据以降低高基数
有时你实际上不需要引起高基数的那些数据。在这些情况下,你可以使用删除API端点删除一些数据,或者简单地删除整个桶。请注意,你的账户反映基数降低可能需要数小时。
设计模式或读取性能
一般来说,使用标签的价值在于创建索引,而创建索引的价值在于在查询过程中,查询引擎不需要扫描数据库中的每一条记录。然而,如上所述,过多的索引会带来自己的性能问题。所以,窍门是在扫描和索引之间找到一个平衡点。
例如,假设你经常查询特定的用户ID,但你有成千上万的用户。一个简单的查询像这样
from(bucket: "my-bucket")
|> range(start: -7d)
|> filter(fn: (r) => r.userId == "abcde")
其中userId是一个字段,InfluxDB将导致扫描存储中的每一行以查找userId。然而,如果你在模式中包含一个可以合理索引的标签,你可以大大减少扫描的行数。例如,假设你有成千上万的用户,但每个用户可以按类型、位置、公司等进行分类。你可以包含一个具有更少值的标签,以便它可以合理地索引。结果,这个查询
from(bucket: "my-bucket")
|> range(start: -7d)
|> filter(fn: (r) => r.companyTag == "Acme")
|> filter(fn: (r) => r.userId == "abcde")
可以执行得更快,因为“companyTag”是索引的,因此可以快速检索这组行,然后对userId的扫描可以扫描的行数就少得多。
希望这篇文章能帮助你解决基数问题,让你的InfluxDB项目重新走上轨道。一旦你了解了基数的影响因素以及与你的查询之间的关系,你就可以设计你的系统以更快地工作。