Skip to content

Incident 2026-03-30 — Saturation I/O Uptime Kuma

Date : 2026-03-30 ~14:00
Durée : ~5–10 minutes
Services impactés : LXC 101 (Uptime Kuma, Homepage, Portainer, Zensical)
Déclencheur : Suppression de l'historique des events dans l'interface Uptime Kuma


1. CHRONOLOGIE

Heure Événement
~13:55 Suppression "clear history" depuis UI Uptime Kuma
~13:55–13:58 Load monte à 300%+, iowait très élevé, IOR/s uptime-kuma en MB/s
~13:58 LXC 101 freeze – impossible à shutdown depuis Portainer
~13:58 pkill uptime-kuma sur host PVE → process redémarre automatiquement
13:58:11 vzreboot:101 Proxmox (TASK OK)
13:59 LXC 101 restart, containers up
13:59–14:00 Uptime Kuma error log : DB pool aborted (3x "Error: aborted" + "Unable to acquire a connection")

2. ANALYSE TECHNIQUE

2.1 Mécanisme réel — SQLite write lock contention (bug connu Uptime Kuma)

sdc est en parfait état (SMART sain, 210 GB libres sur 228 GB, 0 erreur ATA). La durée et l'intensité de l'incident ne peuvent pas s'expliquer par une lenteur du disque : une DB de 300 MB sur un SSD 850 EVO serait traitée en ~1 seconde en séquentiel, ~10 secondes au pire en random 4K.

La cause réelle est un bug applicatif connu et documenté dans Uptime Kuma :

GitHub issue #3248 (juin 2023) : "Clearing the Events on a http(s) monitor hangs Uptime Kuma for over a minute. Load average jumps to 4.5" — Raspberry Pi 4, symptômes identiques.
GitHub issues #4041, #2751, #4709, #5667 : variantes du même problème — DB croissante → SQLITE_BUSY, KnexTimeoutError, instance inaccessible.
Statut : non résolu dans la branche v1 / v2 à ce jour.

Uptime Kuma utilise better-sqlite3 via knex avec un pool de connexions SQLite = 1 (SQLite n'autorise qu'un seul writer simultané). Lors du "Clear History" :

  1. DELETE FROM heartbeat WHERE ... — 37 jours × ~40 moniteurs × 1440 min = ~2,1 millions de lignes à supprimer (~280 MB)
  2. Le DELETE tient le write lock SQLite pendant toute sa durée
  3. Pendant ce maintien du lock, tous les beats (40+ monitors toutes les 60s) font une queue sur ce lock unique → SQLITE_BUSY + retry en boucle
  4. Le pool de connexions knex s'épuise → "Unable to acquire a connection" (visible dans error.log)
  5. Les retry loops de tous les monitors génèrent une charge CPU explosive (300%+ dans top)
  6. L'iowait élevé vient du lock contention SQLite (threads bloqués sur le lock), pas de la lenteur physique du disque

Pourquoi la majorité des users ne crashe pas complètement :

Facteur Setup classique Ce setup
Isolation CPU Uptime Kuma seul ou VM dédiée LXC partagé, pas de CPU limit
Services affectés Uptime Kuma seul ralentit Homepage, Portainer, Zensical en CPU starvation
DB size Rétention configurée (7–30j) Pas de config rétention → 37 jours × 40 monitors
Hardware SSD NVMe ou disque sain sdb avec secteurs défaillants (allonge les I/O waits)

Un Raspberry Pi (issue #3248) voit 1–2 minutes de hang puis recovery. Ici : le CPU starvation s'étend à tout le LXC, rendant Portainer inaccessible pour l'intervention.

Estimation DB avant delete : - ~2,1M rows × 150 bytes ≈ 305 MB → visible dans les IOR/s élevés - Après delete + checkpoint : 26 MB (kuma.db actuel) - WAL résiduel : 1,3 MB (checkpoint interrompu par pkill)

2.2 Pourquoi le système gèle (LXC inaccessible)

  • Tous les containers Docker de LXC 101 tournent dans le même LXC sans CPU limits
  • Uptime Kuma monopolise les CPU cores du LXC → Homepage, Portainer, Zensical en starvation
  • Le LXC lui-même devient inaccessible depuis Portainer (API calls timeout)
  • pkill uptime-kuma libère immédiatement les CPU → système récupère en quelques secondes

2.3 Preuves

# sdc : parfaitement sain, non impliqué dans la défaillance
Reallocated_Sector_Ct : 0
Uncorrectable_Error_Cnt : 0
ATA Error Count : 0
Espace libre : 210 GB / 228 GB (4% utilisé)
Total LBAs Written : 31 TB sur lifetime → dans les specs 850 EVO (150 TBW)

# sdb : erreurs hardware préexistantes (NON liées à cet incident)
Reallocated_Sector_Ct : 3
Uncorrectable_Error_Cnt : 3
ATA Error Count : 3 (dont 1 UNC à LBA 0x00402000 = ~2.0 GB)

# dm-10 (LXC 101) filesystem state
FS Error count : 9  ← INCHANGÉ depuis le 23/03 (pré-existant, aucune nouvelle erreur aujourd'hui)
Last error time : Mon Mar 23 18:55:08 2026

# kuma error.log (post-pkill shutdown)
3× "Error: aborted"         ← pool SQLite interrompu pendant le shutdown
1× "Unable to acquire a connection"  ← pool épuisé (conséquence du thundering herd)

2.4 État pool LVM au moment de l'incident

pool data: 48.42%  ← sain (était 92.6% le 23/03, remediation effectuée)
vm-101-disk-0: 50.78%
vm-104-disk-0: 87.30%  ← ⚠️ toujours élevé

3. RELATION AVEC L'INCIDENT DU 23/03

Critère Incident 23/03 Incident 30/03
Disque impliqué sdb (pool thin LVM) — défaillance physique réelle sdc — sain, non défaillant
Cause racine Pool thin 92.6% + backup COW + secteur UNC sdb Bug applicatif : Node.js event loop block + SQLite thundering herd
Mécanisme EIO hardware → ext4 s'arrête Lock contention SQLite → CPU starvation LXC
Erreurs kernel 9+3+1 nouvelles erreurs EIO ext4 Zéro — compteurs inchangés depuis le 23/03
Erreurs disque OUI (secteurs défaillants sdb) NON (sdc parfaitement sain)
SMART sdb UNC à LBA 4,202,496 → cause des EIO Inchangé, toujours critique

Verdict : DIFFÉRENT du 23/03 — confirme partiellement, précise et corrige.

  • ✅ Confirme : une opération intensive peut rendre le LXC inaccessible
  • ✅ Confirme : le NVMe est nécessaire pour sdb (raison inchangée — secteurs défaillants réels)
  • 🔎 Précise : le 30/03 est une défaillance applicative Uptime Kuma, pas un problème disque
  • 🔎 Précise : même pattern que l'incident Gramps (pic I/O inattendu d'une opération app-level)
  • ✏️ Corrige : sdc n'est pas insuffisant ni un SPOF — c'est better-sqlite3 synchrone qui bloque
  • ❌ N'invalide pas : les secteurs défaillants sdb restent critiques et non résolus

4. PROBLÈMES PERSISTANTS NON RÉSOLUS

4.1 sdb — Secteurs défaillants (CRITIQUE)

Samsung SSD 850 EVO 250GB (sdb) — 32 486 h d'utilisation
Reallocated_Sector_Ct : 3 (secteurs remappés)
Uncorrectable_Error_Cnt : 3
ATA Error Count : 3 (dont UNC à LBA 4 202 496 = ~2.0 GB depuis début disque)
Warning: SMART ATA Error Log Structure error: invalid SMART checksum  ← firmware/HW dégradé

Conséquence : LXC 100, 101, 103 ont des filesystems avec erreurs ext4 non corrigées.

dm-10 (LXC 101) : 9 erreurs, state "clean with errors", orphan_present
  → First error: 2026-03-20 20:42:40 — ext4_do_writepages (EIO)
  → Last error:  2026-03-23 18:55:08 — ext4_journal_check_start (EIO)
dm-6  (LXC 100) : 3 erreurs (journal_check_start, 23/03)
dm-8  (LXC 103) : 1 erreur  (journal_check_start, 23/03)

e2fsck OBLIGATOIRE sur ces 3 volumes avant le remplacement de sdb.

4.2 vm-104-disk-0 à 87.3% (ATTENTION)

LXC 104 occupait déjà 87.28% lors du rapport 23/03. Aucune amélioration.
À surveiller avant la migration NVMe.

4.3 kuma.db-wal non checkpointé

kuma.db-wal = 1.3 MB après l'incident (checkpoint interrompu par le kill).
SQLite le retraitera au prochain démarrage propre — aucune action requise, mais noter que la DB peut être légèrement incohérente si Uptime Kuma a crashé au mauvais moment.


5. ACTIONS REQUISES

🔴 Immédiat (avant installation NVMe)

  1. e2fsck sur LXC 100, 101, 103 — à faire conteneur STOPPÉ :

    pct stop 100 && e2fsck -f /dev/mapper/pve-vm--100--disk--0 && pct start 100
    pct stop 101 && e2fsck -f /dev/mapper/pve-vm--101--disk--0 && pct start 101
    pct stop 103 && e2fsck -f /dev/mapper/pve-vm--103--disk--0 && pct start 103
    

  2. Uptime Kuma — mitigation applicative : ne pas utiliser "Clear All History" en production. Si nécessaire, préférer la rétention configurable (ex: 30 jours) plutôt qu'un delete massif ponctuel.

🟠 Court terme (avec installation NVMe)

  1. Migrer sdb → NVMe : copie LVM complète + remount. Les 3 secteurs défaillants et l'UNC error disparaissent. Cible principale du remplacement hardware.

  2. CPU limits LXC 101 : configurer un cgroup CPU limit pour que uptime-kuma ne puisse pas saturer tous les cores du LXC et bloquer les autres containers.

🟡 Long terme

  1. Rétention Uptime Kuma : configurer une rétention automatique des heartbeats (30–90 jours) pour que la DB reste petite et que les deletes futurs soient négligeables.

  2. Isolation des services : envisager de sortir Uptime Kuma de LXC 101 dans son propre LXC si les incidents applicatifs affectent Homepage/Portainer.


6. SCRIPT DE DIAGNOSTIC DE RÉFÉRENCE

# État santé disques
smartctl -a /dev/sdb | grep -E "Reallocated|Uncorrectable|CRC|ATA Error|overall"
smartctl -a /dev/sdc | grep -E "Reallocated|Uncorrectable|CRC|ATA Error|overall"

# Erreurs ext4 filesystems LXC
for dev in dm-6 dm-8 dm-10; do
  echo "=== $dev ===" && tune2fs -l /dev/$dev | grep -E "FS Error|error time|error func|state"
done

# Pool LVM
lvs --options lv_name,data_percent,lv_size pve

# Erreurs kernel récentes
dmesg -T | grep -E "EXT4|I/O err|UNC|blk_update" -i | tail -20