Skip to content

4.6 v4 Events 查询

学习目标

完成本节后,你将能够:

  1. 从 EventsTable 的 filter/search/order 追到 ClickHouse SQL。
  2. 区分 events_coreevents_full 的读取场景。
  3. 理解 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。

标准路径是:

text
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。
scoresForTracev4 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:

text
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 latency
  • metadata -> mapFromArrays(...) as metadata
  • input / output -> 大字段,默认不读

Field sets

FIELD_SETS 把常见查询需要的字段分组:

  • base
  • baseWithoutTools
  • calculated
  • io
  • metadata
  • tools
  • core
  • basic
  • usage
  • prompt
  • export
  • eval

调用方选择 field set,而不是手写 select list。这样列表、详情、Public API v2、export、eval 都能明确说自己需要哪些字段。

Tenant filter

BaseEventsQueryBuilder 在 build query 时自动加:

sql
e.project_id = {projectId: String}

除非显式使用 NoProjectId opt-out。这个设计把多租户隔离放在 query builder 默认行为里。

排序和 ClickHouse primary key 贴合

orderByColumns 在按 start_time 排序时会补:

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

目的是贴合 events table 的排序/主键形状,减少无谓扫描。

4.6.6 events_coreevents_full 如何选择

EventsQueryBuilder 默认读 events_core。只有需要完整 I/O 或 metadata 时才读 events_full

场景
列表页 base 字段events_core
filter optionsevents_core
count / aggregate通常 events_core
详情页完整 input/outputevents_full
metadata expandedevents_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 的思路是:

  1. 先用 baseBuilderevents_core 里过滤、排序、分页;
  2. 把匹配到的 (start_time, trace_id, span_id) 作为 key;
  3. 再从 events_full 里只取这些行的 input/output/metadata;
  4. LEFT ANY JOIN 避免 ReplacingMergeTree duplicates 导致 fan-out。

这是 v4 查询性能设计里很关键的一点:重字段不是不能读,而是要在缩小候选集之后读。

4.6.8 为什么不能随便手写 SQL

绕开 query builder 容易漏掉:

  • project_id tenant filter;
  • ClickHouse 参数化 query;
  • field alias 和 domain field 的一致性;
  • events_core/events_full 表选择;
  • input/output/metadata 的轻重字段策略;
  • trace_id equality 的 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 写入新字段IngestionServiceClickhouseWriter record。
列表显示新字段EVENTS_FIELDSFIELD_SETS、events table column definition。
筛选新字段FilterState、search-bar field registry、filter lowering。
Public API 暴露字段public API schema、Fern、generated client。
需要 full payloadselectIO、metadata expansion 或 split query。

下一篇

第 5 篇 · 契约与抽象