bug: eval preview tables run unbounded full-history ClickHouse queries, can exhaust memory

用户在自部署的 Langfuse 实例中,进入 Evaluators → Set up evaluator,选择 Observations 或 Traces 作为评估目标时,预览表格的 generations.all / generations.countAll tRPC 请求缺少 startTim

bug: eval preview tables run unbounded full-history ClickHouse queries, can exhaust memory

bug: eval preview tables run unbounded full-history ClickHouse queries, can exhaust memory

快速结论:在 Langfuse 的 eval 预览表格(ObservationsTable、TracesTable、EventsTable)中,当传入 externalFilterState 时,会覆盖掉包含 dateRangeFiltercombinedFilterState,导致 ClickHouse 查询缺少时间范围限制,扫描整个项目历史数据,可能引发 OOM。优先检查 web/src/components/table/use-cases/ 下的 externalFilterState || combinedFilterState 逻辑。

问题场景

用户在自部署的 Langfuse 实例中,进入 Evaluators → Set up evaluator,选择 Observations 或 Traces 作为评估目标时,预览表格的 generations.all / generations.countAll tRPC 请求缺少 startTime >= 过滤器,导致 ClickHouse 查询全量历史数据,引发内存溢出。问题存在于 v3.174.1 及更新的主分支版本。

报错原文

Code: 241. DB::Exception: (total) memory limit exceeded: would use 15.41 GiB
(attempt to allocate chunk of 6523591 bytes), current RSS: 18.92 GiB, maximum: 18.90 GiB.
OvercommitTracker decision: Query was selected to stop by OvercommitTracker:
While executing MergingSortedTransform. (MEMORY_LIMIT_EXCEEDED)
(comment: {"kind":"rows","feature":"tracing","type":"observation",
"operation_name":"getObservationsTableInternal"})

原因分析

问题根因在于三个表格组件(ObservationsTable、TracesTable、EventsTable)都使用了相同的 filter state 覆盖模式:const filterState = externalFilterState || combinedFilterState;。当 eval 预览通过 externalFilterState 传入外部过滤状态时,该逻辑会用 externalFilterState 完全替换掉 combinedFilterState,而 combinedFilterState 中包含了默认的 24 小时时间范围过滤器(dateRangeFilter)。尽管 eval 预览同时传入了 externalDateRange={last1Day},但这个独立的 prop 并未被合并到生效的 filter state 中,因此 ClickHouse 查询没有时间范围限制,导致扫描整个项目历史。

环境排查

  • Langfuse 版本:v3.174.1(以及当前 main 分支)
  • ClickHouse 版本:未明确指定,但需确保内存配置足够支撑大项目查询
  • 项目规模:如果 observation/trace 数据量超过数万条,更容易触发此问题
  • 受影响组件路径:web/src/components/table/use-cases/traces.tsx(约 L346)、web/src/components/table/use-cases/observations.tsx(约 L467)、web/src/features/events/components/EventsTable.tsx(约 L420)

解决步骤

  1. 定位并修改 filter state 合并逻辑:在 ObservableTable、TracesTable 和 EventsTable 组件中,将 const filterState = externalFilterState || combinedFilterState; 改为合并方式,例如:const filterState = externalFilterState ? [...externalFilterState, ...dateRangeFilter] : combinedFilterState;。注意 dateRangeFilter 需要从组件现有的时间范围逻辑中提取。
  2. 或者修改 eval 预览调用方:在 web/src/features/evals/components/inner-evaluator-form.tsx(约 L187-L193)中,手动将时间范围过滤条件包含到 externalFilterState 中再传入表格组件。
  3. 协调并行工作:注意 PR #14939 正在重构相同的 filter state 逻辑,如果该 PR 尚未合并,建议在其基础上进行修改以避免冲突。
  4. 部署修复后重启后端服务:确保修改后的代码已部署并重启相关服务。

验证方法

在拥有大量 observation/trace 历史数据的自部署项目中,进入 Evaluators → Set up evaluator,预览表格的 generations.all / generations.countAll tRPC 请求中应包含有效的 startTime >= 过滤器。同时检查 ClickHouse 日志,确认不再出现因全量扫描导致的 MEMORY_LIMIT_EXCEEDED 错误。对于约 70,854 条 observation 的项目,24 小时限制的查询应只读取约 5.1K 行 / 386 KiB 内存,而非全表的 78.98K 行 / 22.14 MiB。

参考来源

langfuse/langfuse #14946

celebrityanime
celebrityanime
文章: 13480

发表回复

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