Mastra ClickHouse vNext 可观测性设计解析:五大信号的表结构与查询契约 Mastra ClickHouse vNext 可观测性设计解析五大信号的表结构与查询契约【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra本文是 Mastra 开源仓库中 ClickHousev-next可观测性设计文档集observability/clickhouse-design的深度导读与实现佐证。文章以设计文档为主体逐层讲解span_events、trace_roots、metric_events、log_events、score_events、feedback_events六大核心表与 discovery 辅助表的物理形态、写入路径和查询契约并结合 v-next 实现源码 说明真实落地方式。读完本文你将掌握 ClickHouse 上 append-only 可观测性存储的核心取舍重试幂等、保留策略、发现子系统以及 MastraObservabilityStorage标准接口下的扩展路径。一、文档集定位设计先行的 v0 契约observability/clickhouse-design/README.md是整个设计文档集的入口entry point。它明确划分了职责边界跨表共享的横切决策集中在 shared.md每张表特有的行为物理形态、查询行为落在各自的 per-table 文档中trace-roots.mdspan-events.mdmetric-events.mdlog-events.mdscore-events.mdfeedback-events.mddiscovery.md具体列类型与可空性方向集中在 physical-types.md让 DDL 工作机械化而不是在实现时临时推断。文档集有一句重要的自我约束这是初始v-next实现的设计文档用于指导实现而不是实现与 DDL/查询代码存在之后的长期第二真相来源。一旦v-next落地代码与测试才是持续行为的权威来源。也就是说这份文档是一份带有明确时效性的契约快照。二、核心 v0 模型一次读懂的五大横切决策shared.md给出了v-next的全部跨表决策可以用五句话概括全部五类信号使用 append-only 表tracing 相关的span_events与trace_roots使用ReplacingMergeTree其余四类metrics、logs、scores、feedback使用普通MergeTree。tracing 采用 insert-only 路由只持久化已结束ended的 span 记录不保存 start 事件因此也没有基于 start/end 事件的重建路径——这是与 DuckDB 实现的有意分叉。tracing 具备重试幂等通过 tracing 专用的dedupeKey traceId || : || spanId实现metrics、logs、scores、feedback 在 v0 中故意不幂等重试可能产生重复行这是一个被文档明确接受的 v0 限制等待未来的 event-id 设计。共享上下文拓宽logs、metrics、scores、feedback 共享同一套实体层级、关联 ID、部署元数据与执行上下文列executionSource是执行上下文的物理列名scoreSource/feedbackSource是信号专有的 source 字段。trace 过滤语义刻意收窄v0 只支持基于metadataSearch的顶层字符串等值元数据过滤且不支持 trace 的scope过滤。文档明确强调这是有意的 v0 契约而不是偶然的实现缺口。此外还有几条容易被忽略但影响实现的决策score 与 feedback 的traceId可空允许它们在 trace 之外独立记录不新增物理createdAt/updatedAt列以天为粒度的保留期retention策略原始 ClickHouse DDL 是 schema 定义的来源而不是强行套用通用存储 schema 抽象。从源码看模型落地在 v-next/index.ts 文件头部的注释中实现者直接写明了模型的落地方向/** * Insert-only model: Uses ReplacingMergeTree for all signals * with dedupeKey for retry-idempotency. */而 第 332-338 行 展示了一个很实际的工程细节ClickHouse Cloud 会把ReplacingMergeTree重写为SharedReplacingMergeTree自管理复制集群会重写为ReplicatedReplacingMergeTree因此代码用isReplacingMergeTreeEngine统一接受三种引擎名避免在每次初始化时无谓地 churn 辅助表。三、Scope 边界与代码路径设计文档划定了严格的实施范围Scope理解这些边界有助于区分该做什么和明确不做什么目标环境是 Cloud ClickHouse 物理设计Mastra 运行时应继续通过标准存储接口DefaultExporter写入v-next围绕DefaultExporter使用的批量创建路径batched create path设计该路径之外的遗留可观测性方法预期会被废弃不应驱动v-next的设计决策之前的 DuckDB 或其他存储实现只能作为 parity对等性参考不是设计真相来源新代码应位于stores/clickhouse/src/storage/domains/observability/v-next/迁移、切换cutover与共存规划明确不在本设计范围内。预期的领域布局shared.md给出了v-next域的预期文件布局stores/clickhouse/src/storage/domains/observability/v-next/ index.ts ddl.ts metrics.ts tracing.ts trace-roots.ts logs.ts scores.ts feedback.ts discovery.ts filters.ts helpers.ts对照仓库现状index.ts与index.test.ts已存在且index.ts中已经实现/声明了batchCreateSpansL720、batchCreateLogsL890、batchCreateMetricsL923、batchCreateScoresL972、batchCreateFeedbackL1055以及observabilityStrategygetterL681与设计文档描述的标准接口形状一致。四、写入路径Write Path不变的标准流水线设计文档明确写入路径本身不改变仍然是四步运行时发出可观测性信号DefaultExporter批量收集事件exporter 调用可观测性存储域的相应batchCreate*方法ClickHousev-next通过标准存储接口持久化并查询这些记录。与策略属性相关的关键点在现有 exporter 实现中observabilityStrategy只影响 tracing 事件的路由metrics、logs、scores、feedback 始终是纯 create-only 批量写。v-next必须把observabilityStrategy暴露为权威策略属性DefaultExporter读取它不要把tracingStrategy当作主要集成面也不要提供tracingStrategy兼容别名。insert-only模式下正常运行时应让 started-span 记录不进入batchCreateSpans路径。batchUpdateSpans在v-next中应保持未实现正常 tracing 写只依赖 insert-only 的 create 路径。幂等与查询去重写路径在 ClickHouse 适配器内计算并持久化dedupeKey traceId || : || spanId读路径应保证每个dedupeKey只返回一行不能仅依赖后台ReplacingMergeTree合并v0 的重试幂等假设同一dedupeKey的重复写是字节级相同的 ended-span 行如果生产者用不同内容重试同一dedupeKey就违反了 v0 写契约。五、v0 Trace 行为只存已完成状态派生这是 tracing 部分最核心的语义shared.md与span-events.md保持完全一致只存储和返回已完成的spans 与 traces(traceId, spanId)是span_events与trace_roots中的逻辑行身份公共 API 中仍可能存在status running但 v0 对该过滤返回空行因为只存已完成行trace 状态从根行派生而不是存列status error即error IS NOT NULLstatus success即error IS NULLstatus running返回空不要从output推断 trace 状态trace 列表与根 span 过滤基于根行实现测试应**显式锁定无实时 running trace 可见性**这一行为。事件 span 的归一化事件 span 以零时长 span 存储当isEvent true且endedAt为 null 时持久化前归一化为endedAt startedAt事件 span 仍然从SPAN_ENDEDtracing 事件写出即使导出形状不带真实结束时间isEvent是判断该行是事件 span的规范读时指示器v0 不要求事件 span 读回来时endedAt null。读路径整形startedAt必须直接存储因为没有 started-span 行可供重建返回的 span 记录从metadataRaw重建metadata返回记录中createdAt startedAt、updatedAt null。六、共享字段规则命名、去重键与 JSON 载荷列命名约定表内无歧义时优先直接用公共字段名作为 ClickHouse 列名executionSource作为 tracing、logs、metrics、scores、feedback 共用的物理执行上下文列公共 trace 记录仍暴露source但物理存储列是executionSourcescoreSource/feedbackSource保留为 score 与 feedback 记录各自的信号专有列若未来需要存储侧改名保持显式且集中。Typed 查询热列 vs Information-only JSON设计上有一条清晰的分类原则需要过滤、分组或 discovery 支持的产品维度必须进 typed 列不要藏进 JSON。以下字段在 v0 中属于信息性 JSON 载荷留在热查询路径之外metadata、scope、costMetadatalogdataspanattributes、links、input、output、error、requestContext规则以 JSON 编码字符串存储保留任意 JSON 可序列化值形状包括标量与null写时 JSON 编码、读时 JSON 解码不用于 discovery 或分组requestContext仅供检查不参与过滤、搜索、discovery 或分组。tracing 是 metadata 的例外span_events.metadataRaw保留完整逻辑 metadata 载荷保真与响应重建span_events.metadataSearch是收窄后的 string-string 搜索面trace_roots沿用同样的metadataRaw/metadataSearch双列拆分作为根行的投影。查询相关的灵活字段v0 中保持查询相关性的灵活字段只有四个tagslabelsspan_events.metadataSearchtrace_roots.metadataSearch物理方向字段物理类型tagsArray(LowCardinality(String))labelsMap(LowCardinality(String), String)metadataSearchMap(LowCardinality(String), String)七、共享过滤与归一化规则过滤语义tags过滤使用contains-all包含全部语义labels过滤基于精确 key/value 对的 contains-all 语义除非某个端点特别说明v0 对tags/labels不隐含wildcard、regex、prefix、substring 或 fuzzy-match 语义。归一化规则字段归一化动作labelstrim 字符串值丢弃null、非字符串与空值tagstrim丢弃null、非字符串与空值行内重复 tag 去重metadataSearch只保留顶层值为非空字符串的条目丢弃null、非字符串、数组与对象metadataSearch 的 promoted-key 移除集写metadataSearch前必须移除以下规范 promoted-key 集合它们的值已经存在 typed trace 列中experimentId、entityType、entityId、entityName、userId、organizationId、resourceId、runId、sessionId、threadId、requestId、environment、executionSource、serviceName。需要特别强调的是metadataSearch只做顶层字符串等值过滤这是 v0 的 ClickHouse 契约不是保留其他后端更丰富的 JSON-path / 嵌套 metadata 查询行为。若未来 ClickHouse 原生 JSON 列实用化应重新审视该契约而不是无限扩张metadataSearch。不匹配即返回空对 trace metadata 的过滤如果命中非字符串值、嵌套值或未索引键应返回空行而不是抛错。scope过滤在 v0 中不受支持有意的 v0 ClickHouse 契约。八、物理类型方向从 shared 到 physical-typesphysical-types.md 把列类型决策固化为机械规则避免实现时逐列推断。核心共享约定事件与 span 时间戳一律DateTime64(3, UTC)必填文本标识符用String可空文本标识符用Nullable(String)除非显式标记为LowCardinality候选可空低基数维度用LowCardinality(Nullable(String))必填低基数维度用LowCardinality(String)布尔用Bool数值测量用Float64序列化 JSON 载荷用Nullable(String)tags默认[]、labels默认{}、metadataSearch默认{}不新增物理createdAt/updatedAt列一个容易踩坑的点被描述为序列化 JSON 载荷的列即使逻辑值是标量字符串、数字、布尔、null也要存其JSON 编码表示。LowCardinality 候选清单strong v0 候选entityType、parentEntityType、rootEntityType、environment、source、serviceName、metricname、provider。有意的 v0 决定不要 LowCardinalityentityId/entityName不设为 LowCardinalitymodel不设为 LowCardinalityfeedback_events.valueString/valueNumber不设为 LowCardinality。九、逐表设计六张核心表9.1span_events全 trace 表span_events是 v0 中 tracing 的写入目标与全 trace 读取表承担getTrace与getSpan。物理形态ENGINE ReplacingMergeTree PARTITION BY toDate(endedAt) ORDER BY (traceId, endedAt, spanId, dedupeKey)要点PARTITION BY toDate(endedAt)与 ended-span 存储模型对齐同时让天级 TTL 与分区过期变得实际可行ORDER BY (traceId, endedAt, spanId, dedupeKey)优先全 trace 读取与 trace 内点查同时把dedupeKey纳入替换身份每行都持久化dedupeKey使 tracing 写入可幂等重试读路径正确性不能只依赖后台合并tracing 查询仍要保证每个dedupeKey只返回一行。存储语义不存物理status列状态从error存在性派生事件 span 存为零时长 span只持久化已完成 span不存eventType每行代表该 span 的最终 ended 状态。查询契约getSpan按(traceId, spanId)过滤 普通LIMIT 1getTrace使用两阶段查询内层查询先收窄行集、做确定性 pre-dedupeORDER BY、再LIMIT 1 BY dedupeKey外层查询做最终 span 展示排序。不要用单层查询同时做去重与最终排序所有 trace 过滤除hasChildError针对根 span 求值hasChildError定义为同 trace 中任一非根 span 的error IS NOT NULL根 span 自身排除在外v0 不存专用列查询时计算。9.2trace_rootslistTraces 助手表trace_roots是 v0 的listTraces助手表不是通用根 span 缓存。它由span_events上的增量物化视图填充只投影parentSpanId IS NULL的行。物理形态ENGINE ReplacingMergeTree PARTITION BY toDate(endedAt) ORDER BY (startedAt, traceId, dedupeKey)要点ORDER BY面向默认listTraces读模式按startedAt排序分区与span_events同样基于endedAt使两表 tracing TTL 保持一致管理——这是有意的 v0 取舍分区裁剪不完全对齐 started-time 列表过滤但保留期对齐优先重试幂等假设同一dedupeKey的重复根行字节级相同删除、truncate、TTL 必须在trace_roots上显式管理不会从span_events传播物化视图不会自动传播。查询契约listTraces读trace_rootsgetRootSpan在 v0 可作为兼容路径读trace_rootsLIMIT 1但它不是该表的设计驱动未来可能废弃除hasChildError外的所有根 span 级 trace 过滤都在trace_roots上求值hasChildError存在时用span_events做子 span 存在性检查主列表源仍是trace_roots该检查不需要FINAL或独立去重重复子行不改变布尔结果listTraces计数查询必须从同一过滤去重后的内层查询计数而不是直接数trace_roots原始行batchDeleteTraces必须同时向trace_roots发出匹配的 lightweight deletedangerouslyClearAll显式 truncatetrace_roots。有意的 v0 限制无实时/运行中 trace 可见性无存储的hasChildError无 scope 过滤parentSpanId始终为null不做独立的 summary-only schema。9.3metric_events指标事件ENGINE MergeTree PARTITION BY toDate(timestamp) ORDER BY (name, timestamp)要点不存status列——指标发出时并不知道所在 trace/span 的最终终态name、实体类型字段、environment、executionSource、serviceName、provider是 strongLowCardinality候选labels用Map(LowCardinality(String), String)tags用Array(LowCardinality(String))。支持的查询操作batchCreateMetrics、listMetrics、getMetricAggregate、getMetricBreakdown、getMetricTimeSeries、getMetricPercentiles、指标 discovery。响应整形aggregate / breakdown / time-series 返回value加可选estimatedCost/costUnitpercentile 响应只返回 value。groupBy 行为容易被忽视的细节groupBy 键命中 typed 指标列 → 按 typed 列分组否则视为 metric-label 键按labels中的值分组typed 列与 label 键冲突时typed 列优先缺少请求 label 键的行从该 label 分组结果中排除metadata、costMetadata、scope不参与 groupBy。DiscoverygetMetricNames/getMetricLabelKeys读discovery_valuesgetMetricLabelValues读discovery_pairs而不是扫metric_events。9.4log_events日志事件ENGINE MergeTree PARTITION BY toDate(timestamp) ORDER BY (timestamp, traceId)要点ORDER BY (timestamp, traceId)是有意选择日志主要面向**recency-first最新优先**读取trace 关联读取受支持但不是log_events的主要物理设计驱动level、实体类型字段、environment、executionSource、serviceName是 strongLowCardinality候选tags用Array(LowCardinality(String))。查询契约listLogs支持当前公共日志过滤面tags可过滤data、metadata、scope留在行上但不参与discovery 或分组。v0 限制无日志可搜索 metadata map、无data过滤/分组、无scope过滤/分组。9.5score_events评分事件ENGINE MergeTree PARTITION BY toDate(timestamp) ORDER BY (traceId, timestamp)要点traceId应为Nullable(String)支持 trace 外的独立评分scoreSource、scorerId、scorerVersion是 strongLowCardinality候选ORDER BY (traceId, timestamp)有意偏向trace-scoped读取recency-first 全局列表仅作二级兼容/管理面可空排序列需要表 DDL 中启用对应的 nullable-key 设置。查询契约listScores支持timestamp、traceId、spanId、organizationId、experimentId、scorerId、scoreSource、executionSource过滤面reason只用于展示不参与过滤/搜索/discovery/分组typed 上下文字段从显式顶层记录字段写入不从 metadata 提升。v0 限制无 score metadata 搜索、无 queryablereason。9.6feedback_events反馈事件ENGINE MergeTree PARTITION BY toDate(timestamp) ORDER BY (traceId, timestamp)要点traceId可空支持 trace 外反馈feedbackSource/feedbackType是 strongLowCardinality候选逻辑feedback.value物理上拆成两个 typed 可空列valueStringNullable(String)与valueNumberNullable(Float64)恰好一个非空字符串值写valueString数值写valueNumberstring/number 之外的类型超出 v0 范围拆分 typed 存储是有意的未来要加数值排序/后过滤时无需重新设计物理表示sourceId是被链接源记录的标识符不是反馈类别反馈类别存在feedbackSource。查询契约feedbackSource、feedbackType可过滤其余过滤面timestamp、traceId、spanId、userId、organizationId、experimentId、executionSource直接从行支持读路径重建逻辑value时优先valueNumber非空时否则valueStringvalue、comment不参与过滤/搜索/discovery/分组。v0 限制无 metadata 搜索、无可搜索value、无可搜索comment。十、Discovery尽力而为的助手子系统discovery.md 定义了 discovery 的 v0 模型——它服务于 UI 的轻量 picker不是核心可观测性的启动依赖读两个专用助手表discovery_values唯一值与discovery_pairs键值对式查找两者都是普通表由refreshable materialized views可刷新物化视图维护不用 insert-time 物化视图增量喂入——刷新方式可以在 delete 与 TTL 过期后重算当前集合discovery 有意保持最终一致刷新能力不可用时将 discovery 标记为不可用而不能让基础可观测性适配器失败也不要在助手表不可用时静默回退到基表扫描首次成功刷新前discovery 方法返回空结果而非显式 unavailable 错误不要把 scores / feedback 为了对称硬塞进跨信号 discovery。物理方向discovery_values: ENGINE MergeTree, 无分区, ORDER BY (kind, key1, value) discovery_pairs: ENGINE MergeTree, 无分区, ORDER BY (kind, key1, key2, value)维度语义discovery_valueskindkey1valueentityTypeentityTypeserviceNameserviceNameenvironmentenvironmenttagentityTypetagmetricNamemetric namemetricLabelKeymetric namelabel key对语义discovery_pairskindkey1key2valueentityTypeNameentityTypeentityNamemetricLabelValuemetric namelabel keylabel value源映射只从span_events、metric_events、log_events三个源表生成不读trace_roots、score_events、feedback_events。key1/key2始终非空String无父键/次键维度时用哨兵发现值为NULL或空串时丢弃。刷新节奏产品默认值非硬性架构要求discovery_values每 1 分钟刷新支撑最常见的轻量 UI picker需要更频繁discovery_pairs每 5 分钟刷新体量更大、延迟不敏感。刷新查询形态每个源子查询投影kind、key1、valuepairs 多一列key2跨子查询UNION ALL外层SELECT DISTINCTtag 用ARRAY JOIN tags AS tagmetric label-key 用ARRAY JOIN mapKeys(labels) AS labelKeylabel-value 用labels[labelKey] AS labelValue。端点映射getEntityTypes、getEntityNames、getServiceNames、getEnvironments、getTags、getMetricNames、getMetricLabelKeys、getMetricLabelValues全部读助手表并按 kind 过滤、prefix在排序前应用、limit在排序后应用。引导与过期行为discovery bootstrap 可选可在适配器启动后进行创建助手表与 refreshable 视图后bootstrap 尽可能自动触发一次双表立即刷新bootstrap 成功要求首次刷新对两表都成功bootstrap 失败不应让基础适配器失败discovery 方法继续返回空结果直到后续刷新成功至少一次成功的 bootstrap 刷新后后续刷新失败应保留上一个成功快照而不是清空 discovery删除与 TTL 过期通过下次成功刷新反映到 discovery新鲜度受刷新节奏约束。十一、删除与保留lightweight deletes 天级 TTL删除行为batchDeleteTraces等删除类操作使用 ClickHouselightweight deletes假定删除最终一致dangerouslyClearAll使用TRUNCATE TABLEtrace 删除必须同时作用于span_events与trace_roots并按 tracing 身份含dedupeKey定位两表行增量物化视图不会自动传播删除或 truncate因此batchDeleteTraces要显式向两表发 lightweight deletesdangerouslyClearAll显式 truncate 两表读后即删read-after-delete不是 v0 的严格正确性保证删除路径测试验证成功执行 最终消失而不是立即不可见。保留行为TTL 按信号配置以天为增量tracing 保留期对span_events与trace_roots保持一致且配置相同天级分区是默认物理策略让天级过期与分区管理直接了当v0 针对信号级保留优化不支持保留某 trace 子集比源表更久像带评分的 trace 留 30 天、普通 trace 留 10 天这类未来需求明确超出 v0 范围未来可能需要相邻的 trace/span 保留表或归档/导出路径然后再丢源分区。十二、DDL 与助手结构raw DDL 三种助手使用v-next/ddl.ts中的原始 ClickHouse DDL不试图把Map(...)、Array(...)、LowCardinality(...)硬塞进当前通用存储 schema 抽象tracing 助手结构只有一种trace_roots 一条从span_events到trace_roots的增量物化视图discovery 助手结构有两种discovery_values、discovery_pairsrefreshable MV 维护hasChildError保持查询派生定义为trace 中任一非根 span 的error IS NOT NULL若未来成为实际性能问题优先引入 refreshable 的 trace 级助手结构而不是在span_events/trace_roots上做行内反规范化。十三、测试预期把风险契约锁进测试shared.md列出了最低测试覆盖清单README.md的 Rollout Order 也点名了风险契约点。综合如下每表写/读 happy pathtracing insert-only 路由只持久化 ended spanspan_events/trace_roots上基于dedupeKey的重试幂等后台合并完成前 tracing 读每个dedupeKey只返回一行trace_roots物化视图填充discovery 助手刷新行为与过期预期每表ORDER BY预期派生 trace 状态语义error/success/runningtracehasChildErrormetadataRawvsmetadataSearch每信号的精确过滤面共享归一化规则metrics 响应中混合costUnit行为删除最终一致性预期。从仓库现状看v-next/index.test.ts 已与index.ts同步存在作为实现侧的测试载体。十四、实施顺序Rollout OrderREADME.md给出了明确的落地顺序敲定 shared 与各 per-table 文档实现五张信号表、trace_roots、discovery_values、discovery_pairs及其物化视图的 raw DDL实现五类信号的写入与读取围绕风险契约点补充定向测试ended-span-only 持久化、重试去重、trace_roots填充、discovery 刷新与过期、每表排序、派生状态、hasChildError、metadataRawvsmetadataSearch、每信号精确过滤面、label/tag 归一化、删除最终一致性。同时文档强调整个文档集是关于steady-state v0 设计的不是过渡机制迁移、切换与共存规划不应阻塞 v0 实现工作。结语一份可执行的契约而不是空想纵观整套设计ClickHousev-next最值得借鉴的地方在于它的取舍显式化哪些能力 v0 不做running trace 可见性、scope 过滤、嵌套 metadata 搜索、非 tracing 信号幂等、JSON 发现哪些是权宜之计trace_roots保留完整根行而非极简 schema、hasChildError查询派生都写成了有意的契约而非默认行为。配合 v-next/index.ts 中observabilityStrategygetter、五个batchCreate*方法与isReplacingMergeTreeEngine等实现细节读者可以沿着文档 → 源码 → 测试的路径把这份设计完整落成可运行的 ClickHouse 可观测性存储域。【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考