fix(ceph): stop mon compacting on every paxos trim (240 GB/day of writes) #520

Merged
k.renner merged 1 commits from fix-ceph-mon-write-amplification into master 2026-08-19 12:58:25 +00:00
Member

Refs #434.

The finding

The three Ceph monitors write 240 GB/day between them — 80 GB/day each — for a database the repo documents as ~106 MB.

That is ~62% of all OS-disk writes on these nodes, and roughly a quarter of everything the cluster writes. More than every client workload combined.

Measured 2026-08-19:

paxos transactions      ~99,000/day  (1.15/s, sampled over 60 s)
paxos logical data       811 MB/day  (21.9 GB over 27 d mon uptime)
ACTUAL mon disk writes    80 GB/day  per mon, 240 GB/day for three
                                     (cgroup io.stat: 2.15 TB / 27 d)
=> ~98x write amplification

Cause

mon_compact_on_trim, upstream default true. Per the Ceph docs: "Compact a certain prefix (including paxos) when we trim its old states."

With paxos_trim_min at 250 and ~99k transactions/day, that is hundreds of forced range compactions per day, each rewriting the store.

The churn is not cluster activity

99% of paxos versions are cluster log and mgr stats:

paxos service versions share
logm (cluster log) 6,706,070 60%
mgrstat 4,414,921 39%
osdmap 9,585 0.09%
monmap / mdsmap 3 / 445 ~0

Every Rook-operator poll — mgr stat, versions, status, report, osd dump, at 23 audit entries per minute per mon — becomes a cluster-log entry, hence a paxos transaction, replicated to all three mons.

Reducing that polling is the complementary fix and is tracked separately. This change removes the amplification regardless of the rate.

This does not disable compaction

RocksDB levelled compaction still runs and still reclaims trimmed keys. What stops is the extra forced compact_range on every trim.

Two backstops if that assumption is wrong:

  1. mon_data_size_warn (15 GB default) raises HEALTH_WARN → already alerted by CephHealthWarn in monitoring-rules.
  2. mon_compact_on_start: true is set here (upstream default false), so every mon restart — upgrade, node reboot, reschedule — does a full compaction. Cheap on a ~106 MB store.

What to watch: du -sh /var/lib/rook/mon-*/store.db. If it grows past ~1 GB and stays there, revisit rather than leaving it.

Verification baseline

mon.a cgroup io.stat at 2026-08-19T12:56:29Z: wbytes=2154257645056, wios=22509683, uptime since 2026-07-23T13:20:44Z. I will re-read this after it applies and report the actual delta rather than claim an improvement.

Methodology note

cAdvisor container_fs_writes_bytes_total roughly double-counts here — it reported 425 GB/day for OSDs where nvme0n1 shows 212, and 155 GB/day for mons where cgroup io.stat shows 80. It was used only to rank writers. Every figure above comes from cgroup io.stat or kernel diskstats, which reconcile with each other and with the drives SMART counters.

🤖 Generated with Claude Code

Refs #434. ## The finding The three Ceph **monitors** write **240 GB/day** between them — 80 GB/day each — for a database the repo documents as **~106 MB**. That is ~62% of all OS-disk writes on these nodes, and roughly a quarter of everything the cluster writes. **More than every client workload combined.** Measured 2026-08-19: ``` paxos transactions ~99,000/day (1.15/s, sampled over 60 s) paxos logical data 811 MB/day (21.9 GB over 27 d mon uptime) ACTUAL mon disk writes 80 GB/day per mon, 240 GB/day for three (cgroup io.stat: 2.15 TB / 27 d) => ~98x write amplification ``` ## Cause `mon_compact_on_trim`, upstream default `true`. Per the Ceph docs: *"Compact a certain prefix (including paxos) when we trim its old states."* With `paxos_trim_min` at 250 and ~99k transactions/day, that is **hundreds of forced range compactions per day**, each rewriting the store. ## The churn is not cluster activity 99% of paxos versions are cluster log and mgr stats: | paxos service | versions | share | |---|---|---| | **logm** (cluster log) | 6,706,070 | **60%** | | **mgrstat** | 4,414,921 | **39%** | | osdmap | 9,585 | 0.09% | | monmap / mdsmap | 3 / 445 | ~0 | Every Rook-operator poll — `mgr stat`, `versions`, `status`, `report`, `osd dump`, at **23 audit entries per minute per mon** — becomes a cluster-log entry, hence a paxos transaction, replicated to all three mons. Reducing that polling is the complementary fix and is tracked separately. **This change removes the amplification regardless of the rate.** ## This does not disable compaction RocksDB levelled compaction still runs and still reclaims trimmed keys. What stops is the *extra forced* `compact_range` on every trim. Two backstops if that assumption is wrong: 1. **`mon_data_size_warn`** (15 GB default) raises `HEALTH_WARN` → already alerted by `CephHealthWarn` in monitoring-rules. 2. **`mon_compact_on_start: true`** is set here (upstream default `false`), so every mon restart — upgrade, node reboot, reschedule — does a full compaction. Cheap on a ~106 MB store. **What to watch:** `du -sh /var/lib/rook/mon-*/store.db`. If it grows past ~1 GB and stays there, revisit rather than leaving it. ## Verification baseline mon.a cgroup `io.stat` at `2026-08-19T12:56:29Z`: `wbytes=2154257645056`, `wios=22509683`, uptime since `2026-07-23T13:20:44Z`. I will re-read this after it applies and report the actual delta rather than claim an improvement. ## Methodology note cAdvisor `container_fs_writes_bytes_total` **roughly double-counts** here — it reported 425 GB/day for OSDs where `nvme0n1` shows 212, and 155 GB/day for mons where cgroup `io.stat` shows 80. It was used only to *rank* writers. Every figure above comes from cgroup `io.stat` or kernel diskstats, which reconcile with each other and with the drives SMART counters. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-08-19 12:57:12 +00:00
fix(ceph): stop mon compacting on every paxos trim (240 GB/day of writes)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
63f25c1511
The three Ceph monitors were writing 240 GB/day between them for a ~106 MB
database. That was ~62% of all OS-disk writes on the nodes and roughly a
quarter of everything the cluster writes -- more than every client workload
combined.

Measured 2026-08-19:

  paxos transactions      ~99,000/day  (1.15/s, sampled over 60 s)
  paxos logical data       811 MB/day  (21.9 GB over 27 d mon uptime)
  ACTUAL mon disk writes    80 GB/day  per mon, 240 GB/day for three
                                       (cgroup io.stat: 2.15 TB / 27 d)
  => ~98x write amplification

Cause: mon_compact_on_trim, upstream default true, "compact a certain prefix
(including paxos) when we trim its old states". With paxos_trim_min at 250 and
~99k transactions/day, that is hundreds of forced range compactions per day,
each rewriting the store.

The churn driving it is not cluster activity. 99% of paxos versions are cluster
log and mgr stats -- logm 6.7M, mgrstat 4.4M, against osdmap's 9.6k. Every Rook
operator poll (mgr stat / versions / status / report / osd dump; 23 audit
entries per minute per mon) becomes a cluster-log entry, hence a paxos
transaction, replicated to all three mons. Reducing that polling is the
complementary fix and is tracked separately; this change removes the
amplification regardless of the rate.

This does NOT disable compaction. RocksDB's own levelled compaction still runs
and still reclaims trimmed keys; what stops is the extra forced compact_range
on every trim. Two backstops if that assumption is wrong:

  - mon_data_size_warn (15 GB default) raises HEALTH_WARN, which CephHealthWarn
    in monitoring-rules already alerts on.
  - mon_compact_on_start is set to true here (upstream default false), so every
    mon restart -- upgrade, node reboot, reschedule -- does a full compaction.
    Cheap on a ~106 MB store.

Baseline for verification, mon.a cgroup io.stat at 2026-08-19T12:56:29Z:
wbytes=2154257645056, wios=22509683, uptime since 2026-07-23T13:20:44Z.

Note on methodology: cAdvisor's container_fs_writes_bytes_total roughly
double-counts here -- it reported 425 GB/day for OSDs where nvme0n1 shows 212,
and 155 GB/day for mons where cgroup io.stat shows 80. It was used only to rank
writers; every figure above comes from cgroup io.stat or kernel diskstats, which
reconcile with each other and with the drives' SMART counters.

Refs #434

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
k.renner merged commit 1d76dba474 into master 2026-08-19 12:58:25 +00:00
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit 63f25c15112ad64f268b46c9d7de968508e20adb.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

Adds two Ceph mon config overrides in kubernetes/infrastructure/rook-ceph-cluster/cephcluster.yaml: mon_compact_on_trim: "false" (disables the forced RocksDB compact_range on every paxos trim) paired with mon_compact_on_start: "true" (compensating full compaction on mon restart) as a backstop. This is a pure config-and-comment change — no code, no new resources — using a pattern (cephConfig.mon.<key>: "<value>") already established elsewhere in this same file for mon_data_avail_warn. I found no blocking issues.

🟡 Suggestions

  • mon_compact_on_trim (kubernetes/infrastructure/rook-ceph-cluster/cephcluster.yaml:46) is applied via Rook's cephConfig mechanism, which maps to ceph config set mon <key> <value> — a live/central config change. I could not confirm from the checkout alone whether the mon daemon re-reads this particular option on every trim (likely, since it's a simple boolean gate) or only picks it up at process start like mon_compact_on_start does. If it's the latter, the write-amplification fix won't actually take effect on the three already-running mons until they next restart, even though the config value is set immediately. The PR body already commits to re-checking cgroup io.stat after applying, which is the right way to catch this — just flagging that this verification step matters more than usual here, since it's the only thing that would catch a "set but not yet effective" gap. Worth confirming (or capturing the mon restart / ceph daemon mon.<id> config show | grep mon_compact_on_trim output) in the follow-up rather than assuming the metric drop alone proves it.
  • The long rationale comment (kubernetes/infrastructure/rook-ceph-cluster/cephcluster.yaml:48-84) duplicates most of the PR description verbatim into the YAML. That matches the style already used for mon_data_avail_warn and the OSD deferred-write settings just below in this same file, so it's consistent with the repo's established convention of over-documenting non-obvious infra tuning — not something to change, just noting it's intentional and in line with precedent rather than a one-off.

Tests

No automated tests apply to this change (a Flux-managed Ceph config value) — that's consistent with the rest of this file, which also carries no tests for mon_data_avail_warn or the OSD discard/deferred-write settings. Validation here is operational: the PR description states a baseline cgroup io.stat reading was captured before applying and will be re-read after, which is the appropriate check for a change like this given the "unclear if it will be caught in the current review environment" risk noted in the suggestion above.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `63f25c15112ad64f268b46c9d7de968508e20adb`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary Adds two Ceph mon config overrides in `kubernetes/infrastructure/rook-ceph-cluster/cephcluster.yaml`: `mon_compact_on_trim: "false"` (disables the forced RocksDB `compact_range` on every paxos trim) paired with `mon_compact_on_start: "true"` (compensating full compaction on mon restart) as a backstop. This is a pure config-and-comment change — no code, no new resources — using a pattern (`cephConfig.mon.<key>: "<value>"`) already established elsewhere in this same file for `mon_data_avail_warn`. I found no blocking issues. ## 🟡 Suggestions - `mon_compact_on_trim` (kubernetes/infrastructure/rook-ceph-cluster/cephcluster.yaml:46) is applied via Rook's `cephConfig` mechanism, which maps to `ceph config set mon <key> <value>` — a live/central config change. I could not confirm from the checkout alone whether the mon daemon re-reads this particular option on every trim (likely, since it's a simple boolean gate) or only picks it up at process start like `mon_compact_on_start` does. If it's the latter, the write-amplification fix won't actually take effect on the three already-running mons until they next restart, even though the config value is set immediately. The PR body already commits to re-checking cgroup `io.stat` after applying, which is the right way to catch this — just flagging that this verification step matters more than usual here, since it's the only thing that would catch a "set but not yet effective" gap. Worth confirming (or capturing the mon restart / `ceph daemon mon.<id> config show | grep mon_compact_on_trim` output) in the follow-up rather than assuming the metric drop alone proves it. - The long rationale comment (kubernetes/infrastructure/rook-ceph-cluster/cephcluster.yaml:48-84) duplicates most of the PR description verbatim into the YAML. That matches the style already used for `mon_data_avail_warn` and the OSD deferred-write settings just below in this same file, so it's consistent with the repo's established convention of over-documenting non-obvious infra tuning — not something to change, just noting it's intentional and in line with precedent rather than a one-off. ## Tests No automated tests apply to this change (a Flux-managed Ceph config value) — that's consistent with the rest of this file, which also carries no tests for `mon_data_avail_warn` or the OSD discard/deferred-write settings. Validation here is operational: the PR description states a baseline cgroup `io.stat` reading was captured before applying and will be re-read after, which is the appropriate check for a change like this given the "unclear if it will be caught in the current review environment" risk noted in the suggestion above. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=63f25c15112ad64f268b46c9d7de968508e20adb -->
Sign in to join this conversation.