文章摘要 :凌晨 2 点 17 分,手机收到 3 条连续告警------InfluxDB 写入超时。登上服务器一看,写入延迟从日常的 5ms 飙到了 500ms,
top显示 influxd 进程的 I/O wait 飙到 85%。翻日志发现是 Level 3 Full Compaction 在整点触发,把磁盘 I/O 全占了。这就是 TSM Compaction 的暗面------你以为写入快就够了,但后台合并一旦失控,写入再快也得排队等 I/O。本文从这次事故出发,深入解析 TSM 的三层架构、完整写入路径、compaction 合并策略,给出生产环境最全的参数调优指南,以及 5 个存储引擎相关的故障排查案例。
预计阅读时间:18 分钟(全文约 7,000 字)
环境说明:
- InfluxDB 版本:1.8.x / 2.x(本文基于 2.7 验证)
- 操作系统:CentOS 7.9 / Ubuntu 22.04
- 存储引擎:TSM(Time Structured Merge Tree)
重要提示:InfluxDB 3.x 已弃用 TSM 引擎,改用列式存储引擎 IOx。本文内容适用于 InfluxDB 1.x 和 2.x。3.x 的存储架构完全不同,TSM 相关参数在 3.x 中不再适用,如需了解 3.x 请参考官方文档。
TSM 三层架构
#mermaid-svg-L2bF9neAURvRpiuz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-L2bF9neAURvRpiuz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-L2bF9neAURvRpiuz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-L2bF9neAURvRpiuz .error-icon{fill:#552222;}#mermaid-svg-L2bF9neAURvRpiuz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-L2bF9neAURvRpiuz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-L2bF9neAURvRpiuz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-L2bF9neAURvRpiuz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-L2bF9neAURvRpiuz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-L2bF9neAURvRpiuz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-L2bF9neAURvRpiuz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-L2bF9neAURvRpiuz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-L2bF9neAURvRpiuz .marker.cross{stroke:#333333;}#mermaid-svg-L2bF9neAURvRpiuz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-L2bF9neAURvRpiuz p{margin:0;}#mermaid-svg-L2bF9neAURvRpiuz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-L2bF9neAURvRpiuz .cluster-label text{fill:#333;}#mermaid-svg-L2bF9neAURvRpiuz .cluster-label span{color:#333;}#mermaid-svg-L2bF9neAURvRpiuz .cluster-label span p{background-color:transparent;}#mermaid-svg-L2bF9neAURvRpiuz .label text,#mermaid-svg-L2bF9neAURvRpiuz span{fill:#333;color:#333;}#mermaid-svg-L2bF9neAURvRpiuz .node rect,#mermaid-svg-L2bF9neAURvRpiuz .node circle,#mermaid-svg-L2bF9neAURvRpiuz .node ellipse,#mermaid-svg-L2bF9neAURvRpiuz .node polygon,#mermaid-svg-L2bF9neAURvRpiuz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-L2bF9neAURvRpiuz .rough-node .label text,#mermaid-svg-L2bF9neAURvRpiuz .node .label text,#mermaid-svg-L2bF9neAURvRpiuz .image-shape .label,#mermaid-svg-L2bF9neAURvRpiuz .icon-shape .label{text-anchor:middle;}#mermaid-svg-L2bF9neAURvRpiuz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-L2bF9neAURvRpiuz .rough-node .label,#mermaid-svg-L2bF9neAURvRpiuz .node .label,#mermaid-svg-L2bF9neAURvRpiuz .image-shape .label,#mermaid-svg-L2bF9neAURvRpiuz .icon-shape .label{text-align:center;}#mermaid-svg-L2bF9neAURvRpiuz .node.clickable{cursor:pointer;}#mermaid-svg-L2bF9neAURvRpiuz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-L2bF9neAURvRpiuz .arrowheadPath{fill:#333333;}#mermaid-svg-L2bF9neAURvRpiuz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-L2bF9neAURvRpiuz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-L2bF9neAURvRpiuz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-L2bF9neAURvRpiuz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-L2bF9neAURvRpiuz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-L2bF9neAURvRpiuz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-L2bF9neAURvRpiuz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-L2bF9neAURvRpiuz .cluster text{fill:#333;}#mermaid-svg-L2bF9neAURvRpiuz .cluster span{color:#333;}#mermaid-svg-L2bF9neAURvRpiuz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-L2bF9neAURvRpiuz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-L2bF9neAURvRpiuz rect.text{fill:none;stroke-width:0;}#mermaid-svg-L2bF9neAURvRpiuz .icon-shape,#mermaid-svg-L2bF9neAURvRpiuz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-L2bF9neAURvRpiuz .icon-shape p,#mermaid-svg-L2bF9neAURvRpiuz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-L2bF9neAURvRpiuz .icon-shape .label rect,#mermaid-svg-L2bF9neAURvRpiuz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-L2bF9neAURvRpiuz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-L2bF9neAURvRpiuz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-L2bF9neAURvRpiuz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后台任务
写入路径
固定间隔或超阈值
Level 1 Compaction
Level 2 Compaction
Level 3 Full Compaction
查询路径
查询请求
查询 Cache+WAL
查询 TSM 文件
HTTP/Line Protocol
Cache
内存中缓存
WAL
预写日志-磁盘
TSM 文件
持久化数据文件
较小的 TSM 文件
合并排序
较大的 TSM 文件
去重+压缩
完整合并
清理已删除数据
写入路径详解:一条数据怎么落盘?
下面用时序图展示一条数据从 HTTP API 到最终落盘的完整生命周期:
TSM File(磁盘) WAL(磁盘) Cache(内存) Engine HTTP API(8086) Client TSM File(磁盘) WAL(磁盘) Cache(内存) Engine HTTP API(8086) Client #mermaid-svg-IfF6YM0QHI7Kwtn2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .error-icon{fill:#552222;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .marker.cross{stroke:#333333;}#mermaid-svg-IfF6YM0QHI7Kwtn2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IfF6YM0QHI7Kwtn2 p{margin:0;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IfF6YM0QHI7Kwtn2 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IfF6YM0QHI7Kwtn2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-IfF6YM0QHI7Kwtn2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .sequenceNumber{fill:white;}#mermaid-svg-IfF6YM0QHI7Kwtn2 #sequencenumber{fill:#333;}#mermaid-svg-IfF6YM0QHI7Kwtn2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .messageText{fill:#333;stroke:none;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .labelText,#mermaid-svg-IfF6YM0QHI7Kwtn2 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .loopText,#mermaid-svg-IfF6YM0QHI7Kwtn2 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IfF6YM0QHI7Kwtn2 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .noteText,#mermaid-svg-IfF6YM0QHI7Kwtn2 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .actorPopupMenu{position:absolute;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-IfF6YM0QHI7Kwtn2 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IfF6YM0QHI7Kwtn2 .actor-man circle,#mermaid-svg-IfF6YM0QHI7Kwtn2 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-IfF6YM0QHI7Kwtn2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Cache 达到阈值或达到写入冷却时间 TSM 文件就绪进入 Level 0 alt触发 Snapshot 条件 Level 0 → Level 1小文件合并排序 Level 1 → Level 2去重+压缩 Level 2 → Level 3Full Compaction alt后台Compaction- POST /write?db=mydbcpu,host=web01 value=42.5解析 Line Protocol写入 Points 到 Cache追加 WAL 日志生成 Snapshot 快照写入 TSM 文件截断已刷新的 WAL 段
写入流程三步走
| 步骤 | 组件 | 做了什么 | 耗时参考 |
|---|---|---|---|
| ① | HTTP API | 解析 Line Protocol 协议,校验数据格式 | < 0.1ms |
| ② | Cache + WAL | 写入内存 Cache(立即返回 204),同时追加磁盘 WAL | 0.5~2ms |
| ③ | Snapshot | 后台异步将 Cache 刷到 TSM 文件 | 100ms~2s |
关键设计哲学:写入路径上只有顺序写,没有随机写。 Cache 是纯内存操作,WAL 是顺序追加写,TSM 文件也是一次性写入不再修改。这是 InfluxDB 写入性能远超传统 B+Tree 数据库的根本原因。
Cache 刷盘触发条件
Cache 数据刷到 TSM 文件(即 Snapshot)由以下条件触发,任一满足即可:
以下伪代码展示了 Snapshot 触发的三种条件判断逻辑:
go
// 伪代码:Snapshot 触发逻辑
if cache.Size() > cacheSnapshotMemorySize { // 内存超过阈值
triggerSnapshot()
} else if time.Since(lastWrite) > cacheSnapshotWriteColdDuration { // 写入冷却
triggerSnapshot()
} else if cache.Size() > cacheMaxMemorySize { // 内存达到上限,强制刷
triggerSnapshot()
// 此时写入开始阻塞,直到刷完
}
核心参数调优(完整版)
参数速查表
| 参数分类 | 参数名 | 默认值 | 推荐值(高写入) | 推荐值(低内存) | 说明 |
|---|---|---|---|---|---|
| Cache | cache-max-memory-size | 1g | 2g~4g | 512m | Cache 最大内存,超过后阻塞写入 |
| Cache | cache-snapshot-memory-size | 256m | 512m~1g | 128m | 触发 Snapshot 的内存阈值 |
| Cache | cache-snapshot-write-cold-duration | 10m | 20m | 5m | 写入冷却时间后自动刷盘 |
| WAL | wal-logging-enabled | true | true | true | 禁用 WAL 提高写入但丢失数据 |
| WAL | wal-max-concurrent-writes | 10 | 20~50 | 10 | 并发写 WAL 的 Goroutine 数 |
| WAL | wal-flush-interval | 10m | 10m | 5m | WAL 刷磁盘间隔 |
| Compaction | max-concurrent-compactions | 4 | 2(避免 I/O 争抢) | 1 | 最大并行 Compaction 数 |
| Compaction | compact-throughput | 48mb | 32mb | 16mb | Compaction 写入限速 |
| Compaction | compact-throughput-burst | 48mb | 64mb | 32mb | Compaction 突发写入上限 |
| Compaction | compact-full-write-cold-duration | 4h | 6h~12h | 4h | 写入冷却后执行 Full Compaction |
TSM 完整生产配置
为什么需要这份完整配置:TSM 引擎的参数相互关联,调一个参数可能影响其他参数的效果。以下是我们生产环境经过 3 轮压测后的完整配置,注释中说明了每个值的调优依据:
toml
# influxdb.conf --- TSM 存储引擎完整生产配置
[data]
# ── Cache 参数 ─────────────────────────────────
# 服务器 32GB 内存,分配 4GB 给 Cache(约 12%)
cache-max-memory-size = "4g"
# Snapshot 阈值设为 max 的 25%,避免 Cache 积压太大
cache-snapshot-memory-size = "1g"
# 20 分钟无写入就自动刷盘,防止 WAL 膨胀
cache-snapshot-write-cold-duration = "20m"
# ── WAL 参数 ───────────────────────────────────
# 生产环境绝对不能关闭 WAL
wal-logging-enabled = true
# 写入并发高时增大,减少排队等待
wal-max-concurrent-writes = 30
# WAL fsync 间隔,10 分钟是平衡可靠性和性能的经验值
wal-flush-interval = "10m"
# WAL 启用压缩,减少磁盘占用
wal-ready-series-size = 256
wal-compaction-threshold = 0.5
wal-max-series-size = 256
# ── Compaction 参数 ────────────────────────────
# 只开 2 个并发,避免跟写入争抢 I/O
max-concurrent-compactions = 2
# 限制 compaction 带宽到 32MB/s,留带宽给写入
compact-throughput = "32mb"
# 突发上限 64MB/s,允许短时突发
compact-throughput-burst = "64mb"
# 写入冷却 6 小时后做 Full Compaction(安排在凌晨低峰期)
compact-full-write-cold-duration = "6h"
# ── TSI 索引 ──────────────────────────────────
# 必须开启 TSI,否则高基数查询会全表扫描
index-version = "tsi1"
# ── 存储路径 ──────────────────────────────────
dir = "/data/influxdb/data"
wal-dir = "/data/influxdb/wal"
[coordinator]
# 写入超时,避免慢客户端占着连接不放
write-timeout = "30s"
# 最大并发查询数
max-concurrent-queries = 20
[http]
# HTTP API 配置
bind-address = ":8086"
max-connection-limit = 1000
max-body-size = 25000000 # 25MB 写入请求上限
[retention]
# 保留策略检查间隔
check-interval = "30m"
Cache 参数详解
以下为 Cache 部分的关键参数配置,直接影响写入缓冲和刷盘行为:
toml
# influxdb.conf --- Cache 部分
[data]
cache-max-memory-size = "1g" # Cache 最大内存(超过后写入开始阻塞)
cache-snapshot-memory-size = "256m" # Cache 达到此值时拍快照并写入 TSM
cache-snapshot-write-cold-duration = "10m" # 最后一次写入后过多久触发快照
- cache-max-memory-size :Cache 的上限水位。一旦 Cache 大小超过此值,InfluxDB 会阻塞所有写入直到 Snapshot 完成释放内存。如果磁盘 I/O 跟不上写入速度,这个值设得太大会导致 OOM,设得太小会导致频繁阻塞。
- cache-snapshot-memory-size :Cache 超过此值就启动 Snapshot。建议设为
max-memory-size的 25%~50%。太高会导致 Cache 占用内存过大,GC 压力上升。 - cache-snapshot-write-cold-duration:即便 Cache 未达到内存阈值,如果长时间没有新写入,也触发 Snapshot 把冷数据刷盘。默认 10 分钟,对大部分场景够用。
WAL 参数详解
以下为 WAL 部分的关键参数配置,决定了写入持久性和并发能力:
toml
# influxdb.conf --- WAL 部分
[data]
wal-logging-enabled = true # 是否启用 WAL(生产环境必须开)
wal-max-concurrent-writes = 10 # WAL 最大并发写入 goroutine 数
wal-flush-interval = "10m" # WAL 数据刷到磁盘的时间间隔
- wal-logging-enabled :是否写 WAL。关闭后写入速度能提升 30%~50%,但一旦宕机会丢失所有未刷盘的 Cache 数据。生产环境绝不要关闭。
- wal-max-concurrent-writes:WAL 并发写入的 goroutine 数。当写入并发高时,适当增大可以减少 WAL 写入排队等待。但如果磁盘是 HDD 而非 SSD,增大此值反而会因为磁头寻道变慢。
- wal-flush-interval :WAL 数据从 OS Page Cache 刷到磁盘的间隔。
10m表示每 10 分钟 fsync 一次。间隔越长,宕机时丢失的数据越多(最多丢失一个 flush interval 的数据)。追求更高可靠性的场景可以缩短到1m。
Compaction 参数详解
以下为 Compaction 部分的关键参数配置,控制后台合并的 I/O 节奏:
toml
# influxdb.conf --- Compaction 部分
[data]
max-concurrent-compactions = 4 # 最大并发 compaction 数
compact-throughput = "48mb" # compaction 写入磁盘的限速(每秒)
compact-throughput-burst = "48mb" # compaction 突发写入上限
compact-full-write-cold-duration = "4h" # 最后一次写入经过多久后执行 full compaction
- max-concurrent-compactions:并行执行的 compaction 任务数。数值越大,Compaction 越快完成,但同时 I/O 争抢越激烈。高写入场景下建议设为 2,避免 Compaction 与写入争抢 I/O 导致写入延迟抖动。
- compact-throughput / compact-throughput-burst:限制 Compaction 的写入带宽。默认 48MB/s。如果业务对写入延迟敏感,降到此值的一半(如 24MB/s)。Burst 是短时突发上限,设为 Throughput 的 1.5~2 倍即可。
- compact-full-write-cold-duration:最后一次写入完成后等待多久再触发 Full Compaction。Full Compaction 是把所有 Level 合并为一个文件,读写量最大。建议设置在业务低峰期后。例如业务凌晨 2 点后写入很少,设到 6h 就能保证凌晨 2~8 点之间执行。
Compaction 监控 Flux 脚本
为什么需要 Compaction 监控:Compaction 是"静默工作"的后台任务,你不会收到告警,但它可能正在吃掉你的 I/O 和 CPU。以下 Flux 脚本用于在 Grafana 中实时监控 Compaction 的健康状况:
flux
// compaction_monitor.flux --- Compaction 健康监控仪表盘
// 数据源:InfluxDB _internal 库
// 1. Compaction 执行耗时趋势(超过 30 分钟为异常)
from(bucket: "_internal")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "tsm1_engine")
|> filter(fn: (r) => r._field == "compactionDurationMs")
|> aggregateWindow(every: 5m, fn: max, createEmpty: false)
|> yield(name: "compaction_duration")
// 2. Compaction 错误数(持续增长说明有问题)
from(bucket: "_internal")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "tsm1_engine")
|> filter(fn: (r) => r._field == "compactionErrors")
|> derivative(unit: 5m, nonNegative: true)
|> yield(name: "compaction_errors_rate")
// 3. Cache 内存使用量(接近 max 说明刷盘跟不上)
from(bucket: "_internal")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "tsm1_cache")
|> filter(fn: (r) => r._field == "cacheMemoryBytes")
|> aggregateWindow(every: 1m, fn: max, createEmpty: false)
|> yield(name: "cache_memory")
// 4. WAL 文件数和大小(持续增长说明 Snapshot 不及时)
from(bucket: "_internal")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "wal")
|> filter(fn: (r) => r._field == "numFiles" or r._field == "totalBytes")
|> aggregateWindow(every: 5m, fn: max, createEmpty: false)
|> yield(name: "wal_status")
// 5. 写入延迟(Compaction 期间应关注此指标)
from(bucket: "_internal")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "httpd")
|> filter(fn: (r) => r._field == "writeReqDurationNs")
|> map(fn: (r) => ({ r with _value: r._value / 1000000.0 }))
|> aggregateWindow(every: 1m, fn: percentile, p: 0.99, createEmpty: false)
|> yield(name: "write_latency_p99")
WAL 恢复 Bash 脚本
为什么需要 WAL 恢复脚本:WAL 文件损坏是 InfluxDB 最棘手的问题之一------实例无法启动,日志报 corrupt WAL segment。手动恢复步骤复杂且容易出错,这个脚本将恢复流程自动化:
bash
#!/bin/bash
# wal_recovery.sh --- InfluxDB WAL 损坏恢复脚本
# 使用场景:InfluxDB 因 WAL 损坏无法启动
# 前提:InfluxDB 服务已停止
# 警告:执行前务必先备份整个 data 和 wal 目录!
set -euo pipefail
INFLUXDB_DATA_DIR="/var/lib/influxdb2/engine/data"
INFLUXDB_WAL_DIR="/var/lib/influxdb2/engine/wal"
BACKUP_DIR="/backup/influxdb_wal_$(date +%Y%m%d_%H%M%S)"
EXPORT_DIR="/tmp/wal_export_$(date +%Y%m%d_%H%M%S)"
LOG_FILE="/var/log/influxdb_wal_recovery.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Step 1: 备份原始数据
log "Step 1: Backing up WAL directory to $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
cp -r "$INFLUXDB_WAL_DIR" "$BACKUP_DIR/"
log "Backup complete: $(du -sh "$BACKUP_DIR" | cut -f1)"
# Step 2: 检查 WAL 文件完整性
log "Step 2: Checking WAL file integrity"
corrupt_files=()
for wal_file in "$INFLUXDB_WAL_DIR"/*.wal; do
if [ -f "$wal_file" ]; then
if ! influxd inspect verify-wal -file "$wal_file" 2>>"$LOG_FILE"; then
corrupt_files+=("$wal_file")
log "CORRUPT: $wal_file"
else
log "OK: $wal_file"
fi
fi
done
if [ ${#corrupt_files[@]} -eq 0 ]; then
log "No corrupt WAL files found. The issue may be elsewhere."
exit 0
fi
log "Found ${#corrupt_files[@]} corrupt WAL file(s)"
# Step 3: 尝试导出可读数据
log "Step 3: Exporting readable data from WAL"
mkdir -p "$EXPORT_DIR"
for wal_file in "${corrupt_files[@]}"; do
filename=$(basename "$wal_file")
log "Exporting $filename..."
# influxd inspect export-wal 会跳过损坏的段,导出可读部分
if influxd inspect export-wal \
-wal-path "$wal_file" \
-out "$EXPORT_DIR/${filename}.txt" 2>>"$LOG_FILE"; then
log "Export OK: ${filename}.txt"
else
log "Export PARTIAL: ${filename}.txt (some segments skipped)"
fi
done
# Step 4: 清除损坏的 WAL 文件
log "Step 4: Removing corrupt WAL files"
for wal_file in "${corrupt_files[@]}"; do
log "Removing: $wal_file"
rm -f "$wal_file"
done
# Step 5: 重新导入可读数据(如果有的话)
log "Step 5: Re-importing salvaged data"
export_count=$(find "$EXPORT_DIR" -name "*.txt" -size +0c | wc -l)
if [ "$export_count" -gt 0 ]; then
log "Found $export_count exported file(s), attempting re-import..."
for export_file in "$EXPORT_DIR"/*.txt; do
if [ -s "$export_file" ]; then
# 使用 influx write 将行协议数据重新写入
# 需要先启动 InfluxDB
log "Re-import data from: $export_file"
log "NOTE: Start InfluxDB first, then run:"
log " influx write -b <bucket> -o <org> -f $export_file"
fi
done
else
log "No salvageable data found. Data loss may have occurred."
fi
log "Recovery script complete."
log "Summary:"
log " - Corrupt files: ${#corrupt_files[@]}"
log " - Backup location: $BACKUP_DIR"
log " - Export location: $EXPORT_DIR"
log " - Next step: Start InfluxDB and re-import salvageable data"
Compaction 策略性能对比测试脚本
为什么需要对比测试:TSM 参数调优不是拍脑袋,不同业务场景的最优配置差异很大。这个脚本自动化执行不同参数组合的写入/查询压测,帮你找到最适合的配置:
bash
#!/bin/bash
# compaction_benchmark.sh --- TSM Compaction 策略性能对比测试
# 用法:./compaction_benchmark.sh
# 输出:各参数组合下的写入 TPS、写入延迟 P99、查询延迟、磁盘 I/O
set -euo pipefail
INFLUX_URL="http://localhost:8086"
INFLUX_TOKEN="${INFLUX_TOKEN}"
ORG="benchmark"
BUCKET="tsm_test"
DURATION="300s" # 每轮测试 5 分钟
POINTS_PER_SEC=50000 # 目标写入速率
LOG_DIR="/tmp/tsm_benchmark_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$LOG_DIR"
# 测试参数组合
declare -A PROFILES
PROFILES=(
["default"]="max_concurrent=4:throughput=48mb:burst=48mb:cold=4h"
["conservative"]="max_concurrent=2:throughput=32mb:burst=64mb:cold=6h"
["aggressive"]="max_concurrent=8:throughput=64mb:burst=128mb:cold=2h"
["low_io"]="max_concurrent=1:throughput=16mb:burst=32mb:cold=12h"
)
log() {
echo "[$(date '+%H:%M:%S')] $1"
}
# 修改 InfluxDB 配置
apply_config() {
local profile=$1
local max_concurrent throughput burst cold
IFS=':' read -ra params <<< "${PROFILES[$profile]}"
for param in "${params[@]}"; do
key="${param%%=*}"
value="${param#*=}"
case $key in
max_concurrent) max_concurrent=$value ;;
throughput) throughput=$value ;;
burst) burst=$value ;;
cold) cold=$value ;;
esac
done
log "Applying profile: $profile"
log " max_concurrent=$max_concurrent, throughput=$throughput, burst=$burst, cold=$cold"
# 生成配置片段
cat > /tmp/tsm_profile.conf <<EOF
[data]
max-concurrent-compactions = $max_concurrent
compact-throughput = "$throughput"
compact-throughput-burst = "$burst"
compact-full-write-cold-duration = "$cold"
EOF
# 重启 InfluxDB 使配置生效
sudo systemctl restart influxdb
sleep 10 # 等待启动完成
# 清空测试 Bucket
influx delete --bucket "$BUCKET" --org "$ORG" \
--start 1970-01-01T00:00:00Z --stop 2030-01-01T00:00:00Z \
--token "$INFLUX_TOKEN" 2>/dev/null || true
}
# 执行写入压测
run_write_benchmark() {
local profile=$1
local result_file="$LOG_DIR/${profile}_write.json"
log "Running write benchmark for profile: $profile"
# 使用 influx write 写入测试数据
# 生成行协议数据并写入
start_time=$(date +%s)
end_time=$((start_time + 300))
total_points=0
while [ $(date +%s) -lt $end_time ]; do
# 生成一批行协议数据(每批 5000 点)
batch=""
for i in $(seq 1 5000); do
ts=$(date +%s%N)
value=$(echo "scale=2; $RANDOM / 1000" | bc)
batch+="cpu,host=server-$((RANDOM % 100)),region=us-west value=${value} ${ts}${NL}"
done
# 写入
echo "$batch" | influx write --bucket "$BUCKET" --org "$ORG" \
--token "$INFLUX_TOKEN" --precision ns 2>/dev/null
total_points=$((total_points + 5000))
done
actual_duration=$(($(date +%s) - start_time))
tps=$((total_points / actual_duration))
log " Write TPS: $tps points/sec"
log " Total points: $total_points"
echo "{\"profile\":\"$profile\",\"tps\":$tps,\"total\":$total_points}" \
>> "$result_file"
}
# 执行查询压测
run_query_benchmark() {
local profile=$1
local result_file="$LOG_DIR/${profile}_query.json"
log "Running query benchmark for profile: $profile"
# 查询测试:5 种典型查询
queries=(
"from(bucket:\"$BUCKET\") |> range(start:-1h) |> filter(fn:(r)=>r._measurement==\"cpu\") |> mean()"
"from(bucket:\"$BUCKET\") |> range(start:-1h) |> filter(fn:(r)=>r._measurement==\"cpu\") |> max()"
"from(bucket:\"$BUCKET\") |> range(start:-1h) |> filter(fn:(r)=>r.host==\"server-1\") |> last()"
"from(bucket:\"$BUCKET\") |> range(start:-6h) |> filter(fn:(r)=>r._measurement==\"cpu\") |> aggregateWindow(every:5m,fn:mean)"
"from(bucket:\"$BUCKET\") |> range(start:-1h) |> filter(fn:(r)=>r._measurement==\"cpu\") |> group(columns:[\"host\"]) |> count()"
)
for i in "${!queries[@]}"; do
start_ms=$(date +%s%N)
influx query --org "$ORG" --token "$INFLUX_TOKEN" \
"${queries[$i]}" > /dev/null 2>&1
end_ms=$(date +%s%N)
latency_ms=$(( (end_ms - start_ms) / 1000000 ))
log " Query $((i+1)) latency: ${latency_ms}ms"
echo "{\"profile\":\"$profile\",\"query\":$((i+1)),\"latency_ms\":$latency_ms}" \
>> "$result_file"
done
}
# 收集系统指标
collect_metrics() {
local profile=$1
local metrics_file="$LOG_DIR/${profile}_system.json"
# 磁盘 I/O 统计
iostat -xd 5 1 > "$LOG_DIR/${profile}_iostat.txt" 2>&1 || true
# InfluxDB 进程内存
influx_pid=$(pgrep -f influxd || echo "")
if [ -n "$influx_pid" ]; then
rss=$(ps -o rss= -p "$influx_pid" | tr -d ' ')
log " InfluxDB RSS: $((rss / 1024)) MB"
echo "{\"profile\":\"$profile\",\"rss_mb\":$((rss / 1024))}" >> "$metrics_file"
fi
}
# ── 主测试循环 ─────────────────────────────────────
log "Starting TSM Compaction benchmark"
log "Results directory: $LOG_DIR"
log "Test profiles: ${!PROFILES[*]}"
log "========================================"
for profile in "${!PROFILES[@]}"; do
log ""
log "===== Testing profile: $profile ====="
apply_config "$profile"
run_write_benchmark "$profile"
run_query_benchmark "$profile"
collect_metrics "$profile"
log "Profile $profile complete"
done
# 生成汇总报告
log ""
log "===== Summary ====="
log "Results saved to: $LOG_DIR"
for profile in "${!PROFILES[@]}"; do
write_file="$LOG_DIR/${profile}_write.json"
query_file="$LOG_DIR/${profile}_query.json"
if [ -f "$write_file" ]; then
tps=$(cat "$write_file" | grep -o '"tps":[0-9]*' | cut -d: -f2)
log " $profile: write_tps=$tps"
fi
done
log "Benchmark complete. Full results in $LOG_DIR"
踩坑 1:Compaction 导致写入延迟抖动
现象:每到整点,InfluxDB 的写入延迟从 5ms 飙升到 500ms,持续 2-3 分钟。Grafana 上的写入延迟曲线呈现规律的"毛刺"。
原因:整点触发了 Level 3 Full Compaction,大量读磁盘 + 写磁盘操作挤占了写入 I/O。我们的 InfluxDB 部署在一台 4 核 16G 的虚拟机上,WAL 和 TSM 文件共用一块 SSD,Compaction 一旦启动,写入请求必须排队等 I/O。
解决:限制 compaction 的 I/O 带宽,把 full compaction 安排在低峰期。
通过限制 compaction 带宽和安排低峰期执行来缓解写入延迟抖动:
toml
# 限制 compaction 带宽
compact-throughput = "32mb"
compact-throughput-burst = "64mb"
# 安排 full compaction 在凌晨执行
compact-full-write-cold-duration = "6h"
同时将 WAL 迁移到独立 NVMe 磁盘,从根本上消除 I/O 争抢:
toml
# 将 WAL 放到独立磁盘
[data]
dir = "/data/influxdb/data" # TSM 文件:SATA SSD
wal-dir = "/nvme/influxdb/wal" # WAL:NVMe SSD
踩坑 2:WAL 文件太大导致启动变慢
现象:InfluxDB 重启后花了 15 分钟才恢复服务,日志显示在 replay WAL。
原因:WAL 文件积累了 4GB 未刷新的数据,重启时需要逐条回放。
解决:缩短 Cache snapshot 间隔 + 重启前手动 flush。
缩短 snapshot 间隔并在重启前手动 flush 来加速 WAL 回放:
bash
# 缩短 snapshot 间隔
# 在 influxdb.conf 中修改
cache-snapshot-write-cold-duration = "10m" # 10 分钟无写入就拍快照
# 重启前手动 flush
influxd inspect export-wal -dir /var/lib/influxdb2/wal -out /tmp/export
踩坑 3:Series 基数膨胀导致 Compaction 死循环
现象 :InfluxDB 持续高负载运行,磁盘 I/O 始终 100%,但写入吞吐从 8 万点/秒一路掉到 3 千点/秒。SHOW STATS 看到 compaction_errors 持续增长。
根因 :某业务线在 Tag 中加了 request_id(UUID 格式),导致 Series 基数从 2 万暴增到 500 万。Compaction 在处理这些高基数数据时,索引合并操作消耗了大量内存,部分 Compaction 任务 OOM 后被 Kill,然后又重新调度,形成死循环。
使用 InfluxQL 命令快速定位高基数问题所在的 measurement:
bash
# 快速定位高基数
influx -database mydb -execute "SHOW SERIES EXACT CARDINALITY"
# 输出:5000000 ← 严重超标
# 排查具体是哪个 measurement
influx -database mydb -execute "SHOW SERIES EXACT CARDINALITY ON mydb FROM cpu"
influx -database mydb -execute "SHOW SERIES EXACT CARDINALITY ON mydb FROM http_request"
# 发现 http_request 的基数占了 480 万
解决:立刻删除问题 measurement 的数据,然后在 Schema 层面禁止高基数字段进入 Tag。
删除问题数据并将高基数字段从 Tag 降级为 Field:
bash
# 紧急删除问题数据(数据量大时用 delete + 等待 compaction 清理)
influx -database mydb -execute "DROP MEASUREMENT http_request"
# 长期方案:在写入端做好 Tag 白名单
# 如果必须保留 request_id,降级为 Field
# 写入格式从:
# http_request,service=api,request_id=abc-123 duration=45
# 改为:
# http_request,service=api duration=45,request_id="abc-123"
教训:Tag 的 Cardinality 必须做预算。每个 Tag 的取值数量、组合后的总 Series 数,上线前先评估。超过 100 万的 Series 就要拉警报。
踩坑 4:磁盘空间不足导致写入失败和 WAL 损坏
现象 :凌晨 3 点收到告警,InfluxDB 写入全部返回 500 错误。日志显示 write: no space left on device。检查磁盘,/var/lib/influxdb2 所在分区已用 100%。
原因 :数据保留策略(Retention Policy)设为 90 天,但磁盘容量只够存 60 天。由于没有提前做容量规划,磁盘被写爆后 InfluxDB 无法写入新数据,也无法完成 Compaction。更严重的是,部分 WAL 文件在磁盘满的时刻写入不完整,重启后 WAL Replay 报错 corrupt WAL segment。
排查磁盘空间和保留策略配置:
bash
# 查看各目录空间占用
du -sh /var/lib/influxdb2/*
# 4.5G data
# 12G wal ← WAL 异常大,说明数据刷盘滞后
# 280M meta
# 查看 retention policy
influx -database mydb -execute "SHOW RETENTION POLICIES"
# name duration shardGroupDuration replicaN default
# ---- -------- ------------------ -------- -------
# autogen 90d 168h 1 true
解决:三步紧急恢复 + 预防措施。
三步紧急恢复:缩短保留策略、强制 compaction、修复损坏 WAL:
bash
# ① 紧急清理:缩短 retention policy,立即释放空间
influx -database mydb -execute "ALTER RETENTION POLICY autogen ON mydb DURATION 30d"
# ② 强制 compaction 回收磁盘空间
influxd inspect build-tsi -dir /var/lib/influxdb2/data
# ③ 修复损坏的 WAL(如果上述步骤后仍无法启动)
influxd inspect export-wal -dir /var/lib/influxdb2/wal -out /tmp/wal_export.json
# 清除损坏 WAL
rm -rf /var/lib/influxdb2/wal/*
influxd inspect import-wal -dir /var/lib/influxdb2/wal -in /tmp/wal_export.json
预防方案:磁盘监控阈值设为 70% 告警、85% 严重告警、95% 自动触发数据清理。保留策略留 20% 余量。
踩坑 5:时间戳精度不一致导致数据覆盖
现象:某个 IoT 项目的温度传感器数据出现奇怪的"跳变"------同一台设备同一时间的温度,有时显示 25°C,有时显示 18°C,没有规律。
原因 :数据采集端的 SDK 发送的时间戳是秒级精度(timestamp: 1696123456),而另一个采集程序发送的是毫秒级精度(timestamp: 1696123456000)。InfluxDB 在合并这两条数据时,由于时间戳不一致,生成了两条不同的 Points。查询时返回的结果取决于哪条数据最后被 Compaction 合并进去。
排查时间戳精度不一致导致的数据覆盖问题:
bash
# 排查:检查数据是否有重复的时间线
influx -database iot -execute "SELECT * FROM temperature WHERE device='sensor01' LIMIT 10"
# 发现同一秒内出现两条记录
# time value
# 2023-10-01T12:00:00Z 25.0 ← 秒级精度
# 2023-10-01T12:00:00Z 18.0 ← 毫秒级精度,实际是同一时刻
解决:统一所有写入端的时间戳精度,最好在采集端就约定好。
通过 API 层或全局配置统一时间戳精度:
bash
# 方案一:在 HTTP API 层指定精度(推荐)
curl -X POST 'http://localhost:8086/write?db=iot&precision=s' \
--data-binary 'temperature,device=sensor01 value=25.0 1696123456'
# 方案二:在 influxdb.conf 中强制精度(全局生效)
toml
[http]
write-precision = "s" # 强制所有写入使用秒级精度
教训:时间戳精度不一致是最隐蔽的数据质量问题之一。数据对不上账的时候,先查各端的时间戳精度是否统一。
TSM 参数优化检查清单
生产环境 TSM 参数优化的逐项检查清单:
markdown
【Cache 参数】
□ cache-max-memory-size = 物理内存的 10%-20%(不要超过 4GB)
□ cache-snapshot-memory-size = cache-max-memory-size 的 25%
□ cache-snapshot-write-cold-duration = 10-30min(写入冷却期)
【WAL 参数】
□ wal-logging-enabled = true(不要关)
□ wal-flush-interval = 10min(不要太短,否则频繁 fsync)
□ max-concurrent-compactions = CPU 核数 / 2
【Compaction 参数】
□ compact-throughput = 32-48MB(限制 I/O 带宽)
□ compact-full-write-cold-duration = 4-6h(安排在业务低峰期)
□ 监控 compaction 耗时(超过 30 分钟说明有问题)
【运维检查】
□ 定期查看 WAL 目录大小(超过 1GB 需要关注)
□ 查看 compaction 日志(是否有失败)
□ 在低峰期手动触发 full compaction
influxd inspect compact-shard -shard-id SHARD_ID
FAQ(TSM 存储引擎运维常见问题)
Q1:TSM 文件越来越多,磁盘使用量持续增长,但数据量没怎么变,为什么?
这通常是 Compaction 跟不上写入速度导致的。TSM 文件写入后停留在 Level 0(小文件),还没来得及合并就被新文件覆盖。
检查 max-concurrent-compactions 是否设得太低(建议设为 CPU 核数的一半),以及 compact-throughput 是否限制过严(建议不低于 32MB/s)。另外确认是否开启了 TSI 索引,如果使用旧的 TSM 索引,索引文件不会自动回收。可以用 influxd inspect verify 检查文件完整性,用 SHOW SHARDS 查看各分片的文件数量是否异常。
Q2:Cache 命中率怎么查看?Cache 满了会影响查询性能吗?
InfluxDB 没有直接的 Cache 命中率指标,但可以通过监控 cache-max-memory-size 的使用比例间接判断。Cache 主要用于缓冲写入数据,查询时 Engine 会同时扫描 Cache + WAL + TSM 文件,因此 Cache 不是传统意义上的"查询缓存"。
当 Cache 满(达到 cache-max-memory-size)时,写入会被阻塞,此时查询不受直接影响,但数据不再进入 Cache,查询需要从 TSM 文件读取,冷查询 的延迟会上升。建议监控 influxdb_cache_busy 指标,如果长时间 > 0,说明 Cache 刷盘跟不上写入,需要增大 cache-snapshot-memory-size。
Q3:Full Compaction 执行时间太长,会影响业务吗?
Full Compaction(Level 3)会读取所有 Level 的 TSM 文件并合并写入一个新文件,I/O 和 CPU 开销最大。如果执行时间超过 1 小时,说明磁盘 I/O 带宽不足或数据量过大。
影响业务的程度取决于 compact-throughput 和磁盘 I/O 隔离策略。建议:① 将 Full Compaction 安排在业务低峰期(通过 compact-full-write-cold-duration 控制);② 将 WAL 和 TSM 放在不同的磁盘上,避免 Compaction 的 I/O 与写入争抢;③ 如果 Compaction 持续影响写入延迟,降低 max-concurrent-compactions 到 1 或 2。
另一种思路是手动拆分为多个较小分片(缩短 Shard Duration),每个分片独立 Compaction,减少单次合并的数据量。
Q4:WAL 文件可以删除吗?WAL 目录占用太大怎么办?
不要手动删除 WAL 文件 。WAL 数据如果未刷到 TSM 文件,删除后会导致数据永久丢失。正确的做法是:先确保 Cache 已刷盘(可以触发手动 Snapshot:influxd inspect build-tsi),然后通过 influxd inspect export-wal 导出并清除 WAL。
日常运维中 WAL 目录大小不应超过 1GB,如果持续增长说明 Cache 刷盘不及时,需要缩短 cache-snapshot-write-cold-duration 或增大 cache-snapshot-memory-size。另外 WAL 文件的磁盘性能直接影响写入延迟,建议将 WAL 放在独立的 SSD/NVMe 盘上。
Q5:如何判断当前配置的 Cache 大小是否合理?
可以参考以下三个指标:
- 写入延迟:如果写入延迟稳定且在预期范围内(通常 < 10ms),说明 Cache 配置合理。
- Cache 使用率 :查看
cache-max-memory-size的实际使用比例,长期低于 50% 说明 Cache 偏大,经常接近 100% 说明偏小。 - Snapshot 频率 :如果 Snapshot 过于频繁(每分钟多次),说明
cache-snapshot-memory-size偏小,导致 Cache 数据不断被小批量刷盘,产生大量小 TSM 文件,增加 Compaction 压力。
一个经验值是:Snapshot 频率在 3~10 分钟一次比较合理。可以通过 influxd inspect report 查看各分片的 Cache 大小和 Snapshot 统计信息。
总结
TSM 引擎的核心就是"写入快、压缩高、查询稳"。但需要理解它背后的机制才能用好。记住:大量的写入优化参数其实是在平衡"写入速度"和"查询性能"。写太快了查起来慢,写太慢了又达不到吞吐要求。 根据业务场景找到这个平衡点,才是调优的核心。
如果你只记得一个参数,那就记住 cache-max-memory-size ------ 它决定了你的写入爆发力。内存够大,写入就能扛住突发流量。
另外补充一个容易被忽略的点:WAL 目录的磁盘性能直接决定了写入延迟的稳定性。如果 WAL 和 TSM 文件在同一个磁盘上,compaction 时的 I/O 会跟 WAL 写入抢资源。有条件的话把 WAL 放到单独的 SSD 上,或者至少确保 WAL 目录的 I/O 优先级高于 compaction。
我们在生产环境中就遇到过因为 WAL 和 TSM 共用一块磁盘,compaction 高峰期写入延迟从 5ms 飙升到 800ms 的情况。把 WAL 迁到单独的 NVMe 盘后,写入延迟稳定在了 3ms 以下。
各参数的最佳实践值也会随着 InfluxDB 版本变化。建议每次升级后重新评估一遍参数配置,不要上一版配置文件直接复用到新版本。比如 InfluxDB 2.7 引入了新的 storage-compact-full-write-cold-duration 参数替代了旧版的 compact-full-write-cold-duration,如果你从 2.6 升级到 2.7 没有更新配置,新参数就不会生效。
适用边界:本文所有参数和调优建议仅适用于 InfluxDB 1.x 和 2.x 的 TSM 引擎。InfluxDB 3.x 使用全新的 IOx 列式引擎,架构和调优方式完全不同,不可直接套用。
互动:你遇到过 InfluxDB 写入抖动或者 compaction 导致的性能问题吗?
下篇预告:INFLUX-007 Series Cardinality 高基数排查与治理实战 --- 高基数是 InfluxDB 最隐蔽的杀手,下篇教你排查和预防。
推荐阅读
- INFLUX-005 InfluxDB 高可用架构 --- 单点故障怎么避免
- INFLUX-007 Cardinality 管理 --- 高基数问题排查