news 2026/9/11 13:06:29

Langfuse 中的 ClickHouse LowCardinality 最佳实践:为重复字符串选择正确的数据类型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Langfuse 中的 ClickHouse LowCardinality 最佳实践:为重复字符串选择正确的数据类型

Langfuse 中的 ClickHouse LowCardinality 最佳实践:为重复字符串选择正确的数据类型

【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse

本文基于 Langfuse 仓库内嵌的 ClickHouse 最佳实践技能(.agents/skills/clickhouse-best-practices)中schema-types-lowcardinality规则展开,系统讲解在 LLM 可观测性场景下如何用LowCardinality(String)替代普通String存储重复值,并通过仓库真实的 ClickHouse 迁移脚本(如observationstraces表)印证其落地方式。读完本文,你将掌握字典编码的适用边界、基数(cardinality)判定方法、LowCardinalityFixedString/Enum的选型原则,以及在 Langfuse 事件表中正确运用该类型的实战写法。

规则背景:为什么重复字符串需要特殊处理

在 Langfuse 这类 LLM 可观测平台中,ClickHouse 承载着海量的 trace、observation、score 事件数据。这些表中存在大量取值集合非常有限、但出现次数极高的字符串列——例如观测类型(type)、日志级别(level)、环境名(environment)、SDK 来源(ingestion_sdk_name)等。

普通String类型对每个值都做全量存储:"United States"存储 5 亿次、"Chrome"存储 3 亿次、"page_view"存储 8 亿次,磁盘与内存开销被无限放大。LowCardinality通过**字典编码(dictionary encoding)**解决该问题:为去重后的唯一值建立字典,数据列中仅保存字典索引,从而在唯一值数量较少时获得显著的存储缩减与查询加速。

该规则在技能库中被标记为Impact: HIGH,属于建表时必须检查的核心约束之一(对应 SKILL.md 中 Schema Reviews 的第 6 步)。

反模式:普通 String 存储低基数字符串

以下建表方式虽然语法正确,却会造成严重的空间浪费:

CREATE TABLE events ( country String, -- "United States" stored 500M times browser String, -- "Chrome" stored 300M times event_type String -- "page_view" stored 800M times )

当某一列的取值集合远小于行数时,普通String会一遍又一遍重复写入相同的字节串。列式存储虽然只读取需要的列,但每一列的原始数据量仍然随行数线性膨胀,且压缩效率也远低于字典索引方案。

正确姿势:LowCardinality 包装低基数字符串

CREATE TABLE events ( country LowCardinality(String), -- ~200 unique values browser LowCardinality(String), -- ~50 unique values event_type LowCardinality(String) -- ~100 unique values )

LowCardinality(String)String的一种包装类型:对外仍然表现为字符串,支持全部字符串函数与过滤、分组、排序操作,但底层按字典索引存储。唯一值数量越低,字典编码收益越大。

何时使用 LowCardinality:以 10K 为界

规则给出了明确的决策表:

唯一值数量建议
< 10,000使用 LowCardinality
> 10,000使用普通 String

阈值背后的原因:字典编码有固定的字典构建与维护开销。当唯一值超过约 1 万后,字典本身越来越大,索引指向的收益被稀释,甚至可能出现“字典≈数据”的极端情况——Langfuse 的 Parquet 导出代码中也对此有明确注释(见 packages/shared/src/server/repositories/clickhouse.ts):0 disables dictionary encoding. Near-unique LLM payloads fall back to plain anyway (dictionary ≈ data),即近乎全唯一的 LLM 载荷本就该回退为明文存储。

建表前先检查基数

在决定列类型之前,用聚合函数确认实际基数:

-- Check cardinality before deciding SELECT uniq(column_name) FROM table_name;

uniq()返回去重后的近似计数,足以支撑类型选型判断。Langfuse 的迁移流程中,新增低基数列时同样先基于业务语义判断(如environmenttypelevel),再以LowCardinality(String)落地。

LowCardinality vs FixedString:各司其职

FixedString(N)LowCardinality(String)常被混淆,规则明确了两者的分工:

ReserveFixedStringfor strictly fixed-length data (e.g., 2-char country codes). For most low-cardinality text,LowCardinality(String)outperformsFixedString.

  • FixedString仅适合长度严格固定的数据,例如两位国家码"US""DE""JP"。长度不足时 ClickHouse 会补零,长度超出则直接报错,灵活性极差;
  • LowCardinality适合长度可变但取值集合小的字符串,例如国家名"United States""Germany"。它不需要关心每个值的长短,字典只存一份完整值。
-- FixedString: Only for truly fixed-length data country_code FixedString(2), -- "US", "DE", "JP" - always 2 chars -- LowCardinality: For variable-length low-cardinality strings country_name LowCardinality(String), -- "United States", "Germany"

LowCardinality vs Enum:可变集合 vs 固定枚举

技能库中另一条规则 schema-types-enum 提供了互补的选型视角:

场景使用
建表时值集合固定且已知Enum8/Enum16
值可能频繁变化LowCardinality(String)
需要插入时校验Enum
需要查询中的自然排序Enum

二者的本质区别在于:Enum插入时强制校验(写入"shiped"这类拼写错误会直接报Unknown element),并提供基于枚举值的自然排序;LowCardinality不校验任何值,只做存储压缩。Langfuse 事件表之所以大量使用LowCardinality(String)而非Enum,正是因为typelevelenvironment等列的取值集合会随产品演进持续扩展(新增观测类型、新增环境名),而Enum的 ALTER 成本更高、灵活性更低。

Langfuse 仓库中的真实落地:observations 与 traces 表

规则不是纸面建议——Langfuse 的规范迁移脚本(canonical migrations)就是最佳实践的直接体现。迁移文件统一位于 packages/shared/clickhouse/migrations/canonical 目录。

observations 表:五类 LowCardinality 应用

0002_observations.up.sql 完整展示了五种典型用法:

CREATE TABLE observations {CLICKHOUSE_CLUSTER_CLAUSE} ( ... `type` LowCardinality(String), -- 观测类型:GENERATION/SPAN/EVENT... `metadata` Map(LowCardinality(String), String), -- Map 的 key 列 `level` LowCardinality(String), -- 日志级别:DEBUG/INFO/WARNING/ERROR... `provided_usage_details` Map(LowCardinality(String), UInt64), -- usage key 列 `usage_details` Map(LowCardinality(String), UInt64), `provided_cost_details` Map(LowCardinality(String), Decimal64(12)), -- cost key 列 `cost_details` Map(LowCardinality(String), Decimal64(12)), ... )

要点拆解:

  • 单列直接包装typelevel是典型枚举语义的低基数列(各自只有个位数到几十个取值),直接声明为LowCardinality(String)
  • Map 的 key 使用LowCardinality(String)metadata这类 KV 字典中,key 集合(如"input""output""model")是高度复用的,对 Map 的 key 应用字典编码能显著压缩键名重复存储;Langfuse 的 traces 表(0001_traces.up.sql)同样遵循Map(LowCardinality(String), String)模式;
  • 不适用于高基数列idtrace_idproject_idname等近乎全唯一或中等基数的列仍保持普通String/Nullable(String),与 10K 阈值规则一致。

environment 列:通过 ALTER 追加低基数列

0008_add_environments_column.up.sql 展示了如何对存量表补充低基数列:

ALTER TABLE traces {CLICKHOUSE_CLUSTER_CLAUSE} ADD COLUMN environment LowCardinality(String) DEFAULT 'default' AFTER project_id{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS alter_sync = 2}; ALTER TABLE observations {CLICKHOUSE_CLUSTER_CLAUSE} ADD COLUMN environment LowCardinality(String) DEFAULT 'default' AFTER project_id{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS alter_sync = 2}; ALTER TABLE scores {CLICKHOUSE_CLUSTER_CLAUSE} ADD COLUMN environment LowCardinality(String) DEFAULT 'default' AFTER project_id{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS alter_sync = 2};

environment(如productionstagingdevelopment)取值有限,选LowCardinality(String)且带DEFAULT 'default';同时按 Langfuse 的迁移规范附加{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS alter_sync = 2},确保集群模式下元数据变更同步完成(详见 SKILL.md 中关于alter_sync的说明)。SDK 归因列ingestion_sdk_nameingestion_sdk_version(见 0035_add_ingestion_attribution_columns.up.sql)同样采用LowCardinality(String) DEFAULT 'unknown',因为 SDK 名称与版本号的组合规模有限且高度复用。

与分区基数的联动

低基数列还会影响分区设计。规则 schema-partition-low-cardinality 指出分区基数应控制在 100–1,000 个,且其“正确示例”中event_type正是以LowCardinality(String)作为排序键的组成部分——Langfuse 的observations表将LowCardinality(String)type同时放入PRIMARY KEYORDER BY(位于project_id之后、toDate(start_time)之前),正是“低到高基数排序”这一主键设计规则(schema-pk-cardinality-order)的体现:低基数列在前,高基数列(如id)殿后。

与 Nullable 的搭配原则

LowCardinality也可以与Nullable组合(如Nullable(LowCardinality(String))),但技能库的另一条 HIGH 级规则 schema-types-avoid-nullable 提醒:Nullable会为每列维护额外的UInt8标记列,带来存储与性能开销。Langfuse 的实践中,语义上可空的低基数列(如versionreleaseparent_observation_id)保持Nullable(String),而能用默认值表达的列(如environment DEFAULT 'default'ingestion_sdk_name DEFAULT 'unknown')则直接用DEFAULT而非Nullable。仅当NULL具有独立业务语义(如deleted_at表示“未删除”)时才使用Nullable

落地检查清单

结合规则与 Langfuse 源码,在建表或加列时可依次核对:

  1. 识别重复列:该列取值集合是否远小于行数?用SELECT uniq(column_name) FROM table_name;验证,唯一值 < 10,000 才考虑LowCardinality
  2. 选型三问:值集合是否固定不变?——是则考虑Enum8/Enum16;长度是否严格固定?——是则考虑FixedString(N);其余低基数可变字符串一律LowCardinality(String)
  3. Map 键压缩metadatausage_details等 KV 结构的 key 声明为Map(LowCardinality(String), ...)
  4. 配合主键设计:低基数列放在PRIMARY KEY/ORDER BY前部(参考 0002_observations.up.sql 的列序);
  5. 避免过度使用:高基数列(ID、名称、payload 内容)保持普通String——字典编码对近乎全唯一的数据没有收益,甚至会带来字典维护开销。

小结

LowCardinality(String)是 ClickHouse 表结构中性价比最高的优化手段之一:对唯一值 < 10K 的重复字符串启用字典编码,即可在大幅削减存储的同时提升过滤与聚合效率。Langfuse 在observationstracesscores等核心事件表上的真实迁移脚本,完整示范了单列、Map 键、增量 ALTER 三种落地形态,并与主键排序、分区基数、Enum/Nullable选型等相邻规则形成一套可复用的建表决策框架。对任何以 ClickHouse 存储海量事件数据、且存在大量重复枚举值的系统,这套方法论都值得直接借鉴。

【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 12:59:56

英语发音技巧:of的弱读规律与训练方法

1. 发音现象解析&#xff1a;of的弱读本质英语中of的发音存在强读和弱读两种形式&#xff0c;其中弱读/əv/在实际口语中出现频率高达90%以上。这个现象源于英语的"弱化音节"规律——当介词、冠词、连词等功能词处于非重读位置时&#xff0c;其元音会自然向中央元音/…

作者头像 李华
网站建设 2026/9/11 12:59:19

轨道检测与障碍物识别:Canny+霍夫变换+YOLOv5实战解析

简介&#xff1a;一套面向电车轨道与障碍物检测的目标检测项目&#xff0c;整合传统数字图像处理与YOLOv5深度学习算法&#xff0c;适合计算机相关专业学生、教师及开发者用于课程设计、毕业设计或算法学习。项目先采用边缘检测、透视变换、霍夫变换标注轨道并划定感兴趣区域&a…

作者头像 李华
网站建设 2026/9/11 12:56:41

序列绑定:从算法到UI、网络与三维创作的本质与排查

不用急着翻开任何一本算法书或者框架文档。先说说我怎么注意到"序列绑定"这个问题的&#xff1a;有天晚上我排查一个WPF界面按钮点了没反应的问题&#xff0c;查了两小时&#xff0c;最后定位到是Command绑定的CanExecute没有触发刷新&#xff1b;关掉调试器刷手机&a…

作者头像 李华
网站建设 2026/9/11 12:56:02

OpenProject 快速上手指南:免费的开源项目管理软件

OpenProject 快速上手指南&#xff1a;免费的开源项目管理软件 【免费下载链接】openproject OpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue …

作者头像 李华
网站建设 2026/9/11 12:55:40

Android音频用途管理:AudioAttributes与getUsage详解

1. Android音频用途管理基础 在Android音频系统中&#xff0c;AudioAttributes是一个核心类&#xff0c;它定义了音频流的属性和用途。这个类在API 21(Android 5.0)中被引入&#xff0c;用于替代旧的AudioManager中的流类型(STREAM_MUSIC, STREAM_RING等)系统。AudioAttributes…

作者头像 李华