前端监控(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页面加载触发 Navigation / Resource / Paint TimingWeb Vitals SDK 采集LCPCLSINP自动跟踪每个页面的 Web Vitals 指标sendBeacon / fetch 批量上报页面卸载时自动发送,保证数据不丢失Collector → 列式 OLAP 存储ClickHouse / Husky / Loki+Tempo 等RUM 管道:页面加载 → SDK 采集(LCP/CLS/INP)→ 网络上报 → 列式存储所有主流 RUM 方案共享此采集范式,差异在后端存储选型

动画说明:上方动画逐步展示 RUM 数据从用户浏览器到后端存储的完整管道。首先用户浏览器页面加载触发 Performance API 事件(蓝色),Web Vitals SDK 自动捕获 LCP、CLS、INP 等指标(橙色),通过 sendBeacon 或 fetch 批量上报(紫色),最终落入列式 OLAP 存储(绿色)。所有主流 RUM 方案共享此采集范式,差异在后端存储选型。

Sentry

整体数据流架构

Sentry 的数据管道是多阶段流处理架构,事件从浏览器 SDK 发出到最终聚合查询,经历以下路径[R1]

mermaid
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]

存储用途
PostgreSQLNodestore(原始事件 payload)、Sentry 业务模型
ClickHouse(经 Snuba)事件搜索、span、性能指标、聚合查询的主力 OLAP 存储
RedisCelery 任务队列、热点 issue 缓存、限流计数器
Kafka事件流总线
Blob Storage附件、Replay 段、符号文件

Snuba:ClickHouse 抽象层

Snuba 是 Sentry 自研的事件存储与查询服务,以 ClickHouse 为后端,替代了早期基于 Postgres 的 Search/Tagstore 和基于 Redis 的 TSDB[R5]

为何从 Postgres/Redis 迁移到 ClickHouse[R5]

  1. 数据基本不可变,MVCC 安全机制反而拖累性能
  2. 新增查询维度需对 Postgres 反规范化并全量回填,耗时数月
  3. 过期数据清理需昂贵批量删除

Snuba 设计要点[R5]

  • 读写分离:查询与写入走不同 ClickHouse 集群
  • 批量写入:攒批后一次性提交,减少 merge 压力
  • 时间分区:按天/月分区,过期数据直接 drop 分区
  • PREWHERE 子句:预过滤跳过大量无关数据块
  • Redis 查询缓存:热点查询结果缓存

ClickHouse 列存 + 主键排序使 Tagstore 数据从 TB 级压缩到 GB 级——列存对高基数标签的天然压缩优势。

Issue 去重与聚合机制(Grouping)

Sentry 的去重是工程精华。三套系统依次运行[R2]

  1. normalize_stacktraces_for_grouping:应用堆栈规则,调整帧的 in_app 标志
  2. 服务端 fingerprint 规则:可覆盖默认 fingerprint
  3. 分组算法(仅当 fingerprint 未设或含 {{ default }} 时运行)

按平台的堆栈分组策略[R2]

  • Python:每帧贡献 module + function + context-line(源码行)
  • JavaScriptmodule + 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_iduser_id)视为普通压缩列,不引发索引膨胀,辅以 bloom filter 等轻量二级索引做点查[R6]

这是 Sentry 选择 ClickHouse 而非 TSDB 的核心原因之一——列式存储对高基数天然友好,而传统 TSDB 的倒排索引在高基数下会指数级膨胀。

Datadog RUM

分层事件模型

Datadog RUM 的核心是分层事件模型[R3],各事件类型以树形层级关联,具有不同的保留策略:

mermaid
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 天。

事件类型保留期说明
Session30 天用户会话,唯一 session.id。15 分钟不活动后重置,4 小时活动后结束
View30 天每次页面访问生成,关联其下所有 resource/long-task/error/action
Resource15 天图片/XHR/Fetch/CSS/JS 加载,含详细 timing
Long Task15 天阻塞主线程 > 50ms 的任务
Error30 天浏览器发出的所有前端错误
Action30 天用户交互(点击等),可自定义

层级关系: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]

  1. Faro Web SDK 集成到应用,采集数据并提交到 collector
  2. Collector 接收 SDK 数据,分别写入 Loki(日志/异常)与 Tempo(追踪)
  3. 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]
mermaid
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 页面加载时间线页面加载t=0FCP首次内容渲染~1.2sLCP最大内容渲染~2.5sCLS累计布局偏移~3.0sINP交互响应时间~4.2sFCP → LCP → CLS → INP:四个指标覆盖从渲染到交互的完整用户体验LCP 和 CLS 合计占页面加载体验权重的 50%

动画说明:上方动画在页面加载时间线上依次展示 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/