Frontend Insight:一个页面数字怎样从事实算出来
解释管理数据与事件事实的分工、增量迁移、时间快照、读侧去重,以及缺失、观察值和零值为什么需要分别表达。
页面里已经能查到访问事件,旁边的使用率却还是空的。这不一定是计算漏跑了:事件属于哪个时间范围、该用哪一版定义、谁应该进入分母,都可能还没有确定。下面沿着一个页面数字的形成过程,看存储中的事实怎样变成可以展示的结果。
本文从数据所有权和迁移开始,追踪读模型怎样形成数值与不可计算原因。实现对应系列代码基线,包括诊断扩展后的存储;这里的读模型不特指预先落库的聚合表。
数据所有权
| 存储 | 当前保存什么 | 维护含义 |
|---|---|---|
| MySQL | 用户/会话/项目权限,模块/页面/功能/工作流定义,指标与评分版本,目录与可信使用源,设置/导出配置,接收/消费状态,诊断 receipt、授权、策略与删除墓碑 | 管理与治理事实;事务、版本和历史有效区间影响结果 |
| ClickHouse | raw_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 001 | v18_baseline | v1.8 空库基线;不能当作从 M5 原库自动升级的承诺 |
| MySQL 002 | r1c_score_snapshots | 评分配置、依赖快照、审核摘要与启用时段 |
| MySQL 003 | r2_project_creation | 创建项目请求的幂等记录,关联操作者和 requestId |
| MySQL 004 | r4b_workflow_admission | 工作流准入有效期,不能用今天的状态猜历史是否允许开始 |
| MySQL 005 | r4c_directory | 有版本、环境、有效期与覆盖声明的组织目录 |
| MySQL 006 | r4c_usage_sources | 可信采集源版本、release、范围和覆盖信息 |
| MySQL 007 | r7_settings | 项目探针策略、导出调用状态/限流窗口、异常规则版本 |
| ClickHouse 001 | v18_baseline | 原始事件及常用展开列、索引、90 天 TTL |
| ClickHouse 002 | r4c_directory | 事件关联目录版本 |
| ClickHouse 003 | r4c_repeated_operations | 对象短期材料、重复操作证明、SDK collector 列 |
| ClickHouse 004 | r4c_usage_source | SDK 使用覆盖诊断列 |
诊断扩展继续增加迁移,而没有重写上述历史文件:
| 引擎/编号 | 增量内容 | 对读取与恢复的影响 |
|---|---|---|
| MySQL 008 | 策略、read/export 授权、receipt、速率窗口、审计 | 基础错误存在时,详情仍可能 pending、失败或不可读 |
| MySQL 009 | receipt 的 capture_metadata | 不含 raw 正文的采集状态可单独保存 |
| MySQL 010 | 上传凭证与 SourceMap 目录 | 记录构建身份、摘要与文件状态,文件本体在私有卷 |
| MySQL 011 | 策略 revision、诊断删除墓碑、健康及维护状态 | 晚到写入不能复活已删除详情;旧备份可能带回旧授权 |
| ClickHouse 005 | diagnostic_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,不要把历史重建步骤套到已有数据上。
“读模型”在本项目是什么
这里说的读模型,是为某类查询组织数据和计算的过程,不全是预先建好的聚合表。多数请求会先取定义,再取事实,最后计算并解释结果:
- 从 MySQL 取定义版本、历史归属、启用窗口和权限。
- 从 ClickHouse 有界读取事实并去重。
- 用 TypeScript reducer 检查关联、完整性和口径,计算观察值。
- 绑定指标定义,形成
value/status/reason/sampleSize等输入;符合条件时才交给评分。 - 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_at | API 接收时刻 | 认为晚接收一定不属于较早的业务日期 |
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 已在已有数据上完成升级。修改表结构还需验证保留数据升级和恢复;指标如何进入正式分数见业务分析与评分。