数据模型:时间序列、日志、Span、Profile 的内部表达
你在 Grafana 上点开一张 Dashboard,看到的是一条平滑的 p99
延迟曲线、一个柱状的 QPS 图、一张按 method
分组的成功率面板。你鼠标悬停在曲线上的一个异常尖峰,点进去——Tempo
或 Jaeger 在几百毫秒内吐出一条完整的分布式调用链。你发现
redis_get_cart Span 花了 850ms,点击 Related
Logs——Loki 在几秒内把同 trace_id
的十几条日志全拉了出来。你切到
Pyroscope——一张火焰图在几百毫秒内渲染完成,红色方块精确指向
runtime.mallocgc 占用的 CPU 比例。
这整个过程行云流水——但背后是五种完全不同的数据模型、四种存储引擎、三种索引策略和两种压缩算法在同时工作。它们之所以能”行云流水”,是因为每一层的数据模型都被设计成了与它的查询模式精确匹配——而你之所以在每个月末收到一张可观测性账单,也是因为你对这些数据模型的理解和它们的设计假设之间存在着偏差。
本文拆解 Metrics、Logs、Traces、Profiles、Events 五大支柱在磁盘和内存中的内部表达。这不是”这些工具各有什么参数”的配置指南——那是官方文档覆盖的事。本文试图回答:当你把一条数据交给 Prometheus / Loki / Tempo / Pyroscope 之后,它经历了怎样的内部转换?为什么这些转换决定了存储成本和查询延迟?以及理解了这些之后,你在选型和设计埋点策略时应该做出什么不同的决策。
一、三种典型查询,三种数据模型
排障场景里最常见的三类查询,背后对应三种完全不同的存储假设:
| 查询 | 典型语句 | 数据模型 | 索引策略 |
|---|---|---|---|
| 看 p99 延迟 24 小时趋势 | PromQL histogram_quantile(0.99, ...) |
时间序列 (labels, timestamp) → value |
label 倒排 → series ID → chunk |
搜 order_id=xxx 的完整链路 |
LogQL
{service="order"} \| json \| order_id="xxx" |
日志流 (labels, timestamp) → line |
仅 label 索引;正文扫描 |
| 某 Pod 过去 5 分钟 CPU 火焰图 | Profile {service="checkout"} 时间范围 |
(labels, timestamp, stack[]) → value |
标签分块;栈符号去重 |
核心结论:存储结构和索引策略必须匹配查询模式。用
ES 全索引存 Trace、用 Loki 当高基数精确查找引擎、把
request_id 打进 Prometheus
label——都是在用错误的数据模型回答正确的问题。
二、时序数据模型
时序数据是可观测性栈中最成熟的一层。从 RRDtool(1999)到
Prometheus(2015),核心抽象不变:metric_name + labels → (timestamp, value)
序列。变化的是索引结构、压缩算法和分布式架构——这三个维度决定
series 上限、查询延迟和磁盘放大系数。
关于 Gorilla 压缩、WAL 重放、VictoriaMetrics mergeset 的源码级细节,见本系列 时序数据库内核。本节聚焦数据模型本身以及 Prometheus TSDB block 的字段级结构。
2.1 Prometheus 的数据模型
Prometheus 的一条样本在文本 exposition 格式下形如:
http_requests_total{method="GET", endpoint="/api/order", status="200"} 104723 @1718000000
整串 http_requests_total{...}
唯一标识一条时间序列(time series)。每条
series 是有序的 (timestamp_ms, float64)
列表。scrape interval(通常 15s 或 30s)决定采样密度。
内存与磁盘上的路径:
flowchart LR
A[Scrape 样本] --> B[WAL segment]
B --> C[Head Block memSeries]
C --> D[关闭的 chunk mmap]
D --> E[Compaction]
E --> F[Persistent Block]
WAL(Write-Ahead Log):所有新样本先追加到 128 MB 的 segment 文件,crash 后从 checkpoint 重放。WAL 不做压缩,职责是 durability。
Head Block:活跃 series 对应
memSeries,含 open head chunk(默认 120
样本后关闭)和 label set。每个 memSeries 约 3–5
KB——这是 Prometheus OOM 的首要来源。
Persistent Block:时间窗口内 head chunk 全部关闭后,compaction 合并为不可变 block 目录。
2.2 TSDB Block 字段级结构
一个 TSDB block 是 ULID 命名的目录。与 Loki chunk、Tempo block 对照时,以下字段是”同一抽象在不同后端上的投影”:
| 字段 / 文件 | 类型与含义 | 查询用途 | 压缩 |
|---|---|---|---|
meta.json |
JSON:minTime、maxTime、stats.numSeries、stats.numSamples、compaction.level |
块选择、compaction 层级 | 无 |
index |
倒排:label pair → posting list(series_ref) |
PromQL label matcher | 无(块级) |
chunks/NNNNNN |
变长 bit-stream:Gorilla 编码的 timestamp + value | 按 series_ref 读样本 | Gorilla 10:1–20:1 |
tombstones |
删除区间 (series_ref, mint, maxt) |
查询时跳过已删样本 | 极小 |
series ref |
uint64:块内 series 局部 ID |
连接 index 与 chunks | — |
series_ref 编码(Prometheus tsdb/index
约定):高 16 bit 为 chunk file 编号,低 48 bit 为文件内
offset。查询 {method="GET"} 时:index 取
posting list → 对每个 ref 定位 chunk → 解码 Gorilla stream →
时间范围过滤 → 聚合。
meta.json 示例(字段名与 Prometheus v2.45+
一致,数值为说明性示例):
{
"ulid": "01J2K3M4N5P6Q7R8S9T0",
"minTime": 1718000000000,
"maxTime": 1718007200000,
"stats": {
"numSamples": 12453021,
"numSeries": 89234,
"numChunks": 178468
},
"compaction": {
"level": 2,
"sources": ["01J2K...", "01J2L..."]
}
}2.3 Chunk 内部:Gorilla 编码要点
Gorilla(Pelkonen et al., VLDB 2015)利用两个假设:时间戳等间距(delta-of-delta 常为 0)、相邻值变化小(XOR 前导零多)。第一个时间戳 64 bit 完整存储;后续 timestamp 平均 ~1.5 bit;counter 类指标 value 平均 1–2 byte,剧烈波动指标 4–6 byte。
不重复推导——完整数学与 XOR 位布局见 07-tsdb-internals 第二节。此处只给工程口径:15s scrape、一天 5760 点、未压缩 \(5760 \times 16\) B ≈ 92 KB/series/day;Gorilla 后约 4–8 KB/series/day。
2.4 VictoriaMetrics 与 Prometheus 模型差异
VictoriaMetrics 兼容 Prometheus 文本模型,存储层差异:
| 维度 | Prometheus TSDB | VictoriaMetrics |
|---|---|---|
| Series 标识 | 文本 label set + 块内 ref | 整数 TSID |
| 索引 | 每 block 倒排 index 文件 | mergeset 全局索引 |
| 分区 | 2h head + compaction 层级 | 按月分区 + 后台 merge |
| 高基数 | label 字符串索引占内存 | TSID 映射,同等内存更多 series |
选型含义:label 治理良好时两者成本接近;label churn 严重时 VictoriaMetrics 缓冲期更长,但不替代治理。
2.5 Metrics 存储成本公式(假设模型)
以下公式用于数量级估算,参数需按你的环境代入。不引用任何云厂商单价。
变量定义:
- \(N_s\):活跃 time
series 数量(
prometheus_tsdb_head_series) - \(\Delta t\):scrape 间隔(秒)
- \(R\):保留天数
- \(b\):Gorilla 压缩后每样本字节数(counter/gauge 稳态取 1.5,高波动取 4)
- \(f_i\):index 放大系数(Prometheus 典型 1.2–1.8)
未压缩样本量(bytes):
\[ V_{\text{raw}} = N_s \times \frac{R \times 86400}{\Delta t} \times 16 \]
压缩后 chunk 体积:
\[ V_{\text{chunk}} = N_s \times \frac{R \times 86400}{\Delta t} \times b \]
含 index 的磁盘占用:
\[ V_{\text{disk}} \approx f_i \times V_{\text{chunk}} \]
内存 head 占用(经验):
\[ M_{\text{head}} \approx N_s \times 4\,\text{KB} \]
数值示例(假设模型):
- \(N_s = 800{,}000\),\(\Delta t = 15\) s,\(R = 15\) 天,\(b = 1.5\),\(f_i = 1.5\)
- 日样本数:\(800000 \times 86400 / 15 = 4.608 \times 10^9\)
- \(V_{\text{chunk}} \approx 4.608 \times 10^9 \times 1.5 \times 15 \approx 104\) GB(15 天)
- \(M_{\text{head}} \approx 800000 \times 4\text{KB} \approx 3.2\) GB(仅 memSeries,不含 query 缓存)
基数与成本的关系:成本对 \(N_s\) 线性,对 label
组合数线性。把 user_id 打进 label 使 \(N_s\) 从 \(10^5\) 升到 \(10^7\),磁盘与内存同比例恶化——这是
Metrics 数据模型对基数最敏感的原因。
三、日志数据模型
时序数据的挑战是压缩;日志数据的挑战是在未知查询模式与有限索引之间折中。
3.1 日志的通用模型
{"timestamp":"2024-06-11T03:14:22.423Z","level":"ERROR","service":"checkout",
"message":"redis: connection pool exhausted",
"trace_id":"0af7651916cd43dd8448eb211c80319c","duration_ms":843}特征:level/service
低基数、高过滤频率;message
高熵;trace_id 高基数、几乎只做精确匹配。
3.2 Elasticsearch:全字段倒排
Lucene
segment:词项 → (doc_id, tf, positions)。任意字段可搜,存储放大
1.5–3×。高基数 trace_id
若建索引,词典膨胀极快——mapping 中应
"index": false 仅 _source
精确查。
3.3 Loki:label 索引 + 正文扫描
Log stream:固定 label set
下的有序日志行。{service="checkout", level="ERROR"}
标识一流。
Chunk 是 Loki 的最小存储单元(与 TSDB
block、Tempo block 对照见第六节)。写入路径:Ingester 缓冲 →
切 chunk(目标压缩后 1–2 MB)→ 上传对象存储;index 存
(labels fingerprint, start, end) → chunk ref。
Loki 2.9+ Bloom Filter:chunk 级 n-gram
bloom,低频 token 查询可跳过大量 chunk。对
"error" 这类高频词加速有限。
3.4 ClickHouse:列存折中
MergeTree:列独立压缩;主键
(service, timestamp) 范围剪枝;跳数索引
bloom_filter/minmax。适合结构化聚合查询,不适合任意全文
ad-hoc。
3.5 日志存储成本公式(假设模型)
变量:
- \(Q\):日志行写入速率(行/秒)
- \(L\):平均行大小(字节,JSON 典型 300–800)
- \(\sigma\):ZSTD 压缩比(文本 JSON 典型 4–6)
- \(f_e\):ES 索引放大(2–3);Loki 无正文字段索引(1.0–1.2)
- \(R\):保留天数
原始日增量(GB/天):
\[ V_{\text{day,raw}} = \frac{Q \times 86400 \times L}{10^9} \]
压缩后对象存储:
\[ V_{\text{day,store}} = \frac{V_{\text{day,raw}}}{\sigma} \times f_e \]
总保留:
\[ V_{\text{retain}} = V_{\text{day,store}} \times R \]
示例(假设模型):\(Q=50000\) 行/s,\(L=500\) B,\(\sigma=5\),Loki \(f_e=1.1\),\(R=30\)
- \(V_{\text{day,raw}} = 50000 \times 86400 \times 500 / 10^9 \approx 2160\) GB/天
- \(V_{\text{day,store}} \approx 2160 / 5 \times 1.1 \approx 475\) GB/天
- 30 天 ≈ 14 TB(对象存储侧;不含 ingester 热缓存)
同一流量若 \(f_e=2.5\)(ES),30 天约 32 TB——数量级差异来自索引策略,不是 ZSTD 本身。
四、Trace 数据模型
Trace 查询双峰:trace_id 精确查找(高频)与 attribute 搜索(低频、排障关键)。Jaeger 与 Tempo 分别优化两端。
4.1 Span 内部结构(OTLP)
| 字段 | 类型 | 典型大小 | 索引价值 |
|---|---|---|---|
trace_id |
16 bytes | 固定 | 分区键 / 精确查 |
span_id |
8 bytes | 固定 | 树结构 |
parent_span_id |
8 bytes | 固定 | 树结构 |
name |
string | 20–80 B | 中基数 |
kind |
enum | 1 B | 低基数 |
start/end_time_unix_nano |
fixed64×2 | 16 B | 范围查 |
attributes[] |
KeyValue | 200–2000 B | 高基数风险 |
events[] |
可变 | 0–几 KB | 通常不索引 |
resource |
KeyValue | 100–500 B | 低基数 |
典型 HTTP SERVER Span protobuf 序列化约 500–800 B;含 8–10 个 attribute 约 1–2 KB。每个请求 10–20 个 Span 时,Trace 写入量远超 Metrics。
4.2 Jaeger:全索引路线
Span 写入 ES/Cassandra:字段映射为 ES document,tags
建倒排。可搜
http.status_code=500 AND service.name=checkout,代价是存储放大与分片压力。
4.3 Tempo:无 attribute 索引
Span 按 trace_id 分组 → Ingester WAL → flush
为 Parquet block → 对象存储。查询
trace_id=X:hash ring 定位 ingester 或 block →
读 parquet row group。不维护 attribute
倒排。
TraceQL 搜索(duration > 500ms)靠并行
block 扫描 + predicate
pushdown,秒级而非毫秒级——设计取舍,非实现缺陷。
4.4 Trace 存储成本公式(假设模型)
变量:
- \(Q\):入口 QPS
- \(S\):每请求 Span 数
- \(B\):每 Span 序列化大小(字节,取 1200)
- \(p_h\):头部采样率
- \(p_t\):尾部额外保留比例(相对未采样流量,错误+慢请求)
- \(\sigma_t\):Trace 块压缩比(ZSTD+Parquet 典型 2–4)
- \(f_j\):Jaeger+ES 索引放大(3–5);Tempo(1.0–1.3)
日 Span 数:
\[ N_{\text{span/day}} = Q \times 86400 \times S \times (p_h + p_t) \]
日存储(Tempo 类):
\[ V_{\text{tempo/day}} = \frac{N_{\text{span/day}} \times B}{\sigma_t \times 10^9} \times f_t \]
示例(假设模型):\(Q=5000\),\(S=12\),\(B=1200\),\(p_h=0.01\),\(p_t=0.005\)(错误+慢请求额外保留),\(\sigma_t=3\),\(f_t=1.1\)
- \(N_{\text{span/day}} = 5000 \times 86400 \times 12 \times 0.015 \approx 77.8 \times 10^6\)
- \(V_{\text{tempo/day}} \approx 77.8 \times 10^6 \times 1200 / 3 / 10^9 \times 1.1 \approx 34\) GB/天
若 \(f_j=4\)(Jaeger+ES 全索引),同参数约 136 GB/天——4× 差距来自索引,不是 Span 大小。
全量(\(p_h=1\)):\(N_{\text{span/day}} \approx 5.18 \times 10^9\),Tempo 约 2.3 TB/天——说明不采样不可规模化。
五、Profile 数据模型
Profile 单条体积大、频率低。核心是 Sample → Location → Function 映射与栈合并。
5.1 pprof 格式
- Function:符号表条目(name, file, line)
- Location:地址 + 行号 → Function
- Sample:
location_id[]栈 +value[]计数
符号去重:数万 sample 共享数千 Function,.gz
通常几十 KB–几百 KB。
5.2 Pyroscope / 火焰图
数据模型:(timestamp, labels, stacktrace[], value)。火焰图
= 按栈深度合并 sample 计数。查询 = 时间范围内扫描 block +
内存合并,不依赖复杂倒排。
5.3 Profile 成本公式(假设模型)
- \(F\):每服务 profile 频率(次/分钟,典型 1)
- \(P\):单次 pprof.gz 大小(KB,典型 100–500)
- \(N_svc\):被 profile 的服务数
\[ V_{\text{profile/day}} = \frac{N_{\text{svc}} \times F \times 1440 \times P}{10^6}\ \text{GB} \]
示例:200 服务,1 次/分钟,300 KB → 约 83 GB/天 未压缩语义;相对 Logs/Traces 通常 < 5% 账单。
六、TSDB Block vs Loki Chunk vs Tempo Block:字段级对照
这是本文的核心对照表。三者在工程上都叫 “block/chunk”,但字段语义不同。
| 概念 | Prometheus TSDB Block | Loki Chunk | Tempo Block (Parquet) |
|---|---|---|---|
| 唯一 ID | ULID 目录名 | chunk_id + fingerprint |
ULID + tenant |
| 时间边界 | meta.minTime/maxTime |
from/through ns |
start/end unix |
| 分区键 | 无(块内多 series) | label fingerprint | trace_id hash |
| 主索引 | index 倒排 label→ref |
boltdb/tsdb index: labels→ref | 仅 trace_id + 时间;可选 bloom |
| 数据文件 | chunks/* Gorilla |
.gz 日志行序列 |
.parquet 列存 Span |
| 单块典型大小 | 压缩后数百 MB–几 GB | 1–2 MB 压缩目标 | 100 MB–1 GB |
| 块内记录单位 | (series_ref, t, v) |
log line + ts | Span row |
| attribute 索引 | label 仅(metric 名+labels) | 无(正文) | 无(TraceQL 扫描) |
| 删除语义 | tombstones 区间 | retention 整 chunk 删 | compaction 合并丢弃 |
| 对象存储 | 可选(Thanos/Mimir) | 默认 S3/GCS | 默认 S3/GCS |
| 精确键查询 | series + 时间范围 | labels + 时间 + line filter | trace_id O(1) 定位 |
| ** ad-hoc 属性搜索** | PromQL label | LogQL \|= 扫描 |
TraceQL 块扫描 |
flowchart TB
subgraph prom [Prometheus TSDB Block]
PI[index: label→series_ref]
PC[chunks: Gorilla samples]
PI --> PC
end
subgraph loki [Loki Chunk]
LI[index: fingerprint→ref]
LC[chunk body: compressed lines]
LI --> LC
end
subgraph tempo [Tempo Block]
TB[footer: min/max id time]
TP[parquet: spans by trace_id]
TB --> TP
end
Loki chunk 字段级(Ingester 内存 → 持久化):
| 字段 | 含义 |
|---|---|
fingerprint |
label set 哈希 |
from / through |
chunk 时间边界 |
metric |
内部流标识 |
encoding |
压缩编码(gzip/snappy/zstd) |
head / tail |
块内首尾行(调试) |
synced |
是否已 flush 到对象存储 |
Tempo block 字段级(Parquet schema 简化):
| 列 / 元数据 | 含义 |
|---|---|
trace_id |
16 byte 十六进制 |
span_id |
8 byte |
parent_span_id |
8 byte |
name |
operation name |
start/end |
unix nano |
duration |
派生或存储 |
status_code |
OK/ERROR |
attributes |
map 或 JSON 列 |
resource |
service.name 等 |
footer |
bloom / 统计 / 版本 |
七、Events 数据模型
Events
查询模式:某时间点前后发生了什么。CloudEvents
最小模型:id, source, type, time, data。存储归宿通常是
Loki/Kafka,而非 Prometheus——Prometheus
无法保留离散事件原文。
K8s Event 经 kube-event-exporter 进 Loki
是常见模式;强行 event→metric 会丢失单次事件的上下文。
八、五大支柱访问模式与成本占比
| 维度 | Metrics | Logs | Traces | Profiles | Events |
|---|---|---|---|---|---|
| 写吞吐 | 低 | 极高 | 极高 | 低 | 中–高 |
| 查询模式 | 聚合 | 过滤+扫描 | trace_id + 属性搜索 | 火焰图 | 时间+type |
| 索引需求 | label 倒排 | 后端相关 | Jaeger 强 / Tempo 弱 | 几乎无 | 类日志 |
| 压缩率 | 极高 | 中–高 | 中 | 高 | 中 |
| 基数敏感度 | 极高 | 中–高 | 中/低 | 低 | 中 |
| 典型成本占比* | 10–20% | 40–60% | 20–35% | <5% | 并入 Logs |
*占比为假设模型下的工程经验区间,非实测账单;见 存储与成本。
80/20 法则:Logs + Traces 占 85–95% 存储;成本控制主战场是日志索引策略与 Trace 采样。
2.6 Head Block 内存结构与查询路径
Head Block 是 Prometheus 实时查询的数据源。理解
memSeries 结构有助于解释为什么”series
数量”比”样本数量”更决定内存。
memSeries 主要字段(概念模型,对应
tsdb/head.go):
| 字段 | 含义 | 内存影响 |
|---|---|---|
lset |
完整 label set | 与 label 数量、字符串长度正相关 |
chunks |
已关闭 chunk 列表 + head chunk | head chunk 满前持续增长 |
mmappedChunks |
mmap 到磁盘的 chunk 引用 | 不占堆内存 |
staleNaN |
标记 series 是否 stale | 极小 |
一次典型 PromQL 查询
{service="checkout", method="GET"} 的路径:
- PostingsForMatchers:在 index 上求
service=checkout∩method=GET的 posting list - 对每个 series_ref,加载 head chunk 或 mmap chunk
- Gorilla 解码时间范围内的样本
- 引擎聚合(rate、sum、histogram_quantile)
Churn 的非线性成本:若每秒创建 1000 个新
label 组合(例如把订单 ID 打进 label),即使总样本 QPS
不变,memSeries 分配速率也会拖垮
GC。这比”steady 800k series”更危险——因为除了 3–5 KB/series
的常驻内存,还有频繁的 map 插入、index 更新、WAL Series
record。
2.7 Exemplars 与 Native Histograms 的数据模型扩展
OpenMetrics Exemplars 在 TSDB 中作为
(series_ref, timestamp, value, trace_id, span_id)
附加存储,不进入主 Gorilla chunk——单独 WAL record 类型与
head exemplar storage。查询 histogram_quantile
时可选附带 exemplar 用于跳转 Trace。Exemplar 基数受 trace
采样率约束;若对每个 histogram bucket 都挂 exemplar,head
内存会显著上升。
Native Histograms(Prometheus 2.40+ 可选)改变 value
编码:从单 float64 变为 bucket span + counter 结构。chunk 内
encoding 类型字段区分 XOR float 与 native histogram
schema。选型含义:native histogram 减少 _bucket
系列爆炸,但 chunk 体积与查询路径更复杂——属于同一
TSDB block 模型上的 encoding 扩展,分区键仍是 label
set。
2.8 Mimir / Thanos 对 Block 模型的扩展
单机 Prometheus block 通过 sidecar
上传对象存储(Thanos)或由 Mimir ingester
写入共享块存储。逻辑模型不变:index + chunks + meta.json。差异在查询层
fan-out:Querier 并行读多个 block,Store Gateway
缓存 index header。成本公式中 \(f_i\) 需加上对象存储 GET 与跨
AZ 流量——见 存储与成本。
三附、日志索引演进:BoltDB-Shipper 与 TSDB Index
Loki 索引经历两代:boltdb-shipper(每 24h 一个 index table 上传 S3)与 TSDB index(Loki 2.8+,与 Prometheus TSDB index 思路类似)。对 chunk 字段无影响,影响的是 label → chunk ref 的查找延迟与 cardinality 上限。
| 索引类型 | 存储 | 适合规模 | 注意 |
|---|---|---|---|
| boltdb-shipper | 本地 bolt + S3 shipper | 中小 | 单 ingester 索引热 |
| tsdb index | TSDB 格式 index 文件 | 大 | 需 compactor 维护 |
无论哪种索引,正文仍在 chunk body——LogQL
|= 过滤不会 magically 变成倒排查。
3.6 日志查询执行计划(概念)
查询
{namespace="prod", app="checkout"} |= "timeout" [1h]:
- Index lookup:1h 内匹配 label 的 chunk 列表(可能数千个)
- 并行 fetch chunk from object storage
- 解压 → 逐行 regex/line filter
- 合并排序 → limit
瓶颈通常在 step 2–3。缩小 label 范围(加
level="ERROR")比优化 regex 更有效。
3.7 结构化日志 schema 设计
推荐固定字段(低基数 label 或 JSON 键):
{
"timestamp": "ISO8601",
"level": "INFO|WARN|ERROR",
"service": "checkout",
"trace_id": "32hex",
"span_id": "16hex",
"message": "human readable",
"error.type": "optional",
"duration_ms": 123
}禁止把 user_id、完整 URL
path 作为 Loki label。需要按 user 查时,走 ES/ClickHouse
或短窗口 + Bloom 加速的 LogQL。
四附、Tempo WAL 与 Ingester 生命周期
Tempo Ingester 在 flush 前将 trace 保留在内存 + 本地 WAL:
sequenceDiagram
participant D as Distributor
participant I as Ingester
participant W as Local WAL
participant S as Object Storage
D->>I: Push spans by trace_id
I->>W: Append WAL records
I->>I: Complete trace buffer
I->>S: Flush Parquet block
I->>W: Truncate WAL
Complete trace 判定:收到 root span 且 idle timeout,或 max block duration 到达。尾部采样在 Collector 层合并 span 后再 push——Ingester 不负责采样决策。
Block footer 含
blockID、tenantID、totalRecords、可选
bloom filter(vParquet 格式演进见 Tempo 文档)。与
Prometheus block 的 meta.json 类比,但
min/max trace ID 不是 primary key——primary
是 hash(trace_id) 到 ingester ring。
4.5 Jaeger 存储后端数据模型差异
| 后端 | 分区键 | Span 布局 | 搜索 |
|---|---|---|---|
| Elasticsearch | 日期 index + trace_id hash | 每 span 一 document | 全字段索引 |
| Cassandra | trace_id | 宽行存 span 列 | 有限 |
| Badger | trace_id prefix | KV 值 blob | 本地开发 |
Jaeger ES document 典型字段:traceID,
spanID, operationName,
startTime, duration,
tags, logs,
process。每个 tag 键在 mapping
中可能动态映射——动态 mapping
是生产事故来源,应预定义 mapping template。
4.6 SkyWalking Segment 模型(对照 OTLP Span)
SkyWalking 原生协议用 Segment(类似一组
Span)而非纯 OTLP Span。Segment 含
spans[]、service,
serviceInstance, endpoint。存储进
ES 时按 segment 或 span 扁平化取决于 OAP 版本与配置。与
Tempo block 对照:SkyWalking 默认索引 endpoint 与
latency——介于 Jaeger 全索引与 Tempo
无索引之间。
五附、Parca 与 Pyroscope 存储块
Parca 使用 parquet 存 profile sample,按
(labels, timestamp) 分区——与 Tempo
块模型类似但列是 stack trace ID。Pyroscope
合并后写入对象存储,查询时按时间 + label matcher 选
block,再内存 merge 火焰图。Profile 不需要
trace 级索引——这是 Profile 账单通常最低的原因。
六附、跨信号关联的数据模型
| 关联键 | Metrics | Logs | Traces | Profiles |
|---|---|---|---|---|
trace_id |
Exemplar | JSON 字段 | 主键 | 可选 link |
span_id |
Exemplar | JSON 字段 | 主键 | — |
service.name |
label | label/JSON | resource | label |
Grafana 互跳要求 同一 trace_id 字符串格式(32 hex 小写)在 Logs 与 Traces 一致。数据模型设计阶段应统一,而非在 UI 层 patch。
七附、成本估算 Worksheet(假设模型)
以下三组场景均为推导示例,非某客户真实账单。
场景 A:200 微服务,中等流量
| 参数 | 值 |
|---|---|
| 总入口 QPS | 50000 |
| Prometheus series | 1200000 |
| 日志行/s | 80000 |
| Span/请求 | 10 |
| Trace 头部采样 | 1% |
Metrics 15 天磁盘(§2.5 公式):约 150 GB 量级。
Logs 30 天 Loki(§3.5):约 20 TB 量级。
Traces 7 天 Tempo(§4.4):约 150 GB 量级。
结论:Logs 占主导;优先 Loki label 治理与日志采样。
场景 B:高基数事故
某团队将 pod_uid 作为 metric label(K8s
每次重启新 UID):
- \(N_s\) 从 50 万 → 800 万
- \(M_{\text{head}}\) 从 ~2 GB → ~32 GB → OOM
修复:用 pod 名称(低 churn)或去掉 pod 级
label,Pod 级详情走 Logs/Traces。
场景 C:Jaeger ES vs Tempo
同场景 A Trace 参数,Jaeger \(f_j=4\) vs Tempo \(f_t=1.1\):
- 7 天 Jaeger ES:约 4.2 TB
- 7 天 Tempo:约 1.0 TB
差额买运维时间与 ES 集群——不是”谁更好”,是查询 SLA 与预算 trade-off。
八附、OpenTelemetry 统一数据模型与后端映射
OTel 定义 cross-signal 的 Resource、Instrumentation Scope、Signal-specific payload。Collector 处理器可能修改 attribute——存储前须明确 canonical label set。
| OTel 概念 | Prometheus 映射 | Loki 映射 | Tempo 映射 |
|---|---|---|---|
| Resource.service.name | service label |
stream label | resource column |
| Span.name | — | — | span name |
| Log.severity | — | level label |
— |
| Metric name | metric name | — | — |
Attribute 翻译损失:OTel 允许 nested
map;Prometheus 只认 flat label。Collector
attributes processor 做 flatten
时可能引入高基数键——在 埋点哲学
层禁止。
九附、更多工程坑点
9.9 Histogram
_bucket 爆炸
每个额外 label 维度复制全部 bucket
series。le label 有 ~20 个 bucket 时,一个
histogram 变 20+ series。Native histogram 或 recording rule
预聚合可缓解。
9.10 Loki
structured_metadata 误用
Loki 3.x structured metadata 允许有限高基数——若把 trace_id 塞 metadata 仍可能拖慢查询。阅读版本文档边界。
9.11 Tempo ingester 内存与 trace 大小
单 trace 上万 span(循环内创建 span)撑爆 ingester buffer。应在 SDK 层限制 inner span。
9.12 对象存储 LIST 成本
Loki/Tempo 大范围查询触发大量 S3 LIST/GET。Compaction 减少小文件数量;查询必须带时间边界。
9.13 副本与 erasure coding
S3 3 副本使 \(V_{\text{retain}}\) 乘以副本因子;与压缩比分开核算。
9.14 Cold query vs hot cache
首次查询 block 延迟高;querier 缓存 hit 后延迟降一个数量级。Dashboard 频繁刷新相同查询——缓存友好; ad-hoc 大扫描——贵且慢。
9.15 Profile 符号表缺失
pprof 只有地址无 symbol——火焰图显示
0x...。部署时保留 debug info 或 sidecar
symbolizer。
十附、扩展落地清单
九、工程坑点
9.1 用 ES 存 Trace Span——全字段索引
Jaeger 默认 mapping 为大量 tag 建倒排,50 个 attribute 中
30 个从未被搜索却占 60% 磁盘。缓解:mapping
审查,"index": false 高基数 tag。
9.2 用 Loki 搜高基数字段
| json | user_id="xxx" 触发全 chunk 扫描。7
天 20 TB/天 保留时单次查询可扫数百 GB。缓解:Bloom
Filter、缩短 query
窗口、max_entries_limit_per_query、把
trace_id 放 label(低 Cardinality
流)而非正文。
9.3 Prometheus label 失控
动态 URL path 作为 endpoint label → 每次发布
series 暴涨 → OOM。缓解:路径模板化或
metric_relabel_configs:
metric_relabel_configs:
- source_labels: [endpoint]
regex: '/api/users/[^/]+/orders/[^/]+'
target_label: endpoint
replacement: '/api/users/:id/orders/:id'9.4 用 Metrics 存 Trace 级信息
把每个 trace_id 做成 label 或
high-cardinality gauge——数据模型错配。Trace 应用 Span 模型 +
采样,Metrics 保留 RED 聚合。
9.5 ClickHouse 日志不分区
全表扫描火焰图式查询数十秒。按
(service, toDate(timestamp)) 分区。
9.6 Tempo 当 ES 用
期望毫秒级 user_id 全历史搜索——TraceQL
扫描对象存储,延迟秒级。先 Metrics/Logs 缩小 trace_id,再
Tempo 精确拉取。
9.7 忽略 block/chunk 边界与查询计划
跨过多天 block 的 PromQL 会 merge 大量 index;Loki 跨 chunk 查询放大对象存储 GET。保留期与查询窗口应对齐块边界(天/小时)。
9.8 WAL 与对象存储双份成本
Tempo/Loki ingester WAL + S3 同时占空间;compaction 失败时 WAL 膨胀。监控 compactor lag。
十、落地清单
十一、关键概念回顾
- Prometheus TSDB:WAL → Head →
Block;
index+chunks+meta.json;Gorilla 压缩详见 07 篇。 - Loki chunk:label fingerprint 索引 + 正文 ZSTD;正文查询是扫描。
- Tempo block:Parquet 按 trace_id;attribute 搜索靠 TraceQL 扫描。
- 成本公式:对 Metrics 线性依赖 \(N_s\);Logs 依赖 \(Q,L,f_e\);Traces 依赖 \(Q,S,p_h,p_t,f_j\)。
- 选型原则:索引匹配查询;Logs/Traces 占账单主体。
十二、常见误解
“Tempo 不能 attribute 搜索”——能,用 TraceQL,秒级非毫秒级;设计换成本。
“Prometheus 内存只和 series 数有关”——churn 带来非线性 GC;稳态 80 万 series 可能比 churn 高的 40 万更稳。
“JSON 日志只是格式不同”——JSON 有 schema,管道可 tokenize;纯文本每层 regex,CPU 与维护成本成倍。
“块越大越好”——大 block 降低元数据开销但增大 compaction 压力和单次查询 IO;各系统有 sweet spot(Loki 1–2 MB,Prometheus 小时级–天级)。
十四、查询语言与数据模型的耦合
PromQL、LogQL、TraceQL 的能力边界由底层 block/chunk 结构决定——不是语法糖问题。 ### 14.1 PromQL rate/avg - 后端:TSDB chunk 顺序扫描 + index - 要点:聚合友好;高 cardinality 分组贵 理解这一点可以避免「在 Loki 里写 SQL 心态」或「在 Tempo 里期望 ES 速度」。 ### 14.2 PromQL @ modifier - 后端:block 时间边界 - 要点:跨 block 需 merge 理解这一点可以避免「在 Loki 里写 SQL 心态」或「在 Tempo 里期望 ES 速度」。 ### 14.3 LogQL json parser - 后端:chunk 内逐行 - 要点:无 index 加速 理解这一点可以避免「在 Loki 里写 SQL 心态」或「在 Tempo 里期望 ES 速度」。 ### 14.4 LogQL metric queries - 后端:log→metric 规则 - 要点:依赖 label 低基数 理解这一点可以避免「在 Loki 里写 SQL 心态」或「在 Tempo 里期望 ES 速度」。 ### 14.5 TraceQL {duration>1s} - 后端:Parquet 列扫描 - 要点:并发 querier 扫描 block 理解这一点可以避免「在 Loki 里写 SQL 心态」或「在 Tempo 里期望 ES 速度」。 ### 14.6 Jaeger tag query - 后端:ES inverted index - 要点:毫秒级但存储贵 理解这一点可以避免「在 Loki 里写 SQL 心态」或「在 Tempo 里期望 ES 速度」。
十五、数据保留与 Compaction 对模型的影响
保留期不是「删旧文件」这么简单——compaction 改变 block
粒度,影响查询 IO 模式。 ### 15.x Prometheus retention 与
block 形态 - 默认 15 天 retention;compaction level 1/2/3
合并 2h→12h→1d 块。 - 删除 tombstone 后 block 重写;查询
ancient sample 需 --storage.tsdb.retention.time
对齐。 - retention.size 与 time
同时生效——先触达者执行。
15.x Loki retention 与 block 形态
- retention 按 chunk 时间戳删除对象;index 条目同步清理。
- compactor 合并小 chunk 减少 S3 对象数——LIST 成本下降。
- 过短 retention + 长查询窗口 = 空结果,非 bug。
15.x Tempo retention 与 block 形态
- Tempo block 按 max block duration flush;compactor 合并 parquet 文件。
- WAL 截断依赖成功 upload;监控
tempo_compactor_running。 - 搜索 TraceQL 扫描范围 = retention 内全部 block——保留越久搜索越慢。
十六、从 Dapper 到 OTLP:Trace 模型标准化
Google Dapper(2010)定义 trace tree + span
annotation;OTLP 将其 protobuf 化并与 Logs/Metrics 共享
Resource。 标准化收益:Collector 一次接收,多后端
fan-out;成本是每 span 固定 protobuf overhead ~几十字节。 -
trace_id:OTLP 必选或推荐;Jaeger/Tempo
存储列均映射自此。 - span_id:OTLP
必选或推荐;Jaeger/Tempo 存储列均映射自此。 -
parent_span_id:OTLP 必选或推荐;Jaeger/Tempo
存储列均映射自此。 - name:OTLP
必选或推荐;Jaeger/Tempo 存储列均映射自此。 -
kind:OTLP 必选或推荐;Jaeger/Tempo
存储列均映射自此。 - attributes:OTLP
必选或推荐;Jaeger/Tempo 存储列均映射自此。 -
events:OTLP 必选或推荐;Jaeger/Tempo
存储列均映射自此。 - links:OTLP
必选或推荐;Jaeger/Tempo 存储列均映射自此。 -
status:OTLP 必选或推荐;Jaeger/Tempo
存储列均映射自此。 - resource:OTLP
必选或推荐;Jaeger/Tempo 存储列均映射自此。
十七、Metrics 与 Logs 的边界
| 信号 | 何时用 | 数据模型原因 |
|---|---|---|
| Counter/Gauge 聚合 | QPS、错误率、资源使用率 | 固定 label 空间,15s 采样 |
| Histogram | 延迟分位数 | bucket 预聚合,避免存每条请求 |
| Structured Log | 单次请求上下文、堆栈 | 高熵 message,不可预聚合 |
| Access Log 转 metric | 按 status 聚合 QPS | LogQL metric 或 Collector 计数 |
十八、Profile 与 Trace 的联合排障模型
Trace 回答「哪段调用慢」;Profile
回答「慢在哪个函数」。数据模型上 Trace span name 应能与
profile 栈顶帧语义对齐(同 operation 命名)。
Grafana 中 trace_id → profile 跳转依赖 exemplar 或 label
一致——见 Profiling。
十九、术语表(数据模型视角)
series:Prometheus:唯一 label set 对应一条时间序列。 chunk:Prometheus/Loki:压缩后的时间窗口数据单元。 block:Prometheus/Tempo:不可变持久化目录或 parquet 文件组。 stream:Loki:相同 label set 的日志序列。 posting list:倒排索引:label value → series/chunk ID 列表。 fingerprint:Loki:label set 哈希。 row group:Parquet:列存行组,TraceQL 扫描单位。 head chunk:Prometheus:仍接收样本的 open chunk。 WAL:预写日志,crash recovery。 compaction:后台合并小 block 为大 block。 tombstone:Prometheus 逻辑删除标记。 bloom filter:Loki/Tempo:chunk 级概率跳过。 exemplar:histogram 上附带的 trace 指针。 native histogram:Prometheus 新 histogram 编码。 segment:SkyWalking/Jaeger 原生 trace 分组单元。 resource:OTel 进程级属性,跨 signal 共享。
十三、下一步
理解了存储端内部表达后,工程问题回到管道与 Trace 栈:日志管道、Traces 栈与采样。
参考资料
- T. Pelkonen et al., Gorilla: A Fast, Scalable, In-Memory Time Series Database, VLDB 2015
- Prometheus, TSDB Format, v2.45+, https://github.com/prometheus/prometheus/tree/main/tsdb
- Grafana Loki, Architecture, https://grafana.com/docs/loki/latest/architecture/
- Grafana Tempo, Architecture, https://grafana.com/docs/tempo/latest/architecture/
- Jaeger, Storage Backends, https://www.jaegertracing.io/docs/latest/storage/
- ClickHouse, MergeTree Engine, https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree
- Google, pprof format, https://github.com/google/pprof
- CNCF, CloudEvents Specification, v1.0
- VictoriaMetrics, Technical Papers, https://docs.victoriametrics.com/
- OpenTelemetry, OTLP protobuf, https://github.com/open-telemetry/opentelemetry-proto
- 本系列 时序数据库内核
- 本系列 Logs 技术栈对比
下一篇:日志管道:Fluent Bit、Vector、Logstash、Cribl 的取舍
附录 A.1 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.2 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.3 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.4 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.5 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.6 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.7 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.8 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.9 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.10 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.11 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.12 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.13 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.14 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.15 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.16 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.17 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.18 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.19 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.20 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.21 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.22 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.23 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.24 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.25 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.26 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.27 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.28 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.29 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.30 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.31 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.32 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.33 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.34 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.35 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.36 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.37 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.38 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.39 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.40 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.41 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.42 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.43 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.44 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.45 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.46 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.47 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.48 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.49 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.50 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.51 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.52 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.53 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.54 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.55 Metric 下采样
recording rule 生成低分辨率 series,新 series 仍是 TSDB 模型。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.56 Prometheus 查询与 block 边界
跨 block 查询时 Querier 需 merge index
header;minTime/maxTime 过滤减少扫描块数。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.57 Loki 分片与 ingester 环
hash ring 决定 chunk 归属;扩缩容触发 flush 与 rebalance。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.58 Tempo vParquet 列布局
attribute 列可选;TraceQL 只读必要列降低 IO。
详见 07-tsdb-internals 与 20-storage-cost。
附录 A.59 日志采样与数据模型
采样在 Agent 层减少 chunk 写入 QPS,不改变 Loki 索引结构。
详见 07-tsdb-internals 与 20-storage-cost。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【可观测性工程】存储与成本:采样、下采样、冷热分层、对象存储
可观测性数据量持续增长,存储成本常超过计算成本。拆解四大支柱的成本结构、采样与保留期策略、冷热分层架构,以及带显式假设的成本估算 worksheet。
【可观测性工程】时序数据库内核:TSM、TSI、倒排索引与 Gorilla 压缩
深入时序数据库的存储内核:Prometheus TSDB 的 WAL 与块管理、InfluxDB 的 TSM 引擎与 TSI 倒排索引、Gorilla 压缩算法的数学原理、VictoriaMetrics mergeset 架构、ClickHouse MergeTree 作为 metrics 后端,以及国内大厂在 series churn 和 compaction 风暴上踩过的坑。
【可观测性工程】真实事故复盘剧本:从指标抖动到根因的全链路追查
虚构但可复现的 checkout 服务事故全链路:SLO Burn Rate 告警后按 Golden Minute→Metrics→Traces→Logs→Profile→Events 五阶递进排障,含 PromQL/LogQL/kubectl 命令与三条分级剧本,交叉引用系列 01–22。
【可观测性工程】埋点哲学:粒度、采样、基数爆炸与成本模型
埋点不是多加几行日志,而是一整套关于什么该记、什么该采样、什么该丢弃的工程决策体系。从信号分层、基数控制、采样策略到落地规范与工程坑点,给出可操作的埋点治理框架。