系统设计 E03:指标日志平台的基数与保留代价
指标平台常在查询量还不大时就被标签组合拖垮。一个请求计数器加上 user_id,不是多存一个字符串,而是可能把六十条时间序列变成六万条。保留期和降采样又会改变哪些历史问题还能回答,平台设计不能只报“每天写入多少 GB”。
本篇限定为内部服务指标与结构化日志,支持服务健康查询、地域汇总和单请求排查。排除完整 APM、全文搜索产品复刻与真实用户数据。写入确认只承诺进入约定持久层,查询需返回数据时间范围及缺失信息;已过期原始数据不得用降采样结果冒充原始精度。
先固定查询集,再选数据布局
三类查询决定三条访问路径:查询某服务最近一分钟最大延迟,需要细粒度窗口值;查询各地域总请求量,需要按有限标签聚合;查询 request_id 的错误日志,需要可定位明细。把三类数据都放同一套标签系统,会让单请求排查所需高基数标识污染常驻指标。
指标模型为 metric_name, labels, timestamp, value,标签集合标识一条序列;日志模型为 timestamp, service, level, request_id, body。写接口 POST /metrics/batch、POST /logs/batch 返回批次受理结果,并区分全部成功、部分拒绝和可重试失败。查询接口强制时间范围与结果上限,不能让一条忘记时间条件的查询扫完整保留期。
Prometheus 命名与标签实践提醒每一种标签组合形成独立序列,避免无界标签。此处借用概念,不把模型算量当成 Prometheus 实测;日志也不通过 request_id 标签复制成指标。服务、地域、有限状态集合可以聚合,用户 ID 与任意 URL 路径应进入明细字段或经过归一化。
教学服务目标设为指标查询 p99 两秒内、日志最近十五分钟查询 p99 三秒内,入口批次确认 p99 200 ms;原始指标保留七天、分钟聚合三十天、日志七天。目标只对带时间范围的规定查询集成立,不承诺任意高基数聚合和全年全文检索同样快。
基数乘法与样本数量
四个服务、三个地域、五种结果状态,基础组合 4×3×5=60。如果每种组合再加入一千个用户,得到六万条序列。每十五秒一个样本,七天样本数 60000×86400/15×7=2419200000;每个时间戳和值的教学预算 16 B,原始数据约 38.7072 GB。两份约 77.4144 GB,未计标签索引、块元数据、WAL、压缩与对象存储开销。
低基数六十条仅为其千分之一。标签总量增加还会扩大内存索引与写路径查找成本;不能只按磁盘压缩比认为问题已经消失。若 user_id 每天有新的取值,活跃序列数与保留期内曾出现序列数也不同,应分别统计。标称支持六万活跃序列不代表七天 churn 后的索引仍然可控。
若把采样间隔从十五秒改成五秒,样本量和基础写速率约三倍,序列基数不变;若只加 user_id,采样频率不变但序列增长千倍。两种增长需要不同处置:前者评估时间精度,后者审查维度是否应成为标签。接口应在接入时检查标签个数、值长度、保留白名单与每租户序列预算,不能等存储崩溃后再清理。
flowchart LR
A[应用指标] -->|有限标签批次| I[接入校验与租户限额]
L[结构化日志] -->|含 request_id 的明细| I
I -->|确认前持久化| W[(写入日志)]
W -->|时间序列样本| T[(原始指标块)]
W -->|日志明细| O[(日志分段存储)]
T -->|分钟 sum count min max| D[(降采样块)]
Q[有范围的查询规划] -->|按时间精度选层| T
Q -->|长期趋势| D
Q -->|请求定位| O
平均值能消除需要寻找的故障
实验构造一分钟六十个样本,其中五十九个为 1,最后一个为 101。平均值是 160/60≈2.667,最大值是 101。若只保存一分钟平均值,查询“有没有超过 100 的瞬时峰值”会得到错误的否定。降采样不是无损压缩,应由未来查询需要的信息决定保留内容。
保留 sum 与 count 可以合并计算不同窗口平均值;只保留平均值而丢 count,样本数不等的窗口合并时又会出错。保留 min/max 能回答极值,却无法恢复发生时间、持续长度或分布。要回答百分位,应选择合适的直方图或可合并摘要,并声明精度;不能从平均值和最大值推导 p99。
计数器还需处理重启后的归零。对累计 counter 的窗口增量要考虑 reset,不能直接把每个采样值求和当作请求数。Prometheus 查询函数文档给出相应函数行为;真实使用时应冻结版本与指标类型。本篇模型未运行 PromQL,只验证聚合信息损失,因此没有性能或兼容性结论。
sequenceDiagram
participant R as 原始样本层
participant D as 分钟聚合层
participant Q as 历史查询
R->>D: 59 个 1 和 1 个 101
D->>D: 保存 avg=2.667
R->>R: 七天到期删除原始样本
Q->>D: 查询是否出现过大于 100
D-->>Q: 仅平均值无法回答
Note over D,Q: 增存 max 可回答极值,仍不能还原发生时刻
确认、背压与查询隔离
最小系统可以先采用批量文件或现有时序存储,不必自制索引引擎。接入先验证批次大小与租户配额,再写持久队列或日志,异步形成查询块。若仅写入进程内队列便确认,崩溃可能丢失已确认指标;这可以是明确的尽力采样契约,但不能标为可靠审计日志。
写入被限流时,客户端采用有界缓冲、批量与退避。缓冲满后的丢弃策略应区分普通调试日志、错误日志与审计事件。无界缓冲会在平台不可用时把业务进程内存也耗尽;全部阻塞又可能让观测系统成为业务故障源。实际选择依赖数据用途,不存在所有日志都采用同一可靠性等级的必要。
查询按租户限制并发、时间跨度和扫描字节预算。长期趋势查询可走降采样块,单请求明细只访问对应时间分段;高成本探索查询应与告警查询隔离,避免一次全量聚合拖延健康判断。查询缓存还要区分已封存历史块与仍接受迟到样本的近实时窗口。
保留策略先标记时间段到期,再清理对象与索引引用。若只删除数据对象却保留索引,查询会不断访问不存在块;若先删除索引,底层对象可稍后回收,但需有孤儿核对。恢复时也不能重新导入本应过期的敏感日志。副本不是长期归档,备份恢复需遵守保留边界。
用固定问题验收平台能力
运行 python3 examples/system-design/labs/E03/run.py:固定维度生成六十与六万条组合,计算七天 24.192 亿样本、38.7072 GB 原始预算;一分钟平均值为 2.667、最大值为 101。原始保留窗口之外的明细查询被标为不可用,不能返回聚合数据却不声明精度。原始输出和版本在 examples/system-design/evidence/E03/。
实验是集合枚举、算量和降采样反例,没有搭建指标数据库、日志索引或真实负载。因此它证明基数与信息损失的关系,不能证明两秒查询目标或 16 B 实际压缩后占用。后续选定产品后,应把同一固定查询集、标签分布和 churn 输入用于真实组件测试,再比较磁盘、常驻内存与尾延迟。
面试追问:保留期翻倍时哪些开销线性增长、哪些索引可能更差;一分钟平均值能否用于尖峰告警;每用户精细诊断是否必须放进指标标签。回答需要返回访问模式与精度契约,不能只有“加分片”。
[PATTERN] 维度决定序列数,频率决定样本数,保留与聚合决定还能回答哪些问题。先约定查询,再为必需信息付费。
实验附件与导航
可运行实验源码 · 本次原始结果系列导读;容量和数据承诺分别沿用系列的方法,本文数字为独立教学假设。






