Skip to content

5.5 Query builder

学习目标

完成本节后,你将能够:

  1. 理解为什么 ClickHouse 查询必须集中到 query builder。
  2. 说出 EventsQueryBuilder 的字段、field set、filter、排序、表选择和 split query 机制。
  3. 修改列表字段或 Public API 字段时知道要同步哪些 contract。

5.5.1 先给结论

Query builder 是 ClickHouse 查询的安全出口。它不是为了少写 SQL,而是为了把这些系统规则集中起来:

  • tenant isolation;
  • field mapping;
  • field set;
  • parameterized query;
  • FilterState lowering;
  • ClickHouse primary key friendly order;
  • events_core / events_full 表选择;
  • 大字段延迟读取;
  • trace_id hash optimization;
  • CTE/JOIN 结构;
  • query output alias 稳定。

如果每个页面手写 SQL,这些规则一定会分叉。

5.5.2 EventsQueryBuilder 的结构

源码:packages/shared/src/server/queries/clickhouse-sql/event-query-builder.ts

5.5.3 字段映射:领域名到 SQL 表达式

EVENTS_FIELDS 把应用里的字段名映射成 ClickHouse expression,例如:

field keySQL 语义
ide.span_id as id
traceIde.trace_id as "trace_id"
projectIde.project_id as "project_id"
metadatamapFromArrays(...) as metadata
latencydate_diff('millisecond', e.start_time, e.end_time)
timeToFirstTokendate_diff('millisecond', e.start_time, e.completion_start_time)
input / output大字段,只有需要时读取。

字段映射的价值是让 UI、API、export、eval 使用同一套字段别名和含义。

5.5.4 Field set:性能 contract

FIELD_SETS 定义常见查询场景需要哪些字段。

field set用途
base列表常用 observation 字段。
baseWithoutTools列表不需要完整 tool payload 时使用。
calculatedlatency、time to first token 等计算字段。
ioinput/output。
metadatametadata map。
toolstool definitions/calls/names。
core / basic / usage / promptPublic API v2 field groups。
exportCSV/JSON export 所需字段。
evalevaluation 相关字段。
experimentItemsexperiment item 视图字段。

field set 是性能 contract:调用方必须明确自己要读什么,不能默认 SELECT *

5.5.5 自动 project filter

BaseEventsQueryBuilder 在 build query 时默认把 project filter 放到 WHERE 开头:

sql
e.project_id = {projectId: String}

只有显式传 NoProjectId 才会 opt out。

这个设计把多租户隔离做成默认行为。手写 SQL 最容易漏的就是这一步。

5.5.6 Filter lowering 和优化

applyFilters(filterList) 会把 FilterState lowering 之后的条件放进 WHERE。

events table 还做了一个重要优化:当筛选条件是 trace_id = ... 时,query builder 会加:

sql
xxHash32(trace_id) = xxHash32({traceIdXxHash: String})

这配合 events table 的排序/主键结构,帮助 ClickHouse 更快缩小扫描范围。

5.5.7 排序优化

当调用方按 start_time 排序,orderByColumns 会补:

sql
ORDER BY e.project_id, toStartOfMinute(e.start_time), e.start_time

原因是 events table 的 primary key 以 project_id 和 start time bucket 为核心。排序不是 UI 小细节,它会影响 ClickHouse 是否能有效利用数据布局。

5.5.8 表选择:events_core vs events_full

EventsQueryBuilder 默认读 events_core

  • 列表;
  • filter options;
  • count;
  • 常见聚合;
  • 轻量字段。

当需要完整 input/output 或 metadata expansion 时,needsFullTable() 会切到 events_full

  • selectIO(truncated = false)
  • selectMetadataExpanded(...)
  • forceFullTable()

这就是 v4 events 的轻重字段策略:默认轻读,必要时 full read。

5.5.9 Split query

buildEventsFullTableSplitQuery 解决的是“既要高效过滤,又要 full payload”的问题。

它先在 events_core 中得到候选行,再从 events_full 取这些行的 I/O/metadata,最后 LEFT ANY JOIN 合并,避免 full table 直接承担复杂过滤和排序成本。

5.5.10 修改字段的同步面

改动同步位置
新增 ClickHouse 列migration、dev table、insert/read record schema。
worker 写新列IngestionService、record validation、ClickhouseWriter
query 返回新字段EVENTS_FIELDSFIELD_SETS、repository mapper。
UI table 展示column definition、sorting/filter config、client domain converter。
Search Bar 支持field registry、grammar completions、adapter、reverse adapter。
Public API 暴露public API field groups、Zod response、Fern。
Export 使用export field set、CSV/JSON mapping。

5.5.11 自检清单

  • 是否通过 query builder 而不是手写 SQL?
  • 是否自动带 project filter?
  • 是否选择了最小 field set?
  • 列表是否避免 input/output/metadata 大字段?
  • 需要 full payload 时是否先缩小候选集?
  • 排序是否和表主键友好?
  • 新字段是否同步 write path、read path、API/UI contract?

下一篇

第 6 篇 · 前端与颜色