# Ceph Pool-Full Diagnosis & Recovery — 2026-07-04

## Symptoms

After adding CT 137's 20 GB RBD to `media_ec`, **6 pools simultaneously showed 100% full** (`MAX AVAIL = 0 B`) despite the HDD class having 6.3 TB free.

## Root Cause

A single OSD (osd.10, 982 GB) reached exactly **95% utilization** = `full_ratio`. Ceph blocks ALL writes to PGs mapped to a full OSD. Since nearly every pool has PGs on osd.10, all pools became unwritable — even though osd.6 (2.8 TB) had 2.5 TB free.

### Why osd.10 Filled First

CRUSH distributes data by OSD weight. osd.10 and osd.7 are ~1 TB disks; osd.6 and osd.8 are ~2.8 TB disks. The smaller OSDs filled faster because data grew unevenly. The `VAR` column in `ceph osd df` showed osd.10 at 2.13x average utilization vs osd.6 at 0.20x.

## Diagnosis Commands

```bash
# Per-OSD utilization (look for %USE > 90% and high VAR)
ceph osd df

# Pool availability (MAX AVAIL = 0 means full)
ceph df

# Cluster health (look for "full osd" and "pool(s) full")
ceph -s

# Check full ratio threshold
ceph osd dump | grep full_ratio
# Default: full_ratio 0.95, nearfull_ratio 0.93, backfillfull_ratio 0.95
```

## Recovery Procedure

### Step 1: Raise full_ratio temporarily

```bash
ceph osd set-full-ratio 0.97
```

This unblocks writes immediately. Pools regain `MAX AVAIL > 0`. Safe as a temporary measure while rebalancing.

### Step 2: Drain overloaded OSDs

```bash
# Automatic: reweight all overloaded OSDs at once
ceph osd reweight-by-utilization
# Moves PGs away from OSDs above average utilization threshold

# Manual: target specific OSDs for aggressive draining
ceph osd reweight 10 0.5    # drain to 50% of CRUSH weight
ceph osd reweight 7 0.8     # moderate drain
```

### Step 3: Speed up backfill (optional, moderate)

```bash
# Allow 2 backfills per OSD (default: 1)
ceph config set osd osd_max_backfills 2
# Or inject directly:
ceph tell osd.<N> injectargs "--osd_max_backfills=2"

# Reduce recovery sleep (default: 0.1s)
ceph config set osd osd_recovery_sleep 0.05
```

**Tradeoffs of increasing backfill speed:**
- Higher client I/O latency (VMs feel slower)
- More "slow ops" warnings (we already had 4)
- Higher CPU/IO load on stressed OSDs
- May worsen BlueFS spillover

**Recommendation:** Only double `osd_max_backfills` (1→2) and halve `recovery_sleep` (0.1→0.05). Don't go aggressive on production clusters.

### Step 4: Verify recovery

```bash
# Watch recovery progress
ceph -s | grep -A2 "io:"
# recovery: 32 MiB/s, 8 objects/s

# Track misplaced objects
ceph -s | grep "misplaced"
# 353960/1858594 objects misplaced (19%)

# Estimate time: misplaced_objects / objects_per_second
# Or: misplaced_data_bytes / recovery_rate_bytes_per_second
```

### Step 5: Restore full_ratio after rebalance

```bash
# Once OSDs are balanced (< 80% on all), restore default
ceph osd set-full-ratio 0.95
```

## Backfill Blocking Issues

### backfill_toofull

If a target OSD is too full to accept backfill data, PGs stay in `backfill_toofull` state. Fix: lower the reweight of the full OSD further, or raise `backfillfull_ratio`:

```bash
ceph osd set-backfillfull-ratio 0.97
```

### injectargs not sticking

`ceph tell osd.N injectargs` may not reliably set all parameters. `osd_recovery_sleep` in particular gets reset to 0. Use `ceph config set osd` for persistent values, and verify with `ceph tell osd.N config get <param>`.

### CRITICAL: `ceph config set osd osd_max_backfills` does NOT apply at runtime

Setting `ceph config set osd osd_max_backfills 3` updates the MON config store, but **already-running OSD daemons keep using their old value (1)**. The change only takes effect on next OSD restart. Two ways to apply at runtime:

**Method A — `ceph tell osd.N injectargs` (runtime, no restart):**
```bash
# Apply to each HDD OSD individually
ceph tell osd.7 injectargs "--osd_max_backfills 3"
ceph tell osd.1 injectargs "--osd_max_backfills 3"
ceph tell osd.10 injectargs "--osd_max_backfills 3"
# For OSDs on other nodes, SSH there first or use ceph tell remotely

# ⚠️ Output is CONFUSING — prints all related config vars with empty values like:
# osd_max_backfills = '' osd_recovery_max_active = '' ...
# But the change DID take effect. Verify with:
ceph daemon osd.7 config get osd_max_backfills
# Should return: {"osd_max_backfills": "3"}
```

**Method B — Restart OSD daemon (pick up MON config):**
```bash
# ⚠️ MUST restart on the correct node! Use ceph osd find to locate:
ceph osd find 7
# Returns JSON with "host": "proxmox7" and IP

# Then SSH to THAT node and restart:
ssh proxmox7 "systemctl restart ceph-osd@7"
# ❌ WRONG: restarting on a node that doesn't host the OSD →
#   "OSD data directory /var/lib/ceph/osd/ceph-7 does not exist; bailing out"
```

**Verification — check runtime value on each OSD:**
```bash
ceph daemon osd.N config get osd_max_backfills
# Returns actual runtime value. If still "1", the config hasn't been applied.
```

### `osd_recovery_max_active` cannot be effectively changed

`ceph config set osd osd_recovery_max_active 3` accepts the value but OSDs report 0 (meaning auto/default). `injectargs` also shows empty. This appears to be a Ceph limitation — the parameter is managed internally. Do not waste time trying to force it.

### Also raise `nearfull_ratio` alongside `backfillfull_ratio`

When an OSD is above 93%, it triggers `nearfull` warnings which add additional pool-level flags (`nearfull` on pools). If you only raise `backfillfull_ratio` to 0.97 but leave `nearfull_ratio` at 0.93, pools still show warnings and recovery may be partially blocked. Raise both together:

```bash
ceph osd set-backfillfull-ratio 0.97
ceph osd set-nearfull-ratio 0.97
# Verify:
ceph osd dump | grep -E "nearfull_ratio|backfillfull_ratio"
```

### Recovery speed progression

After applying all tweaks, recovery speed increases progressively as OSDs pick up new config:

| Stage | Recovery Speed | Notes |
|-------|---------------|-------|
| Before intervention | 0 MiB/s (stuck) | backfillfull blocks all backfill |
| After ratio increase + reweight | 22-23 MiB/s, 5 obj/s | 1-2 PGs backfilling |
| After osd_max_backfills=3 via injectargs | 34 MiB/s, 8 obj/s | Still only 2-3 PGs simultaneous |

Even with `osd_max_backfills=3`, expect only 2-3 PGs backfilling simultaneously — Ceph schedules conservatively. The throughput improvement comes from larger transfer windows, not more parallel PGs.

### Recovery monitoring cron pattern

Set up a recurring check to track progress and get alerted when complete:

```
Schedule: every 30 minutes
Command: ssh to monitor node, run `ceph status`, report:
  - PGs remapped/backfilling count
  - % misplaced objects
  - OSD 7 utilization
  - Recovery speed
  - Alert if any OSD down or health ERR
  - Announce "COMPLETE" when 0 remapped PGs
```

## Estimating Backfill Duration

| Metric | Formula | Example |
|--------|---------|---------|
| By objects | misplaced_objects ÷ objs/sec | 353,960 ÷ 8 = ~12h |
| By bytes | misplaced_bytes ÷ bytes/sec | 418 GB ÷ 32 MiB/s = ~3.6h |
| Realistic | Take the higher estimate, factor in PG scheduling | 4-8h |

Only a few PGs backfill simultaneously (controlled by `osd_max_backfills`). 192 PGs in `backfill_wait` won't all start at once.

---

# OSD Preparation on Proxmox Nodes — 2026-07-04

## Hardware Assessment Pattern

Before adding HDD OSDs to a node, verify:

1. **Available SATA/SAS ports:**
   ```bash
   lspci | grep -i "sas\|sata\|nvme\|raid\|hba"
   ls /sys/class/ata_port/
   dmesg | grep -i "ahci\|ata[0-9]"
   ```
   ⚠️ AHCI controllers may show N ports but only implement a subset. Check dmesg for `"ports implemented (port mask 0xN)"`. An OptiPlex 3070 showed 5 SATA ports but only port 0x1 (1 port) was implemented.

2. **Solution for limited ports:** Install a PCIe HBA card (LSI 9211-8i / 9207-8i in IT mode) to connect additional SATA/SAS drives.

3. **Existing OSD inventory:**
   ```bash
   ceph osd tree | grep -A20 "host <hostname>"
   ceph-volume lvm list
   ```

## WAL/DB Space Preparation

### Identifying available flash space

```bash
# LVM VG free space on system disk
vgs pve
lvs pve

# Check if thin pool is unused (safe to remove)
pvesm list local-lvm    # if storage doesn't exist, thin pool is orphaned
lvdisplay pve/data      # check allocation %
```

### Repurposing unused thin pool for WAL/DB

If `pve/data` thin pool exists but is 0% allocated and not registered as a PVE storage, it can be removed to free NVMe space:

```bash
# Remove unused thin pool
lvremove pve/data
lvremove pve/data_tmeta
lvremove pve/data_tdata
# Or simply: lvremove pve/data (removes associated meta)

# Now VG has ~141 GB free for WAL/DB volumes
# Create DB LVs for new OSDs
lvcreate -L 30G -n osd-db-<id> pve
```

### Rule: No loopback files for DB/WAL

Per user policy: Ceph DB/WAL must use real partitions or LVs, never loopback files. Options:
- Repurpose swap partition (if RAM is adequate)
- Shrink root LV (risky)
- Remove unused thin pools
- Add a physical SSD/NVMe

## Node Checklist for Adding OSDs

1. ☐ Verify physical SATA/SAS ports available (or install HBA)
2. ☐ Verify disks are SMART-clean (`smartctl -a /dev/sdX`)
3. ☐ Prepare WAL/DB space on flash (real partition/LV, no loopback)
4. ☐ Create DB LVs: `lvcreate -L 30G -n osd-db-N pve`
5. ☐ Create OSDs: `ceph-volume lvm create --data /dev/sdX --block.db pve/osd-db-N`
6. ☐ Verify OSD joins cluster: `ceph osd tree`
7. ☐ Check rebalance: `ceph -s`
8. ☐ Set `osd_max_backfills` back to 1 after rebalance completes
