bug: numeric DateTime64 inserts are misparsed on ClickHouse >= 26.8, multiplying every timestamp by 1000

这是 ClickHouse 26.8+ 与 Langfuse 自托管部署的兼容性问题,不是 Langfuse 逻辑 Bug。ClickHouse 26.8 将 input_format_read_datetime_number_as_raw_value 的默认值从 1 改为 0 ,导致 Worker

快速结论:这是 ClickHouse 26.8+ 与 Langfuse 自托管部署的兼容性问题,不是 Langfuse 逻辑 Bug。ClickHouse 26.8 将 input_format_read_datetime_number_as_raw_value 的默认值从 1 改为 0,导致 Worker 以毫秒 ticks 写入的数字时间戳被 ClickHouse 当作 Unix 秒再乘以 1000 解析,所有 DateTime64 列的时间都被放大 1000 倍。优先排查 ClickHouse 版本及该设置项。

适用环境:Langfuse v3.225.5 OSS 自托管(web + worker,Docker Swarm 部署)、ClickHouse 26.8.1.2041(官方构建)、Python SDK langfuse==3.15.0、opentelemetry-sdk==1.43.0、外部 Postgres 实例。Issue 确认影响所有使用 ClickHouse >= 26.8 的自托管部署。

最快修复方案:在 ClickHouse 中挂载配置文件 /etc/clickhouse-server/users.d/99-langfuse-compat.xml,将 input_format_read_datetime_number_as_raw_value 显式设置为 1,然后重启 ClickHouse。Issue 报告者确认重启后数据损坏立即停止,并通过 system.query_log 验证所有 Worker 插入操作均以该设置为 1 运行。

注意事项:该方案只阻止新的错误写入,已损坏的历史数据需要单独修复(修复 SQL 见下文)。修复 SQL 需在子查询中过滤,不能直接用 WHERE 过滤后再 REPLACE,否则会因列别名遮蔽导致匹配 0 行。损坏行会落在 999912 分区,可使用 DETACH PARTITION 在插入修复数据后清理。Langfuse 官方已确认将内部修复此问题。

问题场景

在自托管 Langfuse v3.225.5(OSS,web + worker)配合 ClickHouse 26.8.1.2041 的部署中,部分 traces 写入 ClickHouse 时 timestampcreated_at 被设置成异常值。表面上显示为 9999-12-31 23:59:59.000(DateTime64 可表示的最大值),实际是真实时间戳被乘以 1000 后发生溢出饱和。由于 9999-12-31 排序在任何真实日期之后,这些行会永久占据 Tracing UI 默认“最近优先”排序的顶部,导致 UI 看起来像摄取停止。

报错原文

bug: numeric DateTime64 inserts are misparsed on ClickHouse >= 26.8, multiplying every timestamp by 1000

原因分析

根本原因已确认:ClickHouse 26.8 将 input_format_read_datetime_number_as_raw_value 的默认值从 1 改为 0。Langfuse Worker 以未加引号的数字(毫秒 ticks)写入时间戳——对于 DateTime64(3) 恰好就是毫秒精度。新默认值下,ClickHouse 把这些数字解读为 Unix 秒,并按列精度(10³)缩放,导致每个时间戳被乘以 1000。

Issue 中提供了 ClickHouse 26.8.1.2041 上 system.settings_changes 的佐证:

input_format_read_datetime_number_as_raw_value | 1 -> 0,说明从 26.8 起,JSON 和 Values/Quoted 路径中 DateTime/DateTime64 列的未加引号数字被视为 Unix 秒。设置为 trueSET compatibility = '26.7' 可恢复 26.8 之前的行为。

需要纠正三点容易误解的地方:

  • 存储值并不是 DateTime64 的最大值,而是真实时间戳乘以 1000;ClickHouse 只是在显示时饱和到 9999-12-31 23:59:59,真实值完全可恢复(除以 1000 即可)。因此 WHERE timestamp = toDateTime64('9999-12-31 23:59:59', 3) 匹配不到任何行,正确过滤条件是 WHERE toYear(timestamp) >= 9000
  • 不只是 timestampcreated_at 受影响。traces 表中的 timestampcreated_atupdated_atevent_ts,observations 表中的 start_timeend_timecompletion_start_timecreated_atupdated_atevent_ts 全部受影响。
  • Latency 同样错误。由于 start_timeend_time 都被放大,UI 会把 4 秒的调用渲染成 1 小时 6 分 56 秒。

环境排查

  • 确认 ClickHouse 版本是否为 26.8 或更高(SELECT version())。
  • 执行 SELECT * FROM system.settings_changes WHERE name = 'input_format_read_datetime_number_as_raw_value',确认该设置默认值是否已变为 0。
  • 检查 Langfuse 版本是否为 v3.225.5 或其他近期版本;使用浮动 :3 tag 的 Docker 镜像部署需特别注意。
  • 检查是否所有近期有流量的项目都受影响;Issue 数据显示,截止 2026-08-27 之后有活动的项目损坏率非零,而之前无活动的项目损坏率为 0%。

解决步骤

  1. 应用临时兼容配置(可优先尝试):在 ClickHouse 服务器挂载 /etc/clickhouse-server/users.d/99-langfuse-compat.xml,内容如下:
    <clickhouse>
        <profiles>
            <default>
                <input_format_read_datetime_number_as_raw_value>1</input_format_read_datetime_number_as_raw_value>
            </default>
        </profiles>
    </clickhouse>

    重启 ClickHouse 后确认新写入不再损坏。Issue 报告者确认此方法立即生效。

  2. 验证设置已生效:重启后通过 system.query_log 检查所有 Worker 插入操作是否以该设置值为 1 运行。
  3. 修复已损坏数据:由于真实值可恢复,可用 REPLACE 修复而非直接删除。核心 SQL 为:
    INSERT INTO traces
    SELECT * REPLACE (
        if(toYear(timestamp) >= 9000,
           fromUnixTimestamp64Milli(intDiv(toUnixTimestamp64Milli(timestamp), 1000)),
           timestamp) AS timestamp
        /* created_at、updated_at、event_ts 同样处理 */
    )
    FROM (SELECT * FROM traces WHERE toYear(timestamp) >= 9000);

    注意:过滤必须放在子查询中,因为 REPLACE 的列别名会在 WHERE 中遮蔽原列,直接过滤会静默匹配 0 行。同样处理 observations 表的各时间列。

  4. 清理损坏分区:由于表按 toYYYYMM(timestamp) 分区,所有损坏行会集中落在 999912 分区。在插入修复数据后,可用 DETACH PARTITION 移除该分区。Issue 报告者用此方法恢复了 4,448 条 traces 和 8,534 条 observations。
  5. 关注官方修复:Langfuse 维护者已确认会内部修复此问题。建议跟进上游 PR,使 Langfuse 在写入时将 DateTime64 值加引号或显式在 ClickHouse 客户端连接上设置 input_format_read_datetime_number_as_raw_value = 1,避免依赖服务器默认值。

验证方法

  • 确认 ClickHouse 的 system.settings_changes 中该设置项已恢复为 1,或通过 system.query_log 确认 Worker 插入按新设置运行。
  • 查询修复后的数据:SELECT toUnixTimestamp64Milli(timestamp) FROM traces LIMIT 5,确认时间戳不再被放大(真实毫秒值应远小于 10¹³ 量级的异常值)。
  • 在 Tracing UI 中确认“最近优先”排序恢复正常,不再被 9999-12-31 的异常行占据。
  • 调用 GET /api/public/traces?limit=5,确认返回的 timestampcreatedAt 是真实时间而非 9999-12-31T23:59:59.000Z

参考来源

langfuse/langfuse #16858

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 21469

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注