Map(LowCardinality(String), String) 列。ClickHouse 也支持强类型的 JSON 类型,而 ClickStack 也提供了可用其替代 Map 的 Beta 支持。
对于典型的可观测性工作负载,我们建议保留默认的基于 Map 的 schema。 JSON 类型适用于想在 attribute 键集合较小且稳定的工作负载上评估它的用户,但它并不是通用场景下推荐的 schema。
为什么推荐默认使用 Map
Map(LowCardinality(String), String) 将键和值存储在同一结构中。Map 过去的主要劣势是:读取单个键时,必须读取整个 map 列。如今这一点已经改变:ClickHouse 现在支持分桶 map 序列化,可将 map 拆分为多个桶,使查询只需读取所需的桶。再结合 map 键和值上的文本索引——这也是 ClickStack 默认 schema 的配置方式——Map 就能在读取时同时具备良好的选择性和高性能,而且新增键时无需付出任何额外的摄取代价。
在实际使用中,这意味着:
- 随着键数量增长,摄取成本依然稳定。 新增属性键不会改变磁盘上的列布局,也不会创建新的列文件。摄取成本受数据量限制,而非键的基数影响。
- 不会出现元数据膨胀。 磁盘上的列文件数量不会随着唯一属性键数量的增加而增加。
- 可通过索引进行选择性查找。 map 键和值上的文本索引支持点查找,无需扫描每一行。
- 在高吞吐量下表现可预测。 Map 能够处理突发的、无 schema 的属性集合——这在 tracing 和日志中很常见——且不会产生按键计算的额外开销。
为什么默认不使用 JSON
JSON 类型采用了不同的思路:在写入时,ClickHouse 会为它看到的每个路径动态创建一个专用的强类型子列。在读取时,这很有吸引力,因为只会读取所请求的子列,类型得以保留,也不需要在查询时进行类型转换。
代价则体现在摄取时。创建和管理大量动态子列会带来写入时开销以及更高的元数据复杂度。对于可观测性工作负载而言,这类工作负载通常具有规模很大或高度动态的属性集合,以及很高的摄取吞吐量,因此这类开销相当显著。max_dynamic_paths 限制可以通过将额外路径落入共享列来控制影响,但访问共享列比访问专用子列更慢,这会削弱当初使用 JSON 的读取时优势。
随着 bucketed map serialization 消除了 Map 在读取时的大部分历史开销,对于典型的可观测性工作负载来说,JSON 的读取时优势已经不足以抵消其摄取时成本。
何时仍可考虑 JSON
- 你的 attribute 键集合较小且稳定,也就是说,不会出现成千上万个唯一键,而且很少有新键出现。
- 相对于 attribute 的基数,摄取吞吐量不高。
- 你希望对 attribute 进行强类型访问,无需在查询时进行类型转换 (数值保持为数值,布尔值保持为布尔值) 。
- 你愿意在 ClickStack 中使用一项 Beta 功能,并接受相关集成后续可能发生变化。
Map 的 schema。
Beta 状态
2.0.4 版本起,ClickStack 开始提供对 JSON 类型的 Beta 支持。
启用 JSON 支持
Map 的 schema,请设置以下环境变量。
托管 ClickStack
OTEL_AGENT_FEATURE_GATE_ARG='--feature-gates=clickhouse.json'。例如:
开源 ClickStack
OTEL_AGENT_FEATURE_GATE_ARG='--feature-gates=clickhouse.json' 设置为启用,并在 HyperDX 应用层中设置 BETA_CH_OTEL_JSON_SCHEMA_ENABLED=true,以便其能够查询 JSON 类型的 schema。
例如:
从基于 Map 的 schema 迁移到 JSON
1
停止 OTel collector
2
3
部署 collector
部署 collector,并设置OTEL_AGENT_FEATURE_GATE_ARG。4
重启 HyperDX 容器以启用 JSON schema 支持
5