ELL3N HSU

Available in Chinese only.

← 文章

Frontend Insight:一个页面数字怎样从事实算出来

解释管理数据与事件事实的分工、增量迁移、时间快照、读侧去重,以及缺失、观察值和零值为什么需要分别表达。

页面里已经能查到访问事件,旁边的使用率却还是空的。这不一定是计算漏跑了:事件属于哪个时间范围、该用哪一版定义、谁应该进入分母,都可能还没有确定。下面沿着一个页面数字的形成过程,看存储中的事实怎样变成可以展示的结果。

本文从数据所有权和迁移开始,追踪读模型怎样形成数值与不可计算原因。实现对应系列代码基线,包括诊断扩展后的存储;这里的读模型不特指预先落库的聚合表。

数据所有权

存储当前保存什么维护含义
MySQL用户/会话/项目权限,模块/页面/功能/工作流定义,指标与评分版本,目录与可信使用源,设置/导出配置,接收/消费状态,诊断 receipt、授权、策略与删除墓碑管理与治理事实;事务、版本和历史有效区间影响结果
ClickHouseraw_events、短期 object_operation_refs、repeated_operation_proofs、受限 diagnostic_details分析事实;去重、范围、采集完整性和查询预算影响结果
Kafka普通事件 envelope、短期对象操作消息、DLQ 元数据有保留窗口的消息日志;消费积压不是已经可查询的数据
私有文件卷SourceMap 文件;MySQL 保存身份、摘要和生命周期元数据文件、元数据、凭证与删除状态必须一起备份和恢复

CoreService 展示 API 实际建立的服务和连接;生产 Compose 展示运行依赖。不要用一份历史架构讨论推断当前数据库拓扑。

ClickHouse raw_events 是 MergeTree,按 received_at 月分区,排序键包含 app、env、接收日期、事件、路由、事件时间和 eventId,TTL 为接收时间后 90 天。排序键不是唯一键,写入不自动去重。项目配置里出现某个保留天数,不意味着已经动态改变这张表的 TTL。

短期通道迁移 中,对象材料为 48 小时 TTL,重复证明为 90 天 TTL。TTL 清理是后台过程,不承诺到了秒点立刻物理删除;投影/查询还必须执行时间限制,不能只依赖后台删除。

增量迁移怎样保护已有数据

MySQL 与 ClickHouse 各自编号,MySQL 004 与 ClickHouse 004 没有一一对应关系。

引擎/编号文件主要目的
MySQL 001v18_baselinev1.8 空库基线;不能当作从 M5 原库自动升级的承诺
MySQL 002r1c_score_snapshots评分配置、依赖快照、审核摘要与启用时段
MySQL 003r2_project_creation创建项目请求的幂等记录,关联操作者和 requestId
MySQL 004r4b_workflow_admission工作流准入有效期,不能用今天的状态猜历史是否允许开始
MySQL 005r4c_directory有版本、环境、有效期与覆盖声明的组织目录
MySQL 006r4c_usage_sources可信采集源版本、release、范围和覆盖信息
MySQL 007r7_settings项目探针策略、导出调用状态/限流窗口、异常规则版本
ClickHouse 001v18_baseline原始事件及常用展开列、索引、90 天 TTL
ClickHouse 002r4c_directory事件关联目录版本
ClickHouse 003r4c_repeated_operations对象短期材料、重复操作证明、SDK collector 列
ClickHouse 004r4c_usage_sourceSDK 使用覆盖诊断列

诊断扩展继续增加迁移,而没有重写上述历史文件:

引擎/编号增量内容对读取与恢复的影响
MySQL 008策略、read/export 授权、receipt、速率窗口、审计基础错误存在时,详情仍可能 pending、失败或不可读
MySQL 009receipt 的 capture_metadata不含 raw 正文的采集状态可单独保存
MySQL 010上传凭证与 SourceMap 目录记录构建身份、摘要与文件状态,文件本体在私有卷
MySQL 011策略 revision、诊断删除墓碑、健康及维护状态晚到写入不能复活已删除详情;旧备份可能带回旧授权
ClickHouse 005diagnostic_details信封按项目/eventId 定位,有独立到期时间

diagnostic_details 使用 ReplacingMergeTree,不能把后台合并或 TTL 当同步删除承诺。受限读取还会检查 receipt、到期与墓碑;raw_events 不保存诊断正文。删除详情不删除统计事实,详见诊断生命周期。

discoverMigrations 按三位编号排序,检查重复编号,并为 SQL 计算 SHA-256。每个数据库都有 schema_migrations。执行过的 SQL 校验和变化会报错,因此修复线上结构应新增迁移,不能改旧文件后假定所有环境都会得到相同结果。

MySQL runner 使用迁移锁;ClickHouse runner 按 -- statement-breakpoint 分隔执行。它们不是跨数据库原子事务,SQL 执行失败也不等于此前 DDL 自动回滚。迁移事故要先检查已执行语句与 migration 记录,再制定恢复措施,不能反复 reset 生产库。

根命令 pnpm migrate 和 pnpm migrate:verify 会连接配置中的真实数据库,执行前需要确认连接目标。保留数据的本地更新按 当前验收指南 使用 ./scripts/dev up-preserve,不要把历史重建步骤套到已有数据上。

“读模型”在本项目是什么

这里说的读模型,是为某类查询组织数据和计算的过程,不全是预先建好的聚合表。多数请求会先取定义,再取事实,最后计算并解释结果:

  1. 从 MySQL 取定义版本、历史归属、启用窗口和权限。
  2. 从 ClickHouse 有界读取事实并去重。
  3. 用 TypeScript reducer 检查关联、完整性和口径,计算观察值。
  4. 绑定指标定义,形成 value/status/reason/sampleSize 等输入;符合条件时才交给评分。
  5. API 返回解释信息,Vue 负责展示数值、缺失、观察状态与原因。

这样,数值不符合预期时,可以分别检查 SQL 是否选对数据、纯函数是否按约定计算,以及绑定层是否把事实对应到正确指标。各文件的实现并非完全一致,实际排查仍需从那张卡片的入口往下追。

读模型入口最重要的不变量
项目概览project-overview、overview-facts同一项目/环境/日历范围下组合事实与分数,不制造统一总分
功能与业务business-facts、business-analysis功能类型、模块页面归属和历史有效窗口参与计算
工作流workflow-reducer、workflow-facts显式实例关联、定义版本、步骤顺序、终态和超时
效率/组织efficiency-facts、organization-facts完整采集、可信目录、采样与有效分母,不能由观察人数反推应使用人数
页面运营page-usage、page-usage-binding先选范围再按原 session 归组,页面筛选不能制造跳出
质量评分事实quality-facts、quality-binding页面起点 cohort、质量采集能力、完整结算和迟到窗口
错误明细page-quality已观察错误的去重、分组和稳定分页;不声称错误覆盖完整
重复操作率repeated-read-model开始事实、投影证明和快照一致,未闭合窗口不能输出正式值

时间有四种,必须分开看

字段/概念含义常见误用
timestamp业务事件发生时刻当成 API 接收到消息的时刻
received_atAPI 接收时刻认为晚接收一定不属于较早的业务日期
asOf本次查询可见事实的快照边界以为改变 asOf 就扩大了 from/to
项目时区的自然日/周/月将 UTC 时刻解释为业务日历桶固定减 24 小时替代全部日历运算

resolveProjectCalendar 统一项目范围:[from,to) 左闭右开,预设范围包含今天,周一作为周开始,按项目时区形成日/周/月桶。范围末尾可能是未完成桶;需要完整自然周期的指标不能把它当已闭合数据。时区转换还考虑夏令时重叠和不存在的本地时间,因此不应在页面另写一份日期算法。

不同 API 有不同事实窗口限制。错误明细支持页面上的 7/30/90/180/365 天与自定义选择,但原始存储只有其保留数据;质量事实 endpoint 本身还限制最多 89 天,并要求不查未来。一个范围能被路由接受,不意味着每个指标都具备完整历史覆盖。

错误明细的快照分页

PageQualityStore.read 同时限定:项目、env、timestamp >= from && timestamp < to、received_at <= asOf。SQL 按 eventId 去重;qualityOccurrencePage 再做保护、筛选与排序。默认 latest 每个错误组展示最新一次;all 展示全部去重后的 occurrence。

cursor 包含筛选 scope 摘要、asOf 和上一页末尾位置,并使用 HMAC 签名,一小时过期。切换环境/日期/类别后复用旧 cursor 会失效。查询设有 50,000 条结果预算及执行/扫描限制;超预算时应缩小范围,不要删除 LIMIT 后把管理台变成无限扫描接口。

R8 的显式刷新会推进预设日期范围并清理 cursor;自定义范围保持不变。浏览器后退/重载依然可保留 URL 的固定快照,这与用户主动刷新是不同动作。

身份、组织和维度不是可随意补齐的标签

pageUsageWindow 的 UV 身份为登录用户优先(u:),否则设备(d:);VV 是原 session 去重。未登录设备不能换算成唯一员工数;中途登录可能使身份口径发生变化,产品应保留其真实含义。

organization-directory.ts 按环境、发布时间、事件时间和有效期选择目录。后发布版本替代前版本,即使后来版本已过期也不能“复活”更早的目录来凑分母。目录条目中的 eligible 决定合格人群;客户端部门声明会被服务端覆盖。

组织渗透计算需要可信目录、完整自然桶、历史目录一致性、可信使用源与采集覆盖等证据。当前 organization-facts.ts 还有小组规模及有效样本门槛。看到被抑制、观察或缺失状态,不应靠扩大 join、使用当前目录重写历史、把未知成员塞进某部门来修复页面。

env 是事实隔离边界;release、页面、模块、组织、浏览器等维度有各自允许的组合和口径。评分查询身份在 scoreQueryIdentity 中包含上下文,不是仅按项目缓存一个结果。新增维度需要同步事实读取、分母、历史归属、绑定和缓存/快照身份,不能只给 SQL 加 GROUP BY。

缺失、观察值和零值

以质量事实为例,reduceQuality 先选择 [from,to) 内开始的 page view,再关联终结与请求事实。未启用对应 collector、未登录、页面未分类、已知传输损失、冲突、终态缺失、迟到窗口未关闭,都可能阻止正式计算。

例如,只有错误事件时,系统知道发生过哪些错误,却不知道总共发了多少请求,因而无法计算可靠的请求失败率。错误明细本身也不要求每条错误都有 qualityVersion。所以列表可以有内容,而质量分数仍然缺少计算条件。

返回信息应怎样理解错误处理
value: 0 且状态可用定义、覆盖、分母、窗口等条件允许,结果确为零随意改成“无数据”
value: null + reason当前无法按正式定义计算前端 value ?? 0 后照常评分
observedValue 有值、正式值为空存在观察证据,但完整性/成熟度等未满足把观察值作为正式评分输入
错误明细 no_data当前筛选与保留范围内没观察到错误宣称系统零错误、稳定性满分
API 失败查询没完成清空数据并伪装成真实零值

readRepeatedRate 是一个易读例子:它返回 numerator、denominator、observedValue、value、reason 与 asOf;投影不完整、实例冲突、保留范围不够、窗口未成熟、少于 5 个相关 session 都阻止正式值。每个指标的 reason 和状态枚举可能不同,不要把此门槛机械套给全部指标。

两个数字例子:范围正确仍不代表分母充分

假设事件发生在 10:05,API 在 10:07 接收;查询事件区间是 [09:00,10:00),快照 asOf 为 10:10。它仍不在结果中,因为事件时间没有落入区间。只把快照改到 10:20 不会改变这一点;预设范围显式刷新时需要重新计算 from/to,自定义历史范围则应保留原边界。

另一个场景是部门 A 昨天有 8 人访问。8 只能作为去重后访问人数的候选分子。若可信目录给出 20 名合格成员,且目录覆盖、采集覆盖、闭合桶和其他门槛都满足,才可计算 8 / 20 = 40%。如果目录未知,再把观察到的 8 人放进分母,就会得到 100%,却完全没有回答还有多少人应当使用。此时缺少的是计算依据,调整前端显示格式解决不了这个问题。

还有一个容易藏在正确范围里的问题:同一事件因重试写入两行,分子也不能跟着翻倍。错误列表、质量 reducer 和页面使用各自有去重入口,冲突诊断策略却不完全相同;新增读模型需要验证重复、乱序、同 ID 内容冲突,不能因其他查询做过去重就省略自己的检查。

对应单元入口:

pnpm exec vitest run packages/event-contract/test/project-range.test.ts packages/server-core/test/page-quality.test.ts packages/server-core/test/page-usage.test.ts packages/server-core/test/quality-facts.test.ts packages/database/test/migrations.test.ts

它们检查范围、去重、分页、事实归约和迁移约束,不证明新 SQL 已在已有数据上完成升级。修改表结构还需验证保留数据升级和恢复;指标如何进入正式分数见业务分析与评分。