mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3147 字
9 分钟
可观测性存储后端
2025-09-28

存储是可观测性平台成本最高的组件,占整体支出的 40-60%。选择合适的存储后端、优化存储策略、控制数据增长,直接决定了可观测性平台的可持续性。

本章将深入四大存储后端的架构与选型:VictoriaMetrics/Mimir 用于指标存储、Tempo 用于追踪存储、Loki 用于日志存储。讨论每种后端的设计哲学、性能特征、成本模型,以及如何根据你的规模和需求做出选择。

一、指标存储后端#

1.1 Prometheus 的局限#

Prometheus 是最流行的指标采集和查询系统,但它有两个根本局限:

局限说明影响
单机架构不支持水平扩展无法处理超过单机容量的指标
本地存储数据存储在本地磁盘无持久化保证、无长期存储

1.2 VictoriaMetrics 架构深入#

VictoriaMetrics 是一个高性能、成本优化的时序数据库,兼容 Prometheus 协议。它的核心设计理念是”简单架构 + 极致压缩 + 低成本”。

graph TB subgraph VM["VictoriaMetrics 集群架构"] VMINSERT["vminsert<br/>写入节点<br/>水平扩展<br/>无状态"] VMSTORAGE["vmstorage<br/>存储节点<br/>水平扩展<br/>有状态"] VMSELECT["vmselect<br/>查询节点<br/>水平扩展<br/>无状态"] end subgraph 客户端 PROM["Prometheus<br/>remote_write"] OTEL["OTel Collector"] GRAFANA["Grafana"] end PROM -->|"remote_write"| VMINSERT OTEL -->|"OTLP"| VMINSERT VMINSERT -->|"一致性哈希"| VMSTORAGE VMSELECT -->|"广播查询"| VMSTORAGE GRAFANA -->|"PromQL"| VMSELECT style VM fill:#e8f5e9,stroke:#2e7d32

VictoriaMetrics 的关键设计决策:

设计决策选择原因
存储格式自定义列存比 Gorilla 编码更好的压缩比
写入路径WAL + 内存缓冲 + 刷盘兼顾写入性能和数据持久性
查询路径广播到所有 vmstorage简单、无需查询路由
数据分片一致性哈希 by tenant + metric name均匀分布、减少热点
压缩zstd + 自定义编码10-20x 压缩比

写入路径

sequenceDiagram participant App as 应用/Collector participant Insert as vminsert participant Storage as vmstorage participant Disk as 磁盘 App->>Insert: remote_write (SNAPPY 压缩) Insert->>Insert: 解压 + 解析 Insert->>Insert: 一致性哈希选择 vmstorage Insert->>Storage: 写入请求 Storage->>Storage: 写入 WAL(预写日志) Storage->>Storage: 写入内存索引 Storage->>Disk: 异步刷盘(每 1s) Note over Storage: WAL 保证数据不丢<br/>内存索引加速查询

查询路径

sequenceDiagram participant Grafana as Grafana participant Select as vmselect participant Storage1 as vmstorage-1 participant Storage2 as vmstorage-2 Grafana->>Select: PromQL 查询 Select->>Storage1: 广播查询 Select->>Storage2: 广播查询 Storage1-->>Select: 返回部分结果 Storage2-->>Select: 返回部分结果 Select->>Select: 合并结果 Select-->>Grafana: 返回完整结果
特性VictoriaMetricsPrometheus
架构分布式单机
写入性能5M+ samples/s500K samples/s
压缩比10-20x3-5x
长期存储原生支持需要 Thanos
多租户集群版
降采样
兼容性Prometheus 兼容原生

1.3 Mimir 架构深入#

Mimir 是 Grafana Labs 开发的大规模指标存储后端,设计目标是”无限扩展”。与 VictoriaMetrics 的简化分布式架构不同,Mimir 采用了微服务架构:

graph TB subgraph 写入路径 DIST["Distributor<br/>写入入口<br/>一致性哈希"] ING["Ingester<br/>写入 WAL + 刷块<br/>TSDB 格式"] end subgraph 读取路径 QRY["Querier<br/>查询引擎"] QF["Query Frontend<br/>查询调度 + 分割"] RULER["Ruler<br/>告警规则评估"] end subgraph 存储["对象存储"] S3["S3 / GCS / ABS"] end subgraph 元数据 RING["Ring<br/>一致性哈希环"] CONSUL["Consul / etcd<br/>成员管理"] end DIST --> ING --> S3 QRY --> S3 QRY --> ING QF --> QRY RULER --> QRY DIST -.-> RING ING -.-> RING RING -.-> CONSUL style 写入路径 fill:#e3f2fd,stroke:#1565c0 style 读取路径 fill:#e8f5e9,stroke:#2e7d32 style 存储 fill:#fff3e0,stroke:#e65100 style 元数据 fill:#f3e5f5,stroke:#6a1b9a

Mimir 的关键组件:

组件职责扩展方式
Distributor写入入口、验证、一致性哈希水平扩展
Ingester写入 WAL、维护内存 TSDB、刷块到对象存储水平扩展(带数据迁移)
Querier查询引擎、合并 Ingester + 对象存储数据水平扩展
Query Frontend查询调度、分割大查询、缓存水平扩展
Compactor合并小块、降采样、清理过期数据单实例或有限扩展
Store Gateway查询对象存储中的历史数据水平扩展
Ruler告警规则评估水平扩展
特性MimirVictoriaMetrics
架构微服务简化分布式
最大规模10 亿+ 活跃序列1 亿+ 活跃序列
对象存储S3/GCS/ABS
多租户原生集群版
运维复杂度
适用场景超大规模中大规模
查询缓存Query Frontend 缓存vmselect 缓存
降采样Compactorvmrollup

1.4 选型建议#

规模活跃序列数推荐原因
小型< 100 万Prometheus简单、够用
中型100 万-1000 万VictoriaMetrics 单机性能好、运维简单
大型1000 万-1 亿VictoriaMetrics 集群水平扩展
超大型> 1 亿Mimir无限扩展

二、追踪存储后端#

2.1 Tempo 架构深入#

Tempo 是 Grafana 生态的追踪存储后端,设计哲学是”仅索引 TraceID,其他全扫描”。这个设计选择使得 Tempo 的存储成本远低于全索引方案(如 Jaeger + Elasticsearch)。

graph TB subgraph Tempo["Tempo 架构"] DIST["Distributor<br/>接收 OTLP<br/>验证 + 路由"] ING["Ingester<br/>写入 WAL + 刷块<br/>Parquet 格式"] QRY["Querier<br/>查询引擎<br/>TraceQL"] QF["Query Frontend<br/>查询调度"] COMP["Compactor<br/>合并块<br/>降采样"] end subgraph 存储["对象存储"] S3["S3 / GCS / Local"] end subgraph 指标["衍生指标"] MG["Metrics Generator<br/>Span → 指标<br/>RED 指标"] end DIST --> ING --> S3 QRY --> S3 QRY --> ING QF --> QRY COMP --> S3 ING --> MG style Tempo fill:#e0f2f1,stroke:#00695c style 存储 fill:#fff3e0,stroke:#e65100 style 指标 fill:#e8f5e9,stroke:#2e7d32

Tempo 的存储格式

Tempo 将追踪数据以 Parquet 列存格式存储在对象存储中。Parquet 格式提供了优秀的压缩比和列式查询性能:

存储层格式用途
WAL本地文件写入缓冲,保证数据不丢
块(Block)Parquet 列存长期存储,高压缩比
索引TraceID → Block 映射快速查找 TraceID 所在块

Tempo 的查询路径

  1. TraceID 查询:通过索引找到 TraceID 所在的 Block,从对象存储读取
  2. TraceQL 查询:扫描 Block 中的 Parquet 数据,利用列存裁剪加速
  3. 搜索查询:全扫描,但利用 Parquet 的列式存储和统计信息加速

2.2 Tempo vs Jaeger#

维度TempoJaeger
索引方式仅 TraceID全索引(服务、操作、标签)
存储成本低(无索引开销)高(索引开销大)
搜索方式TraceID + TraceQL结构化搜索
对象存储S3/GCS仅 ES/Cassandra
与 Grafana 集成深度一般
适用场景大规模 + Grafana 生态需要灵活搜索
查询语言TraceQLJaeger Query
衍生指标Metrics Generator

2.3 Tempo 配置#

# tempo.yaml — 生产级配置
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4319
http:
endpoint: 0.0.0.0:4320
ingester:
max_block_duration: 30m
trace_idle_period: 30s
max_block_bytes: 100MB
storage:
trace:
backend: s3
s3:
bucket: tempo-traces
endpoint: s3.amazonaws.com
wal:
path: /var/tempo/wal
compactor:
compaction:
block_retention: 720h # 30 天保留
metrics_generator:
registry:
external_labels:
source: tempo
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://mimir:9009/api/v1/push

2.4 TraceQL 查询实战#

# 基础查询:按服务名查找
{resource.service.name = "order-service"}
# 按状态码查找错误
{status = error}
# 按延迟查找慢请求
{duration > 1s}
# 组合查询:特定服务的慢请求
{resource.service.name = "order-service" && duration > 500ms}
# 按 Span 属性查询
{span.http.method = "POST" && span.http.status_code >= 500}
# 嵌套查询:调用链关系
{resource.service.name = "frontend"} -> {resource.service.name = "order-service" && duration > 1s}

三、日志存储后端#

3.1 Loki 架构深入#

Loki 采用”仅索引元数据”的设计,成本远低于 Elasticsearch。它的核心思想是:不索引日志内容,只索引标签(Labels)。查询时先通过标签缩小范围,再对日志内容进行扫描和过滤。

graph TB subgraph 写入路径[" 写入路径"] DIST["Distributor<br/>验证 + 路由"] ING["Ingester<br/>写入 WAL + 刷块<br/>压缩日志 + 索引标签"] end subgraph 读取路径[" 读取路径"] QF["Query Frontend<br/>查询调度 + 分割"] QRY["Querier<br/>查询引擎<br/>LogQL"] end subgraph 存储[" 对象存储"] S3["S3 / GCS<br/>日志块(压缩)<br/>索引块(标签)"] end subgraph 缓存[" 缓存"] MEM["Memcached / Redis<br/>查询结果缓存<br/>索引缓存"] end DIST --> ING --> S3 QF --> QRY QRY --> S3 QRY --> ING QRY -.-> MEM style 写入路径 fill:#e3f2fd,stroke:#1565c0 style 读取路径 fill:#e8f5e9,stroke:#2e7d32 style 存储 fill:#fff3e0,stroke:#e65100 style 缓存 fill:#f3e5f5,stroke:#6a1b9a

Loki 的存储格式

存储内容格式说明
日志数据压缩块(gzstd)原始日志文本,按时间分块
标签索引倒排索引标签 → 日志块 ID 映射
WAL本地文件写入缓冲,保证数据不丢

3.2 Loki vs Elasticsearch#

维度LokiElasticsearch
索引方式仅索引标签全文倒排索引
存储成本低(1/5 ~ 1/10)
全文搜索慢(扫描)快(倒排索引)
结构化搜索快(标签索引)
查询语言LogQLLucene/KQL
适用场景结构化日志 + Grafana全文搜索 + 日志分析
对象存储S3/GCS需要本地存储
水平扩展
Warning

不要把所有日志都发到 Elasticsearch。对于结构化日志,Loki 的成本通常是 Elasticsearch 的 1/5 到 1/10。只有在需要全文搜索的场景时才使用 Elasticsearch。

3.3 LogQL 查询实战#

# 基础查询
{service="order-service"} |= "ERROR"
# 结构化查询
{service="order-service"} | json | level="ERROR" | error_code="DB_TIMEOUT"
# 聚合查询
sum(count_over_time({service="order-service"} | json | level="ERROR" [5m])) by (error_code)
# 延迟分析
quantile_over_time(0.99, {service="order-service"} | json | unwrap duration_ms [5m])
# 追踪关联
{service="order-service"} | json | trace_id="abc123"
# Top N 热点路径
topk(10, sum(rate({service="order-service"} | json | unwrap duration_ms [5m])) by (http_route))
# 错误率趋势
sum(rate({service="order-service"} | json | level="ERROR" [5m])) / sum(rate({service="order-service"} | json [5m])) * 100

四、存储格式与压缩策略#

4.1 指标存储格式#

后端存储格式压缩算法压缩比说明
VictoriaMetrics自定义列存zstd + Delta + XOR10-20x类似 Gorilla 编码但更激进
MimirTSDB 块Snappy + Delta + XOR8-15x与 Prometheus 相同格式
PrometheusTSDB 块Snappy + Delta + XOR3-5x原生格式

时序数据压缩的核心技术

压缩技术适用数据压缩比说明
Delta 编码时间戳5-10x相邻时间戳差值很小
XOR 编码浮点值5-10x相邻值异或后前导零多
字典编码标签名/值3-5x重复字符串用 ID 替代
zstd/Snappy整体压缩2-3x通用压缩算法

4.2 追踪存储格式#

后端存储格式压缩算法压缩比说明
TempoParquet 列存zstd5-10x列存天然适合压缩
Jaeger (ES)JSON 文档LZ42-3x行存,压缩比低
Jaeger (Cassandra)自定义Snappy3-5x宽行存储

4.3 日志存储格式#

后端存储格式压缩算法压缩比说明
Loki压缩块gzstd8-15x结构化日志压缩比更高
Elasticsearch倒排索引LZ4/DEFLATE2-5x索引开销大

4.4 压缩比优化实践#

优化方式适用后端效果说明
结构化日志Loki压缩比 8x → 15xJSON 格式比纯文本更易压缩
高基数标签分离VictoriaMetrics压缩比 10x → 20x将高基数标签移到日志
块大小优化Mimir压缩比 8x → 15x更大的块 → 更好的压缩
块合并Tempo压缩比 5x → 10x合并小块减少元数据开销
标签优化Loki存储量减少 50%减少标签数量、避免高基数标签

五、查询性能优化#

5.1 指标查询优化#

# 差:全量扫描
rate(http_request_duration_seconds_sum[5m])
# 好:按服务过滤
rate(http_request_duration_seconds_sum{service="order-service"}[5m])
# 更好:按服务 + 方法过滤
rate(http_request_duration_seconds_sum{service="order-service",method="POST"}[5m])
# 差:高基数 Group By
sum(rate(http_request_duration_seconds_sum[5m])) by (user_id)
# 好:低基数 Group By
sum(rate(http_request_duration_seconds_sum[5m])) by (service, method)
优化策略效果说明
缩小时间范围查询速度 2-10x查询 1h vs 7d 差异巨大
减少标签过滤查询速度 1.5-3x精确匹配比正则快
避免高基数 Group By查询速度 5-100x高基数导致大量时间序列
使用 Recording Rules查询速度 10-100x预计算常用查询
启用查询缓存重复查询 100xMimir/VictoriaMetrics 支持

5.2 追踪查询优化#

# 差:全扫描
{duration > 100ms}
# 好:按服务 + 时间范围
{resource.service.name = "order-service" && duration > 100ms}
# 更好:按 TraceID 直接查询
# 从指标/日志中获取 TraceID,直接查询
优化策略效果说明
使用 TraceID 查询最快直接定位到 Block
缩小时间范围2-10x减少扫描的 Block 数量
添加服务名过滤2-5x利用 Parquet 列存裁剪
使用 Metrics Generator10-100x先查指标再查追踪

5.3 日志查询优化#

# 差:全量扫描 + 正则匹配
{service="order-service"} |~ "error|timeout"
# 好:结构化查询
{service="order-service"} | json | level="ERROR"
# 更好:精确标签过滤
{service="order-service", level="ERROR"}
优化策略效果说明
使用标签过滤10-100x标签有索引,扫描快
使用结构化查询2-5xJSON 解析比正则快
缩小时间范围2-10x减少扫描的 Block 数量
限制返回条数1.5-3x使用 limit 参数
启用查询缓存重复查询 100xLoki + Memcached

六、保留策略#

6.1 保留策略设计#

不同信号的数据价值随时间递减,保留策略应该反映这一特征:

信号热数据(0-7 天)温数据(7-30 天)冷数据(30+ 天)
指标原始分辨率5 分钟降采样1 小时降采样
追踪全量(采样后)仅错误 + 慢请求仅错误
日志全量仅 ERROR + WARN仅 ERROR

6.2 VictoriaMetrics 保留配置#

# VictoriaMetrics 保留配置
-retentionPeriod=90d # 全局保留 90 天
-downsampling.period=5m:30d # 5 分钟降采样保留 30 天
-downsampling.period=1h:90d # 1 小时降采样保留 90 天

6.3 Tempo 保留配置#

# Tempo 保留配置
compactor:
compaction:
block_retention: 720h # 30 天保留
compaction_window: 1h # 1 小时合并窗口
max_block_bytes: 100MB
compacted_block_retention: 1h # 合并后旧块保留 1 小时

6.4 Loki 保留配置#

# Loki 保留配置
limits_config:
- max_query_length: 721h # 最大查询范围 30 天
retention_period: 744h # 保留 31 天
compactor:
working_directory: /var/loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
delete_request_cancel_period: 24h

七、存储成本优化#

7.1 成本模型#

信号存储成本计算公式
指标序列数 × 采样间隔 × 保留期 × 每样本字节数
追踪Span 数 × 每 Span 字节数 × 采样率 × 保留期
日志日志量 × 压缩比 × 保留期

7.2 降采样#

降采样(Downsampling)是降低长期存储成本的核心策略:

# VictoriaMetrics 降采样配置
downsampling:
- resolution: 5m
retention: 30d
- resolution: 1h
retention: 90d
- resolution: 1d
retention: 365d
原始分辨率降采样后存储节省适用场景
15s5m95%30 天以上的历史数据
15s1h99.6%90 天以上的历史数据
15s1d99.99%365 天以上的历史数据

7.3 数据分层#

graph LR HOT["热数据<br/>0-7 天<br/>SSD/内存<br/>原始分辨率"] --> WARM["温数据<br/>7-30 天<br/>HDD/对象存储<br/>5 分钟降采样"] WARM --> COLD["冷数据<br/>30+ 天<br/>对象存储<br/>1 小时降采样"] style HOT fill:#ffcdd2,stroke:#c62828 style WARM fill:#fff3e0,stroke:#e65100 style COLD fill:#e3f2fd,stroke:#1565c0

7.4 成本对比#

后端默认压缩比优化后压缩比优化方式
VictoriaMetrics10x20x高基数标签分离
Mimir8x15x块大小优化
Tempo5x10x块合并
Loki8x15x结构化日志 + 标签优化

八、选型决策矩阵#

8.1 综合选型#

信号小型(< 100 服务)中型(100-500 服务)大型(> 500 服务)
指标PrometheusVictoriaMetricsMimir
追踪JaegerTempoTempo
日志LokiLokiLoki
性能分析PyroscopePyroscopeParca

8.2 选型决策流程图#

graph TB START["选择存储后端"] --> SIGNAL{"什么信号?"} SIGNAL -->|"指标"| SCALE_M{"规模?"} SIGNAL -->|"追踪"| TRACE_Q{"查询模式?"} SIGNAL -->|"日志"| LOG_Q{"全文搜索?"} SCALE_M -->|"< 100 万序列"| PROM["Prometheus<br/>简单够用"] SCALE_M -->|"100 万-1 亿"| VM["VictoriaMetrics<br/>性能好运维简单"] SCALE_M -->|"> 1 亿"| MIMIR["Mimir<br/>无限扩展"] TRACE_Q -->|"TraceID 查询为主"| TEMPO["Tempo<br/>低成本"] TRACE_Q -->|"需要灵活搜索"| JAEGER["Jaeger + ES<br/>全索引"] LOG_Q -->|"否(结构化日志)"| LOKI["Loki<br/>低成本"] LOG_Q -->|"是(全文搜索)"| ES["Elasticsearch<br/>倒排索引"] style START fill:#e8eaf6,stroke:#283593 style PROM fill:#c8e6c9,stroke:#2e7d32 style VM fill:#c8e6c9,stroke:#2e7d32 style MIMIR fill:#c8e6c9,stroke:#2e7d32 style TEMPO fill:#c8e6c9,stroke:#2e7d32 style JAEGER fill:#fff3e0,stroke:#e65100 style LOKI fill:#c8e6c9,stroke:#2e7d32 style ES fill:#fff3e0,stroke:#e65100

8.3 成本估算#

规模指标存储/月追踪存储/月日志存储/月总计/月
小型$50$30$100$180
中型$200$150$500$850
大型$1,000$800$3,000$4,800
Note

以上成本基于 2026 年 AWS S3 存储价格估算,实际成本取决于数据量、保留期和采样策略。使用对象存储(S3/GCS)比使用块存储(EBS/PV)便宜 5-10 倍。

九、动手实践#

9.1 部署完整存储栈#

docker compose up -d prometheus tempo loki
# 验证各组件
curl http://localhost:9090/api/v1/query?query=up
curl http://localhost:3200/ready
curl http://localhost:3100/ready

9.2 验证数据流#

# 发送追踪 → Tempo
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"test"}}]},"scopeSpans":[{"spans":[{"traceId":"01234567890123456789012345678901","spanId":"0123456789012345","name":"test","kind":1,"startTimeUnixNano":"1700000000000000000","endTimeUnixNano":"1700000001000000000"}]}]}]}'
# 在 Grafana 中验证
open http://localhost:3000/explore

9.3 验证清单#

检查项验证方式预期结果
Prometheus 采集查询 uptargets 全部为 1
Tempo 接收追踪Grafana 查询能看到追踪
Loki 接收日志Grafana 查询能看到日志
数据源连通Grafana 测试全部连通

十、本章小结#

上一章从全景视角介绍了可观测性数据管道。 可观测性四大存储后端的要点基本都在这里了。

主题核心要点关键词
VictoriaMetrics简化分布式架构(vminsert/vmstorage/vmselect),10-20x 压缩比,适合中大规模。VictoriaMetrics
Mimir微服务架构,10 亿+ 活跃序列,适合超大规模,运维复杂度高。Mimir
Tempo仅索引 TraceID + Parquet 列存,成本远低于 Jaeger,TraceQL 提供灵活查询。Tempo
Loki仅索引标签,成本远低于 Elasticsearch,LogQL 提供结构化查询。Loki
存储格式指标用列存 + Delta/XOR 编码,追踪用 Parquet 列存,日志用压缩块。存储格式
压缩策略结构化日志、高基数标签分离、块大小优化、块合并。压缩策略
查询优化缩小时间范围、使用标签过滤、避免高基数 Group By、启用缓存。查询优化
保留策略不同信号不同保留期,降采样降低长期存储成本。保留策略
选型原则小规模用简单方案,大规模用分布式方案,始终优先对象存储。选型原则

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

可观测性存储后端
https://blog.souloss.com/posts/observability/storage-backend/
作者
Souloss
发布于
2025-09-28
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时