Ceph osd_memory_target: cache eviction, RSS growth, and OSD OOM kills
An OSD repeatedly killed by the kernel OOM killer, or a host where OSDs flap up and down shortly after systemd restarts them, very often traces back to one knob: osd_memory_target. The same knob set too low for the working set produces a quieter failure: read latency climbs on HDD-backed OSDs as BlueStore evicts onodes and RocksDB block-cache pages the workload actually needs.
osd_memory_target bounds the BlueStore onode cache and RocksDB block cache. The OSD uses tcmalloc and trims its caches to stay near the target, but the target is a soft limit, not a hard cap. Ceph documents that RSS may exceed the target by up to roughly 20%. Spikes from PG log bloat, RocksDB compaction, or many PGs peering at once can push RSS higher still. When target plus overshoot meets the host RAM budget or a container cgroup limit, the OOM killer fires, the OSD goes down, and from the cluster’s perspective it looks like a disk failure or heartbeat timeout.
The reverse failure is just as common and easier to miss. Set the target too low, or disable bluestore_cache_autotune and leave the fixed bluestore_cache_size small, and BlueStore trims faster than the workload can reuse cached entries. On HDD OSDs the result is a steady climb in apply latency that never trips a health check. The OSD is up, the PGs are active+clean, the cluster reports HEALTH_OK. Only the cache hit ratio and per-OSD latency expose the problem.
What this means
osd_memory_target is the cache budget BlueStore uses when bluestore_cache_autotune is enabled (the default). The default target is 4 GiB. With autotune on, BlueStore dynamically splits the budget between the onode cache (metadata served via RocksDB) and the data buffer cache. With autotune off, BlueStore falls back to fixed bluestore_cache_size values, and osd_memory_target no longer drives cache sizing. On any modern cluster, autotune is on, and osd_memory_target is the knob.
Two operator errors dominate incident reports.
The first is setting the target higher than the host can deliver. On a dense host with twelve HDDs, twelve 4 GiB targets already require 48 GiB just for cache, before PG metadata, PG logs, tcmalloc overhead, and the OS. The math gets worse in containers. If the OSD does not detect the cgroup limit correctly, it sizes its cache against host RAM, then gets killed when it crosses the much smaller container limit.
The second is leaving no headroom for spikes. Recovery, backfill, deep-scrub, and PG peering after a topology change all push memory consumption above the target. If the gap between target and the real ceiling is thin, the next recovery event OOM-kills the OSD, recovery restarts, memory spikes again, and the OSD flaps.
The headroom rule is straightforward: each OSD needs at least 1.5x its osd_memory_target available on the host, plus at least 2 GB for the OS. That ratio is not enforced by Ceph. It is the slack tcmalloc fragmentation, delayed kernel reclaim, and recovery spikes need.
flowchart TD
A[osd_memory_target] --> B{Sized for workload and ceiling?}
B -- Too low for working set --> C[Aggressive cache eviction]
C --> D[Low BlueStore cache hit ratio]
D --> E[Apply latency climbs on HDD]
B -- Too high or no headroom --> F[RSS approaches ceiling]
F --> G[Kernel OOM killer fires]
G --> H[OSD marked down]
H --> I[Peering storm on its PGs]
I --> J[Restart, spike, flap]
B -- Container misreads limit --> FCommon causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Target too low for working set | Apply latency climbing on a few OSDs, low cache hit ratio, HEALTH_OK | ceph daemon osd.X perf dump BlueStore cache hits vs misses |
| Target too high for host RAM | OOM kills in dmesg, OSDs flapping on one dense host | ps RSS per OSD, free -m, sum against host RAM |
| Container does not see real cgroup limit | OSD repeatedly OOMKilled, RSS sized to host RAM not pod limit | Pod memory limit, /sys/fs/cgroup/memory.max inside the container |
osd_memory_target_autotune on hyperconverged host | Co-located VMs or pods OOM after OSD restart | ceph config get mgr mgr/cephadm/autotune_memory_target_ratio |
| THP enabled with tcmalloc | RSS drifts above target with no workload change | /sys/kernel/mm/transparent_hugepage/enabled |
| Recovery or backfill memory spike | OSD OOM-killed during rebalance, fine in steady state | Time-correlate OOM with ceph pg dump recovering/backfilling count |
Quick checks
These are read-only. None of them modify cluster state.
# Per-OSD RSS, in MB, sorted
ps -eo pid,rss,args | grep 'ceph-osd' | grep -v grep \
| awk '{print $1, $2/1024 "MB"}' | sort -k2 -n
# Configured target on a specific OSD
ceph daemon osd.0 config show | grep -E 'osd_memory_target|bluestore_cache_autotune|bluestore_cache_size'
# BlueStore cache hit/miss counters
# <!-- TODO: verify exact onode counter names across Ceph versions -->
ceph daemon osd.0 perf dump \
| jq '.bluestore | {hit: .bluestore_cache_hit, miss: .bluestore_cache_miss,
onode_hits: .bluestore_onode_hits, onode_misses: .bluestore_onode_misses}'
# tcmalloc mempool breakdown (more accurate than RSS for what the OSD thinks it holds)
# <!-- TODO: verify dump_mempools is a valid admin socket command on target versions -->
ceph daemon osd.0 dump_mempools
# OOM kills in kernel ring buffer
dmesg -T | grep -iE 'killed process|out of memory|oom'
# OSD state and recent flapping
ceph osd tree
ceph health detail | grep -iE 'OSD_DOWN|OSD_FLAPPING'
# Host memory budget
free -m
# THP setting
cat /sys/kernel/mm/transparent_hugepage/enabled
# Cgroup memory limit visible to this OSD (v2 path; v1 uses memory/memory.limit_in_bytes)
cat /sys/fs/cgroup/memory.max 2>/dev/null || cat /sys/fs/cgroup/memory/memory.limit_in_bytes
# BlueFS slow-device stats
# <!-- TODO: verify exact admin socket command name for BlueFS stats -->
ceph daemon osd.0 bluefs stats
How to diagnose it
Confirm the OSD is actually being OOM-killed, not crashing for another reason. Check
dmesg -T | grep -iE 'killed process|out of memory'and the unit journal:journalctl -u ceph-<fsid>@osd.X.service --since '2 hours ago'. AKilled process ... ceph-osdline is conclusive. A segfault or assertion is a different problem.Compare RSS against the configured target. Read the OSD’s PID via
psand readosd_memory_targetfromceph daemon osd.X config show. RSS near or above 1.2x the target is expected in steady state. RSS climbing monotonically over hours with no workload change suggests tcmalloc fragmentation or a leak.Compare the sum of OSD targets against what the host can give. Sum the per-OSD targets on the host, add 2 GB for the OS, add headroom for any non-OSD workload (MON, MDS, RGW, VM, hypervisor), and compare against total RAM. If the sum is within 20% of total RAM, the host has no spike headroom.
In containers, verify the OSD sees the right limit.
ceph daemon osd.X config get osd_memory_targetshows the resolved value. If it equals roughly 0.8x host RAM rather than 0.8x the pod limit, cgroup detection is broken. This is a known failure mode on cgroup v2 hosts when container tooling hardcodes the v1 path.For the low-target failure mode, look at cache hit ratio and apply latency together. A sustained drop in onode hit ratio, especially combined with rising apply latency on HDD OSDs, indicates the cache budget is too small for the working set.
Time-correlate OOM events with cluster events. Recovery, backfill, and PG peering all push memory up. If OOMs cluster around recovery windows, the issue is spike headroom, not steady-state target.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
OSD RSS (ps, cgroup memory.usage) | Crosses target by 20% in normal use, more under spikes | Steady growth with no workload change, or RSS pegged at the cgroup limit |
osd_memory_target resolved value | Container detection bugs silently set this to host RAM | Resolved target close to host total RAM on a constrained container |
| BlueStore onode hit ratio | Onode misses require RocksDB lookups, expensive on HDD | Hit ratio falling while object access pattern is unchanged |
OSD apply latency (ceph_osd_apply_latency_ms) | The user-visible symptom of cache thrashing | One OSD at 5x cluster median for the same device class |
OSD commit latency (ceph_osd_commit_latency_ms) | WAL/DB device or replication slowness, often co-occurs | Spike without correlated apply-latency spike points at the DB device |
| OOM kills (host kernel logs, container OOMKilled counter) | The hard failure | Any kill of a ceph-osd process |
OSD up/down transitions (ceph_osd_up gauge edges) | Each OOM restart is a flap | Repeated transitions on the same OSD minutes apart |
Recovery rate (ceph_pool_recovering_bytes_per_sec) | Memory spikes during recovery | OOM correlates with elevated recovery rate |
| Swap usage on host | Kernel swaps OSD heap instead of evicting page cache | Any sustained swap usage on an OSD host |
| THP enabled flag | Interacts with tcmalloc to inflate RSS | [always] rather than [never] or madvise |
Fixes
Target set higher than the host can deliver
Lower the target to fit the budget, or move OSDs off the host. Compute the safe target as (host_RAM - 2GB - non_OSD_workload) / OSD_count / 1.5. Apply per-OSD:
ceph config set osd.X osd_memory_target 4294967296
This is a live config change; the OSD will trim back down without a restart, but it must be running and healthy to honor the new value. If the host is fundamentally oversubscribed, no target tuning fixes it. Add RAM or reduce OSD density.
Target set too low for the working set
Raise the target, after confirming the host has the headroom. Pools with many small objects (CephFS metadata, RGW bucket indexes) need proportionally more onode cache. Track onode hit ratio after the change. If raising the target does not move the hit ratio, the working set may be so random that no practical cache helps, and faster media (NVMe for DB) is the actual fix.
Container cgroup limit not reflected in the target
In Rook, set explicit memory requests and limits in the CephCluster spec. Without a request, each OSD can see the entire node and resolve osd_memory_target to a large fraction of host RAM. In cephadm, the container path uses POD_MEMORY_REQUEST and POD_MEMORY_LIMIT. If only the limit is set, the target resolves to limit * osd_memory_target_cgroup_limit_ratio (default 0.8). Make sure both are set and that the limit is what you actually want the OSD to fit in. On cgroup v2 hosts where the OSD misreads the limit, pin to a known-good Ceph image or set the target explicitly per OSD.
Autotune fighting hyperconverged workloads
osd_memory_target_autotune is enabled by default in cephadm since Quincy. It sizes OSD targets against mgr/cephadm/autotune_memory_target_ratio, default 0.7 of host RAM. On hyperconverged hosts (Proxmox, Rook co-located with workloads, OSDs alongside MON or MDS on the same box), that ratio assumes all host RAM belongs to OSDs and will over-allocate. Either disable per-OSD autotune with ceph config set osd.X osd_memory_target_autotune false and set the target explicitly, or lower the global ratio to reflect what the OSDs actually get.
THP and tcmalloc RSS growth
Transparent Huge Pages interact with tcmalloc to inflate RSS above what the OSD thinks it is holding. The practical fix is the same one ceph-ansible and cephadm apply by default: disable THP.
# Requires root; ephemeral, will not survive reboot
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# Persist via tuned, udev rule, or systemd unit depending on your host setup
vm.swappiness
With default swappiness of 60, the kernel may swap OSD heap pages instead of evicting page cache, producing latency spikes when those pages are faulted back in. Setting vm.swappiness=0 or 1 is operator consensus for Ceph hosts. This is host-level tuning, not a Ceph config.
Prevention
Capacity-plan OSD memory the same way you plan raw storage. For each host, write down: number of OSDs, target per OSD, sum of targets, 1.5x headroom multiplier, OS reserve, and any co-located workload. If the result is more than 90% of host RAM, the host is oversubscribed.
For containerized deployments, treat the pod memory limit as the ceiling. Set explicit requests and limits in the spec, verify the OSD resolves the target against the limit, and alert on the resolved value drifting toward host RAM.
Track OOM kills of ceph-osd as a first-class signal. A single OOM is a warning. Repeated OOMs on the same OSD, or OOMs across multiple OSDs on the same host, are an incident even when the cluster reports HEALTH_OK between kills.
Track cache hit ratio alongside apply latency. A cluster with HEALTH_OK, all PGs active+clean, and a steadily falling onode hit ratio is drifting toward a latency incident.
How Netdata helps
- Per-second OSD RSS collection (via the ceph collector and cgroup memory metrics) shows the steady-state RSS vs target ratio and the spike pattern around recovery events that point-in-time
pssnapshots miss. - BlueStore cache hit/miss counters and per-OSD apply and commit latency let you correlate cache eviction with the latency consequence. A falling hit ratio with rising apply latency is the signature of a too-low target.
- ML anomaly detection on RSS highlights monotonic growth (tcmalloc fragmentation, leaks) before it crosses the cgroup limit and becomes an OOM.
- Cgroup memory usage on the same host as the OSD lets you compare the resolved target against the actual container ceiling, surfacing the misconfigured-container failure before the first OOM.
- OOM kill events from the kernel logs and OSD up/down transitions from the Ceph collector appear on the same timeline as recovery rate, so spike-driven OOMs are distinguishable from steady-state oversubscription at a glance.
Related guides
- Ceph backfill_toofull: recovery blocked because target OSDs are full
- Ceph blocked ops: client I/O stuck behind a single slow OSD
- Ceph capacity death spiral: an OSD fails and recovery has nowhere to go
- Ceph client latency vs OSD latency: fast disks, slow clients
- Ceph health detail: mapping ceph_health_detail checks to a cause
- Ceph HEALTH_ERR: reading the umbrella status and finding the real fault
- Ceph HEALTH_WARN: which warnings are noise and which are structural
- How Ceph actually works in production: a mental model for operators
- Ceph MON_CLOCK_SKEW: clock drift between monitors and election churn
- Ceph MON_DOWN: a monitor out of quorum and reduced redundancy
- Ceph monitor election storm: monitors that cannot hold a stable quorum
- Ceph monitor quorum lost: the cluster can no longer update its maps






