bug(self-hosting): v4 backfill background migrations fail with TIMEOUT_EXCEEDED — fired chunk queries inherit a hardcoded 35s max_execution_

自托管 Langfuse ≥ 3.205 的 v4 后台迁移(通过 LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_HISTORIC_BACKFILL=true 启用),在 ClickHouse 实例性能较小(如 ClickHouse Cloud 基础版、小型自管节点

bug(self-hosting): v4 backfill background migrations fail with TIMEOUT_EXCEEDED — fired chunk queries inherit a hardcoded 35s max_execution_

bug(self-hosting): v4 backfill background migrations fail with TIMEOUT_EXCEEDED — fired chunk queries inherit a hardcoded 35s max_execution_time

快速结论:此报错出现在自托管 Langfuse v4 准备阶段的后台回填迁移(v4 backfill background migrations)中,原因是 fireQuery 触发的 ClickHouse 分块查询(chunk query)继承了客户端默认的 35 秒 max_execution_time,与迁移任务本应使用的 fire-and-poll 长时运行设计冲突。优先排查 worker/src/backgroundMigrations/backfillEventsHistoric.tsfireQuery 调用处的 request_timeout 是否被注释或未正确配置。

问题场景

自托管 Langfuse ≥ 3.205 的 v4 后台迁移(通过 LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_HISTORIC_BACKFILL=true 启用),在 ClickHouse 实例性能较小(如 ClickHouse Cloud 基础版、小型自管节点)、每月分区(monthly-partition chunk)数据量较大(例如 observations 表 3300 万行 / 24 GiB,分布 5 个分区)时触发。

迁移步骤 step_1 可能部分成功(小分区完成),但 step_2 全部失败,形成“小分区过、大分区超时”的特征。

报错原文

Code: 159. DB::Exception: Timeout exceeded: elapsed 36448.796263 ms, maximum: 35000 ms. (TIMEOUT_EXCEEDED)

日志中出现于分块查询失败时,例如:

info  [Backfill PidTid Sorting] Firing query backfill-p-202606-...
info  [Backfill PidTid Sorting] Query backfill-p-202606-... confirmed running, aborting HTTP connection
warn  [Backfill PidTid Sorting] Chunk p-202606 failed, will retry (1/3): Code: 159. DB::Exception: Timeout exceeded: elapsed 36224.12178 ms, maximum: 35000 ms.
info  [Backfill PidTid Sorting] Applying retry settings ...: {"max_threads":1,"max_insert_threads":"1","max_block_size":"2048"} (attempt 2)
error [Backfill PidTid Sorting] Chunk p-202606 failed permanently: Code: 159. DB::Exception: Timeout exceeded: elapsed 35748.08744 ms, maximum: 35000 ms.
error [Backfill PidTid Sorting] Migration completed with 5 failed chunks; leaving merges enabled on observations_pid_tid_sorting for --retry-failed

原因分析

问题根源在于代码中的双超时机制矛盾。 fireQuery 函数(位于 worker/src/backgroundMigrations/utils/backfillBase.ts)采用“fire-and-poll”设计:通过 commandClickhouse 发起 INSERT 查询并赋予 query_id,随后立即断开 HTTP 连接使查询在服务端继续运行,并通过轮询 system.processes / system.query_log 确认完成。然而,触发查询时未显式覆盖 max_execution_time,因此继承了客户端级别(client-level)的默认设置。

packages/shared/src/server/clickhouse/client.ts 中定义了:

const CLICKHOUSE_CLIENT_DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
const CLICKHOUSE_SERVER_TIMEOUT_GRACE_SECONDS = 5;
// ...
max_execution_time: Math.ceil(requestTimeout / 1000) + CLICKHOUSE_SERVER_TIMEOUT_GRACE_SECONDS,

这导致每个分块查询服务端 max_execution_time 被固定为 35 秒(30 + 5)。即使 HTTP 连接已断开,该限制仍作为服务端查询设置生效。重试机制通过减小 max_block_size、设置 max_threads: 1 反而使查询更慢,导致重试更易超时。

根本修复方向是让 fireQuery 调用处使用更长的 request_timeout(如 30–60 分钟)或直接设置 max_execution_time: 0(无限制),因为 fire-and-poll 模式本身已管理超时和重试逻辑。

环境排查

  • Langfuse 版本:确认是否为 ≥ 3.205,特别是版本 3.212.0 及更早的主分支(main)均受影响。
  • ClickHouse 版本:自托管实例,如 ClickHouse Cloud 26.2.1 或类似小规模服务。
  • 数据库规模:检查涉及迁移的表(如 observations)的行数、分区数和数据量。如果单月分区数据 INSERT 耗时超过 35 秒,则必定触发。
  • 迁移配置:确认 LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_HISTORIC_BACKFILL=true 已设置,且迁移步骤已按部署路径创建 v4 events 表并授予 system.partsREMOTEsystem.processessystem.query_log 等权限。
  • 自定义超时环境变量:检查是否设置了 LANGFUSE_CLICKHOUSE_DATA_EXPORT_REQUEST_TIMEOUT_MS(默认 10 分钟,PR #14753 提升至 60 分钟)或 LANGFUSE_CLICKHOUSE_DELETION_TIMEOUT_MS(默认 10 分钟),这些仅影响数据导出和删除,不影响后台迁移。

解决步骤

  1. 定位代码文件:打开 worker/src/backgroundMigrations/backfillEventsHistoric.ts,找到 fireQuery 调用的位置(Issue 中提到 request_timeout 被注释掉,行号约 548–550)。
  2. 取消注释或添加超时配置
    方案一(推荐,无需重启?):取消注释 request_timeout 并设置一个较长值,例如 1800000(30 分钟)或 3600000(60 分钟),代码如下:

    // 在 clickhouseConfigs 对象中
    request_timeout: 1_800_000, // 30 分钟

    方案二(更简洁,符合 fire-and-poll 设计):在调用 commandClickhouse 时,通过 clickhouse_settings 显式设置 max_execution_time: 0(无限制),因为 fire-and-poll 模式自身管理超时和重试。例如:

    const clickhouseSettings = {
      max_execution_time: 0,
      ...retrySetting,
    };

    方案三(可选,遵循已有模式):定义新环境变量 LANGFUSE_BACKGROUND_MIGRATION_MAX_EXECUTION_TIME_S 并在代码中引用,但 Issue 中提供的合并建议优先采用方案一或二。

  3. 重新部署:应用代码修改后,重新构建并部署 worker 服务。
  4. 触发迁移重试:如果迁移已经以失败结束,可能需要重新启用并运行,或使用 --retry-failed 参数重新尝试失败的分块。

验证方法

部署修改后的代码,重新执行受影响的迁移步骤(如 step_2)。观察日志,确认之前超时的分块查询不再因 TIMEOUT_EXCEEDED 失败,而是正常完成并进入轮询阶段。例如,日志中应出现 Query ... confirmed running, aborting HTTP connection 后,最终显示该分块成功,不再出现 Code: 159 错误。

参考来源

langfuse/langfuse #14999

celebrityanime
celebrityanime
文章: 13204

发表回复

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