InfluxDB TSM 存储引擎深度解析:写入路径与压缩机制

文章摘要 :凌晨 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 大小是否合理?

可以参考以下三个指标:

  1. 写入延迟:如果写入延迟稳定且在预期范围内(通常 < 10ms),说明 Cache 配置合理。
  2. Cache 使用率 :查看 cache-max-memory-size 的实际使用比例,长期低于 50% 说明 Cache 偏大,经常接近 100% 说明偏小。
  3. 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 管理 --- 高基数问题排查
相关推荐
gwf2162 天前
内核态RDMA驱动调试技巧:ftrace/tracepoint/crash分析 —— 深入驱动底层的排错艺术
性能调优·linux驱动·ftrace·rdma·tracepoint·内核调试·crash分析
gwf2163 天前
常见驱动问题定位与解决:设备识别、性能不达预期、CQ错误码 —— 资深RDMA驱动调试排错指南
驱动开发·linux内核·性能调优·rdma·故障排查·mlx5·irdma
deepseek235 天前
SQLite WAL 开了仍报 database is locked?Python 后台任务的事务与幂等修复
数据库·python·sqlite·并发编程·wal
gwf2166 天前
SSD I/O路径全栈优化与Linux内核存储栈性能调优实战
性能优化·linux内核·nvme·性能调优·固态硬盘·存储技术·io_uring
桦仔7 天前
自动数据库索引压缩合并
性能调优·聚集索引
做个文艺程序员8 天前
MinIO第06篇:分布式集群部署、纠删码调优与生产级性能优化
性能调优·minio·高可用·安全漏洞·分布式部署·纠删码
ly76899 天前
Redis 内存优化与性能调优:编码转换、内存碎片与慢查询的定位链路
redis·性能调优·内存优化·慢查询·编码转换
余槐i9 天前
WAL 下写并发仍是 1:SQLite 单写者模型实测与绕开方案
数据库·python·性能优化·sqlite·wal
爱琴孩10 天前
记一次Prometheus的WAL异常,导致服务器磁盘占用过高
prometheus·wal