Incident: W4 API Pending Forever (Redis auth hang)

PoleWartość
IDINCIDENT-2026-07-01-001
PlatformaW4 (w4.pinbox24.com, bms-1)
SevernośćP0 (produkcja down — wszystkie authenticated API calls wisely)
StatusRESOLVED (hotfix in-container)
Wykryto2026-07-01T~08:00Z
Rozwiązano2026-07-01T~11:00Z
GH Issueradieu/p24-infra#2415

Symptom

Wszystkie requesty w dashboardzie W4 wisiały w stanie “Pending” bez odpowiedzi. Dotyczyło:

  • GET /api/offices/ — spinner bez końca
  • GET /api/profile/ — spinner bez końca
  • POST /api/i18n/langs — 500 z “HMGET can’t be processed”
  • GET /api/offices/work-space/:id — 500

Użytkownicy nie mogli zalogować się ani załadować żadnych danych.

Przyczyna

Incydent #2350 (rotacja credentiali) spowodował zmianę REDIS_PASSWORD w backend-environment.env na bms-1. Jednak OVH Redis DBaaS (kr40258-001.dbaas.ovh.net:35689) ma trzecie, nieznane hasło — ani stare (10-char), ani nowe (32-char) z rotacji nie działają → WRONGPASS.

Redis v2.x ma domyślnie enable_offline_queue: true. Gdy klient nie jest w stanie ready (WRONGPASS → brak auth), wszystkie komendy kolejkują się w nieskończoność. Żadna Promise nigdy się nie resolve/reject.

authGuardMiddleWare wywołuje redisGetData(token._id) dla każdego authenticated request:

// authGuard.middleware.js
const redisUser = yield redis_helper_1.redisGetData(token._id);
// ↑ ta Promise nigdy nie resolveuje gdy Redis offline queue = true
next(); // nigdy nie osiągane

Oś czasu

Czas (UTC)Zdarzenie
~08:00Wykryto: dashboard W4 — wszystkie requesty Pending
~09:00Analiza: redis.config.js, authGuard.middleware.js
~09:30Root cause: enable_offline_queue: true + WRONGPASS
~10:00Patch 1: redis.config.jsenable_offline_queue: false
~10:30Patch 2: redis.helper.jssafeRedis() na redisGetData
~11:00Patch 3: redis.helper.jssafeRedis() na WSZYSTKICH funkcjach
~11:15Weryfikacja OK — dashboard ładuje się, offices/profile 200

Zmienione pliki / kontenery

Kontener/PlikZmianaRollback
v42-prod:/app/dist/config/redis.config.jsDodano { enable_offline_queue: false, retry_unfulfilled_commands: false } do createClientdocker exec v42-prod cp /tmp/redis.config.js.bak /app/dist/config/redis.config.js
v42-prod:/app/dist/globalHelpers/redisHelpers/redis.helper.jsKompletny rewrite — wszystkie funkcje używają safeRedis() z fallback null/[] i timeoutem 1500msBackup w /tmp/redis.helper.js.bak wewnątrz kontenera

Patch marker

Plik redis.helper.js zawiera marker REDIS_HOTFIX_ALL_SAFE na początku i REDIS_HOTFIX_TIMEOUT przy funkcji safeRedis.

Script re-apply

Skrypt do ponownego nałożenia patcha po docker recreate:

  • docs/pinbox24/patch_redis_config.py — redis.config.js
  • docs/pinbox24/patch_redis_helper_full.py — redis.helper.js (full rewrite)

Root cause (szczegółowo)

// Przed patchem redis.config.js:
exports.redisClient = redis_1.default.createClient(
  process.env.REDIS_PORT, process.env.REDIS_HOST
);
// enable_offline_queue: true (domyślnie) — komendy kolejkują się gdy Redis nie ready
 
// Po patchem:
exports.redisClient = redis_1.default.createClient(
  process.env.REDIS_PORT, process.env.REDIS_HOST,
  { enable_offline_queue: false, retry_unfulfilled_commands: false }
);
// Komendy failują natychmiastowo z AbortError: NR_CLOSED
// safeRedis() — klucz do naprawy:
function safeRedis(fn, fallback) {
    return new Promise((resolve) => {
        const t = setTimeout(() => resolve(fallback), 1500); // fallback po 1.5s
        try {
            fn((error, data) => {
                clearTimeout(t);
                if (error) { resolve(fallback); } // błąd → fallback, nie reject
                else { resolve(data); }
            });
        } catch(e) {
            clearTimeout(t);
            resolve(fallback); // wyjątek → fallback
        }
    });
}

Weryfikacja fix

# Na bms-1:
curl -s -o /dev/null -w "%{http_code}" "https://api.w4.pinbox24.com/api/profile/" \
  -H "Authorization: Bearer <token>"
# → 200 (wcześniej timeout)
 
curl -s -o /dev/null -w "%{http_code}" "https://api.w4.pinbox24.com/api/offices/" \
  -H "Authorization: Bearer <token>"
# → 200

Dashboard załadował się, workspace/i18n 200, uploads działają.

Długoterminowy fix

  1. OVH Manager: Zresetować hasło Redis kr40258-001.dbaas.ovh.net:35689 i zaktualizować:
    • secrets/n8n-bms4.env.sops → klucz REDIS_PASSWORD
    • backend-environment.env na bms-1
    • Recreate v42-prod z nowym hasłem
  2. Monitoring: Alert gdy Redis kr40258-001 nie jest osiągalne z bms-1 przez >30s
  3. Przed każdym docker recreate v42-prod: Re-apply oba patche (redis.config.js + redis.helper.js)
  4. Issue: radieu/p24-infra#2415

Lekcje

  1. Redis v2.x enable_offline_queue: true to pułapka — nigdy nie używać bez safeRedis() wrappera
  2. Rotacja credentiali powinna obejmować weryfikację połączenia przed deployem
  3. OVH Redis DBaaS ma własne hasło niezależne od rotacji — należy je przechowywać w secrets/n8n-bms4.env.sops osobno od n8n Redis
  4. authGuardMiddleWare → Redis na każdym requeście = single point of failure dla całego API