From 6ebc93db2eec96479b277e8c960f506ee454f470 Mon Sep 17 00:00:00 2001 From: Julien Lutran Date: Wed, 26 Aug 2026 16:37:05 +0200 Subject: [PATCH] seafile-gc: diagnose never-run GC (24.9M inodes) and the reclamation procedure GC never ran in the server's life: 13 ghost libraries, unlimited history on 16/17 repos, 867G blocks vs 710G live data, and an fs/commits object swarm that makes per-file backup walks take days. Procedure: pre-GC zfs snapshot, keep_days=90 default, offline seaf-gc (+ --rm-deleted, --rm-fs), measure, quarterly GC schedule, then re-decide the S3 backup mechanism and the 13.x upgrade. Co-Authored-By: Claude Fable 5 --- ks4/seafile-gc.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 ks4/seafile-gc.md diff --git a/ks4/seafile-gc.md b/ks4/seafile-gc.md new file mode 100644 index 0000000..c63183a --- /dev/null +++ b/ks4/seafile-gc.md @@ -0,0 +1,102 @@ +# seafile: garbage collection + history retention + +Status: **diagnosed 2026-08-26, GC not yet run.** All crontab backup +jobs on ks4 are commented out until this completes. + +## Root cause of the 24.9 M-inode / 933 G dataset + +Investigated after per-file backups of `/opt/seafile` proved +unwalkable (restic seed: ~20 M objects remaining at ~66 files/s). +Findings (seafile-server **12.0.14**, binary install, upgraded from +8.0 → 9.0 → 12.0): + +- **GC has never run** — no `gc.log` ever, no cron in the container. + `seaf-gc` is the *only* mechanism that deletes blocks/fs/commits + objects; without it nothing is ever reclaimed. +- **13 ghost libraries**: `storage/commits/` holds 30 library dirs; + the DB has 17 live repos and an empty trash. Deleted libraries' + object stores persist until `seaf-gc --rm-deleted`. +- **Unlimited history on 16 of 17 repos**: only one library (the + 240 G one) has a retention limit (30 days, `RepoHistoryLimit`). + Everything else keeps every version of every file since creation. +- Sizes: live repo data ≈ 710 G (DB `RepoSize`) vs **867 G** in + `blocks/` → ≥150 G dead blocks; the rest of the dataset is the + fs/commits object swarm (~5 M commits objects counted; fs likely + 15 M+, sub-4K each) — the thing that makes every per-file walk + take days. + +References: + +## Procedure + +### 0. Safety net (host, instant) + +```sh +zfs snapshot data/containers/seafile@pre-gc # rollbackable; delete after validation +``` + +Leg-1 replica on sdb is the second net (crontab currently disabled — +the replica is pre-GC state, which is exactly what we want here). + +### 1. Default history retention (inside the container) + +`/opt/seafile/conf/seafile.conf`: + +```ini +[history] +keep_days = 90 +``` + +Applies as the default for all libraries without a per-library +setting (the existing per-library 30 d row stays). Users can still +override per library in the web UI if +`ENABLE_REPO_HISTORY_SETTING = True` in `seahub_settings.py`. + +### 2. GC — offline, maintenance window + +CE's GC requires seafile stopped. On this pathological store expect +**hours** (it traverses fs/commits on a cold HDD); the dry-run sizes +the window. Inside the container: + +```sh +cd /opt/seafile/seafile-server-latest +./seahub.sh stop && ./seafile.sh stop # or the container's systemd units — check systemctl + +./seaf-gc.sh --dry-run # per-repo report of what would be reclaimed +./seaf-gc.sh # blocks GC, honors keep_days +./seaf-gc.sh --rm-deleted # purge the 13 ghost libraries +./seaf-gc.sh --rm-fs # unreferenced fs objects — the inode bulk + # (verify flag names: ./seaf-gc.sh --help; manual 12.0) + +./seafile.sh start && ./seahub.sh start +``` + +### 3. Measure the result (host) + +```sh +df -i /var/lib/incus/storage-pools/data/containers/seafile # was 24,885,504 inodes +du -sh .../seafile/rootfs/opt/seafile/seafile-data/storage/blocks # was 867 G +``` + +### 4. Aftermath + +- validate seafile works (client sync, web, download a versioned + file), then `zfs destroy data/containers/seafile@pre-gc` +- re-enable the crontab (incus-copy leg first; its next refresh + transfers the shrunken dataset) +- **schedule GC** so this never regrows: quarterly maintenance window + (offline GC is the CE price); host-side cron via + `incus exec seafile -- ...` or a documented manual ritual +- re-decide seafile's S3 backup with real post-GC numbers: + per-file restic redux if the object count collapses far enough, + `zfs send | restic --stdin-from-command` otherwise + (see [restic-backup.md](restic-backup.md)) +- then plan the 13.x upgrade (Docker-only — see the incus-compose + notes and + ) + against a much smaller estate + +## Related cleanup while in there + +- `logs/seafevents.log.2024-*` and friends — years of rotated logs +- the old `seafile-server-8.0.7` / `9.0.10` dirs once 12.x is proven