前端监控(RUM)时序存储架构:Sentry、Datadog RUM 与 Grafana Faro
前端监控(Real User Monitoring, RUM)与后端监控的根本差异在于数据模型:RUM 采集的是带海量高基数属性的事件流——每个用户访问产生 session_id、user_id、page_url 等上百个维度,传统时序数据库(TSDB)的 label-set 模型在此场景下会剧烈退化。
本文分析三套主流 RUM 方案的时序存储设计:Sentry(开源全能型)、Datadog RUM(SaaS 标杆)、Grafana Faro(Grafana LGTM 体系的开源扩展)。三者的共同趋势是复用列式 OLAP 而非 TSDB。
新手速查 — RUM 是什么?
RUM 就像在用户手机上装了一个隐形调研员,记录每一次点击的等待时间、页面渲染的每一帧延迟。它自动跟踪用户访问的每个环节:
- 页面加载慢吗? → LCP(最大内容渲染时间)告诉你
- 布局会突然跳动? → CLS(累计布局偏移)告诉你
- 点击后没反应? → INP(交互响应时间)告诉你
和后端监控不同,RUM 关心的是用户真实的端到端体验——不只是服务器响应了 200ms,而是从用户点击到页面完全呈现的实际耗时。
RUM 数据采集管道
RUM 数据的完整采集链路从用户浏览器开始,经过 SDK 采集、网络上报,最终落入后端存储。虽然 Sentry、Datadog、Grafana Faro 的存储方案各有特色,但采集管道的前半段高度一致。
动画说明:上方动画逐步展示 RUM 数据从用户浏览器到后端存储的完整管道。首先用户浏览器页面加载触发 Performance API 事件(蓝色),Web Vitals SDK 自动捕获 LCP、CLS、INP 等指标(橙色),通过 sendBeacon 或 fetch 批量上报(紫色),最终落入列式 OLAP 存储(绿色)。所有主流 RUM 方案共享此采集范式,差异在后端存储选型。
Sentry
整体数据流架构
Sentry 的数据管道是多阶段流处理架构,事件从浏览器 SDK 发出到最终聚合查询,经历以下路径[R1]:
flowchart TD
A["Browser SDK"] -->|HTTP POST| B["Nginx<br/>反向代理"]
B -->|proxy pass| C["Relay<br/>事件验证 + 限流"]
C -->|produce| D["Kafka<br/>envelope topic"]
D -->|consume| E["IngestConsumer<br/>预处理 + 符号化"]
E -->|store raw| F["Nodestore<br/>PostgreSQL"]
E -->|produce| G["Kafka<br/>events topic"]
G -->|consume| H["SnubaConsumer<br/>批量写入"]
H --> I["ClickHouse<br/>列存 + 时间分区<br/>10-30x 压缩"]
style A fill:#2196F3,color:#fff
style B fill:#FF9800,color:#fff
style C fill:#FF9800,color:#fff
style D fill:#9C27B0,color:#fff
style E fill:#FF9800,color:#fff
style F fill:#795548,color:#fff
style G fill:#9C27B0,color:#fff
style H fill:#FF9800,color:#fff
style I fill:#4CAF50,color:#fff图中蓝色为客户端,橙色为流处理组件,紫色为 Kafka 消息总线,棕色为持久化存储,绿色为最终 OLAP 引擎。IngestConsumer 是关键分叉点——原始 payload 写入 Nodestore(Postgres)做长期归档,结构化事件经 Kafka events topic 进入 SnubaConsumer → ClickHouse 链路做实时分析。
存储矩阵[R1]:
| 存储 | 用途 |
|---|---|
| PostgreSQL | Nodestore(原始事件 payload)、Sentry 业务模型 |
| ClickHouse(经 Snuba) | 事件搜索、span、性能指标、聚合查询的主力 OLAP 存储 |
| Redis | Celery 任务队列、热点 issue 缓存、限流计数器 |
| Kafka | 事件流总线 |
| Blob Storage | 附件、Replay 段、符号文件 |
Snuba:ClickHouse 抽象层
Snuba 是 Sentry 自研的事件存储与查询服务,以 ClickHouse 为后端,替代了早期基于 Postgres 的 Search/Tagstore 和基于 Redis 的 TSDB[R5]。
为何从 Postgres/Redis 迁移到 ClickHouse[R5]:
- 数据基本不可变,MVCC 安全机制反而拖累性能
- 新增查询维度需对 Postgres 反规范化并全量回填,耗时数月
- 过期数据清理需昂贵批量删除
Snuba 设计要点[R5]:
- 读写分离:查询与写入走不同 ClickHouse 集群
- 批量写入:攒批后一次性提交,减少 merge 压力
- 时间分区:按天/月分区,过期数据直接 drop 分区
- PREWHERE 子句:预过滤跳过大量无关数据块
- Redis 查询缓存:热点查询结果缓存
ClickHouse 列存 + 主键排序使 Tagstore 数据从 TB 级压缩到 GB 级——列存对高基数标签的天然压缩优势。
Issue 去重与聚合机制(Grouping)
Sentry 的去重是工程精华。三套系统依次运行[R2]:
normalize_stacktraces_for_grouping:应用堆栈规则,调整帧的in_app标志- 服务端 fingerprint 规则:可覆盖默认 fingerprint
- 分组算法(仅当 fingerprint 未设或含
{{ default }}时运行)
按平台的堆栈分组策略[R2]:
- Python:每帧贡献
module+function+context-line(源码行) - JavaScript:
module+filename(basename 小写) +context-line,因 source map 函数名不稳定故弃用 function 名 - Native:主要用清理后的
function名
Frame Hiding:对非应用代码帧标记"out of app",仍生成完整哈希以支持隐式合并[R2]。
Session Replay 存储
Sentry Replay 基于 rrweb 技术栈构建[R7]。Replay 数据以 段(segment) 形式存储于 Blob/对象存储,与 error 系统深度集成(可通过 error_id 关联重放)。优化策略包括动态采样和自适应压缩[R7]。
高基数处理
Sentry 工程团队指出:高基数聚合会通过内存耗尽拖垮性能[R6]。ClickHouse 列存将高基数字段(如 trace_id、user_id)视为普通压缩列,不引发索引膨胀,辅以 bloom filter 等轻量二级索引做点查[R6]。
这是 Sentry 选择 ClickHouse 而非 TSDB 的核心原因之一——列式存储对高基数天然友好,而传统 TSDB 的倒排索引在高基数下会指数级膨胀。
Datadog RUM
分层事件模型
Datadog RUM 的核心是分层事件模型[R3],各事件类型以树形层级关联,具有不同的保留策略:
flowchart TD
S[Session<br/>30天保留] --> V[View<br/>30天保留]
V --> R[Resource<br/>15天保留]
V --> L[Long Task<br/>15天保留]
V --> E[Error<br/>30天保留]
V --> A[Action<br/>30天保留]
style S fill:#9C27B0,color:#fff
style V fill:#2196F3,color:#fff
style R fill:#4CAF50,color:#fff
style L fill:#4CAF50,color:#fff
style E fill:#FF9800,color:#fff
style A fill:#FF9800,color:#fff上图展示了 Datadog RUM 的事件层级:每个 Session 包含多个 View(页面访问),每个 View 下关联 Resource、Long Task、Error、Action 等子事件。Session(紫色)和 View(蓝色)保留 30 天,Resource 和 Long Task(绿色)保留 15 天,Error 和 Action(橙色)保留 30 天。
| 事件类型 | 保留期 | 说明 |
|---|---|---|
| Session | 30 天 | 用户会话,唯一 session.id。15 分钟不活动后重置,4 小时活动后结束 |
| View | 30 天 | 每次页面访问生成,关联其下所有 resource/long-task/error/action |
| Resource | 15 天 | 图片/XHR/Fetch/CSS/JS 加载,含详细 timing |
| Long Task | 15 天 | 阻塞主线程 > 50ms 的任务 |
| Error | 30 天 | 浏览器发出的所有前端错误 |
| Action | 30 天 | 用户交互(点击等),可自定义 |
层级关系:Session → View → (Resource, Long Task, Error, Action),通过 session.id / view.id 串联[R3]。
Web Vitals 采集与存储
View 事件直接承载 Core Web Vitals 字段[R3]:
| 字段 | 说明 |
|---|---|
view.largest_contentful_paint(LCP) | 最大内容渲染 |
view.interaction_to_next_paint(INP) | 交互到下次绘制 |
view.cumulative_layout_shift(CLS) | 累计布局偏移 |
view.first_contentful_paint(FCP) | 首次内容渲染 |
view.first_input_delay(FID) | 首次输入延迟 |
view.first_byte(TTFB) | 首字节时间 |
Resource 事件基于 Performance Resource Timing API,采集 duration、size、connect.duration、ssl.duration、dns.duration 等[R3]。
URL 自动分组:view.url_path_group 将 /dashboard/123 与 /dashboard/456 归为 /dashboard/?,有效控制 URL 维度基数[R3]。
后端存储架构
Datadog RUM 的后端存储采用双引擎策略[R6][R9][R10]:
- Husky:Datadog 自研的列式存储引擎,事件以 fragment 为单位存于对象存储(S3/GCS),查询时按元数据发现相关 fragment,分发到 worker 池并行扫描后合并[R9]。本质是存算分离的列式 OLAP。
- RTDB(实时时序数据库,第六代):用 Rust 构建,摄取管道基于 Tokio 异步任务[R10]。
Datadog 的设计哲学是宽事件列存 + 存算分离,而非传统 TSDB 的 label-set 模型,以天然承载高基数 RUM 数据[R6][R9]。Husky 负责历史数据的长周期存储与离线查询,RTDB 负责实时摄入与近实时查询,二者互补。
Grafana Faro
定位与架构
Grafana Faro 是 Grafana Labs 主导的开源前端可观测性 Web SDK,核心思路是复用 Grafana 既有 LGTM 技术栈(Loki 日志 / Grafana 可视化 / Tempo 追踪 / Mimir 指标),而非自建专用存储[R4]。
数据流[R4]:
- Faro Web SDK 集成到应用,采集数据并提交到 collector
- Collector 接收 SDK 数据,分别写入 Loki(日志/异常)与 Tempo(追踪)
- Grafana 可视化 RUM 数据,并与后端/基础设施数据关联
存储后端特性
Faro 不引入新的存储引擎,而是将 RUM 信号映射到既有 LGTM 组件[R4][R8][R17]:
- Loki:只索引标签不索引日志内容,适合结构化 JSON 日志[R8]
- Tempo:微服务架构,trace span 排序为 Apache Parquet schema 列后写入对象存储[R4][R17]
- Mimir:Prometheus 兼容的指标存储,承载 Web Vitals 指标[R4]
设计权衡
Faro 的最大特点是不自建专用时序存储,而是把 RUM 信号映射到既有可观测性后端。优势是全栈开源关联,无需额外运维存储组件;代价是 RUM 事件模型不如 Datadog 那样原生统一——前端 Error 以日志形式存 Loki,性能数据经 Mimir 走指标路径,追踪数据走 Tempo,跨信号关联需依赖 Grafana 的统一查询层。
通用 RUM 技术范式
浏览器数据采集技术
所有主流 RUM 方案共享相同的数据源层[R3][R14][R15]:
- Performance API:Navigation Timing、Resource Timing、Paint Timing(FCP/LCP)、Event Timing(INP/FID)、LayoutShift(CLS)是所有 RUM 方案的数据源[R3][R14]
- Web Vitals 库:统一采集 LCP、INP、CLS、FCP、TTFB[R15]
- 上报通道:
navigator.sendBeacon(页面卸载时可靠上报)、fetch/XHR(常规批量上报)[R15]
flowchart TD
A[Browser<br/>Performance API] --> B[Web Vitals 库]
B --> C[sendBeacon / fetch]
C --> D[Collector]
D --> E[列式 OLAP<br/>非 TSDB]
style A fill:#2196F3,color:#fff
style B fill:#2196F3,color:#fff
style C fill:#FF9800,color:#fff
style D fill:#FF9800,color:#fff
style E fill:#4CAF50,color:#fff上图展示了 RUM 数据从浏览器到存储的通用路径:浏览器 Performance API 提供原始性能数据,Web Vitals 库(蓝色)封装标准化采集,通过 sendBeacon 或 fetch(橙色)上报到 Collector 层,最终写入列式 OLAP 而非传统 TSDB。这个范式已被所有主流 RUM 方案采用。
Web Vitals 测量时间线
Web Vitals 各指标在页面加载的不同阶段被测量。理解其出现顺序对排查性能瓶颈至关重要。
动画说明:上方动画在页面加载时间线上依次展示 Web Vitals 核心指标的测量时机。FCP(蓝色)最先出现,浏览器首次渲染任何内容;LCP(橙色)在主要内容元素渲染完成时触发;CLS(红色)在整个页面生命周期中持续监测布局偏移;用户首次交互触发 INP(紫色)测量。了解各指标的测量时机,有助于准确诊断性能瓶颈。
时序数据存储:为何弃用 TSDB
RUM 数据本质是带海量高基数属性的事件流,而非纯数值时序。传统 TSDB(如 Prometheus)的 label-set 模型在高基数下会"标签爆炸"——每加一个 user_id 标签,百万用户即产生百万独立 series[R6]。
因此主流 RUM 后端不复用纯 TSDB,而是采用列式 OLAP 存"宽事件":
- 列存 + 主键排序 → 10-30x 压缩
- 高基数字段只是普通压缩列,不引发索引膨胀
- 向量化执行 + 多核并行 → 亚秒级聚合
这是 Sentry(Snuba/ClickHouse)、Datadog(Husky)、乃至 Grafana Faro(Loki+Tempo)的共同选择[R5][R6][R9]。
采样与上报策略
为控制数据量和成本,各方案均实施分层采样策略[R7][R8][R15]:
- Session 级采样(非事件级):保证同一 session 的所有事件要么全采要么全不采[R15]
- 动态采样:Sentry 支持固定比率、基于规则采样、动态采样[R7]
- 限流:Collector 层令牌桶限流[R8]
Session 级采样是 RUM 领域的黄金实践:如果事件级采样,单个 session 内的 view 被采而 resource 被丢,会导致"断层"分析,无法完整还原用户路径。
来源
[R1] [官方] Sentry Self-hosted Data Flow: https://develop.sentry.dev/self-hosted/data-flow/
[R2] [官方] Sentry Grouping: https://develop.sentry.dev/grouping/
[R3] [官方] Datadog RUM Browser Data Collected: https://docs.datadoghq.com/real_user_monitoring/browser/data_collected/
[R4] [官方] What is Grafana Faro?: https://grafana.com/oss/faro/
[R5] [官方] Introducing Snuba: Sentry’s New Search Infrastructure: https://blog.sentry.io/introducing-snuba-sentrys-new-search-infrastructure/
[R6] [二级资料] The high-cardinality trap (ClickHouse): https://clickhouse.com/resources/engineering/high-cardinality-slow-observability-challenge
[R7] [二级资料] Sentry 高级功能探索:性能分析与用户体验监控: https://blog.csdn.net/gitblog_00554/article/details/150620759
[R8] [官方] Grafana Agent app_agent_receiver_config: https://grafana.com/docs/agent/latest/configuration/integrations/integrations-next/app-agent-receiver-config/
[R9] [官方] Husky: Efficient compaction at Datadog scale: https://www.datadoghq.com/blog/engineering/husky-storage-compaction/
[R10] [二级资料] Datadog 第六代时序数据库: https://juejin.cn/post/7555079970490531876
[R14] [官方] Cloudflare RUM beacon: https://developers.cloudflare.com/speed/observatory/rum-beacon/
[R15] [二级资料] Real User Monitoring (RUM) with web-vitals Library: https://webperfclinic.com/article/real-user-monitoring-web-vitals-library-2026-setup-guide
[R17] [官方] Grafana Tempo architecture: https://grafana.com/docs/tempo/next/introduction/architecture/