4.6 v4 Events 查询
学习目标
完成本节后,你将能够:
- 从 EventsTable 的 filter/search/order 追到 ClickHouse SQL。
- 区分
events_core和events_full的读取场景。 - 理解 query builder 为什么要统一 tenant filter、field set、排序和 full table split query。
4.6.1 先给结论
v4 Events 查询路径的核心设计是:UI 不直接表达 SQL,而是表达 FilterState、search query、search type、排序和分页;服务端再通过 shared query builder lower 到 ClickHouse。
标准路径是:
EventsTable -> eventsRouter -> eventsService -> shared repository -> EventsQueryBuilder -> ClickHouse其中:
- UI 和 Search Bar 只编辑 URL/filter state;
eventsRouter负责 tRPC project scope 和 input schema;eventsService负责列表组合、score 补充、filter option 限制;- shared query builder 负责字段映射、field set、tenant filter、表选择和 SQL 参数;
- ClickHouse 负责真正的列式扫描。
4.6.2 查询链路图
4.6.3 Router:先做 project scope,再交给 service
源码:web/src/features/events/server/eventsRouter.ts
重要 procedures:
| procedure | 作用 |
|---|---|
all | 列表查询,返回 observations + hasMore。 |
countAll | 计数查询。 |
filterOptions | 返回可选 filter value 和 score filter options。 |
batchIO | 批量读取 input/output/metadata。 |
scoresForTrace | v4 trace 详情页读取 trace-level scores。 |
byTraceId | 从 events table 读取某个 trace 下 observations。 |
all 的关键点是:
- 使用
protectedProjectProcedure; applyCommentFilters先处理评论筛选;normalizeOrderByForTable把排序规范到startTime;- 真正列表查询委托给
getEventList。
4.6.4 Service:列表轻读,重字段延迟读
源码:web/src/features/events/server/eventsService.ts
getEventList 会构造 query options:
projectId
filter
searchQuery
searchType
orderBy
limit + 1
offset
selectIOAndMetadata: false
renderingProps: { truncated: true, shouldJsonParse: false }这里有几个设计点:
| 设计 | 原因 |
|---|---|
limit + 1 | 用多取一条判断是否还有下一页。 |
selectIOAndMetadata: false | 列表页不默认读取 input/output/metadata。 |
renderingProps.truncated | 列表只需要可展示的轻量字段。 |
| score 单独读取 | observation score 和 trace-level score 有不同时间范围和关联方式。 |
batchIO 单独 endpoint | 需要展开 I/O 时按当前页 observation 批量读取。 |
filterOptions 也有规模保护:如果调用方没有传 lower startTime filter,会自动加默认 30 天 lookback,避免 filter options 扫全历史。
4.6.5 Query builder:字段、表、租户、排序的统一出口
源码:packages/shared/src/server/queries/clickhouse-sql/event-query-builder.ts
EventsQueryBuilder 管四类事情。
字段映射
EVENTS_FIELDS 把领域字段映射成 ClickHouse select expression,例如:
traceId->e.trace_id as "trace_id"latency->date_diff(...) as latencymetadata->mapFromArrays(...) as metadatainput/output-> 大字段,默认不读
Field sets
FIELD_SETS 把常见查询需要的字段分组:
basebaseWithoutToolscalculatediometadatatoolscorebasicusagepromptexporteval
调用方选择 field set,而不是手写 select list。这样列表、详情、Public API v2、export、eval 都能明确说自己需要哪些字段。
Tenant filter
BaseEventsQueryBuilder 在 build query 时自动加:
e.project_id = {projectId: String}除非显式使用 NoProjectId opt-out。这个设计把多租户隔离放在 query builder 默认行为里。
排序和 ClickHouse primary key 贴合
orderByColumns 在按 start_time 排序时会补:
e.project_id, toStartOfMinute(e.start_time), e.start_time目的是贴合 events table 的排序/主键形状,减少无谓扫描。
4.6.6 events_core 和 events_full 如何选择
EventsQueryBuilder 默认读 events_core。只有需要完整 I/O 或 metadata 时才读 events_full:
| 场景 | 表 |
|---|---|
| 列表页 base 字段 | events_core |
| filter options | events_core |
| count / aggregate | 通常 events_core |
| 详情页完整 input/output | events_full |
| metadata expanded | events_full |
| full fidelity export | 视字段需要选择 full 或 split query |
needsFullTable() 的判断包括:
selectIO且不是 truncated;- metadata expansion;
forceFullTable()。
4.6.7 Split query:先轻表过滤,再 full 表取重字段
源码:buildEventsFullTableSplitQuery
当查询既需要复杂过滤/排序,又需要 full I/O/metadata 时,直接扫 events_full 成本高。split query 的思路是:
- 先用
baseBuilder在events_core里过滤、排序、分页; - 把匹配到的
(start_time, trace_id, span_id)作为 key; - 再从
events_full里只取这些行的 input/output/metadata; - 用
LEFT ANY JOIN避免 ReplacingMergeTree duplicates 导致 fan-out。
这是 v4 查询性能设计里很关键的一点:重字段不是不能读,而是要在缩小候选集之后读。
4.6.8 为什么不能随便手写 SQL
绕开 query builder 容易漏掉:
project_idtenant filter;- ClickHouse 参数化 query;
- field alias 和 domain field 的一致性;
events_core/events_full表选择;- input/output/metadata 的轻重字段策略;
trace_idequality 的xxHash32优化;ORDER BY与 primary key 的贴合;- query tag / route context;
- FilterState operator 的统一语义。
手写 SQL 的问题通常不是立刻报错,而是在大租户、大时间范围、高基数字段下变慢或越权。
4.6.9 修改 v4 查询字段的同步面
| 改动 | 同步位置 |
|---|---|
| 新增 ClickHouse 列 | migrations、dev tables、record insert/read schema。 |
| worker 写入新字段 | IngestionService、ClickhouseWriter record。 |
| 列表显示新字段 | EVENTS_FIELDS、FIELD_SETS、events table column definition。 |
| 筛选新字段 | FilterState、search-bar field registry、filter lowering。 |
| Public API 暴露字段 | public API schema、Fern、generated client。 |
| 需要 full payload | selectIO、metadata expansion 或 split query。 |