feat(persistence): durable Redis-backed room persistence (#44) #69

Merged
Reliodas merged 3 commits from feat/44-durable-persistence into develop 2026-06-25 21:29:43 +00:00
Owner

Closes #44

Persistance durable optionnelle (Redis) : les parties survivent à un redémarrage contrôlé du serveur, sans déplacer l'autorité de jeu hors du serveur.

Décision

Backend Redis (choisi), désactivé par défaut : sans REDIS_URL, le serveur reste 100 % en mémoire → tests, CI et smoke inchangés.

Fonctionnement (server/src/modules/room/)

  • Sérialisation (room.serialize.ts) : salle → JSON ; exclut timerInterval, socket ids, minuteries de grâce. Donnée invalide → null (jamais de crash).
  • Stockage (persistence.ts) : clé lgc:room:<code> (avec EX = TTL) + index SET lgc:rooms (énumération sans SCAN). Interface abstraite → testable sans Redis réel.
  • Sauvegarde (room.store.ts) : snapshot périodique (PERSIST_INTERVAL_MS, 3 s) des salles connectées ; flush de toutes les salles au SIGTERM/SIGINT ; suppression immédiate à closeRoom.
  • Restauration : restoreRooms() au démarrage avant d'accepter les connexions ; clients rebranchés via room:resume.
  • Expiration : salles orphelines non rafraîchies → clé expirée après ROOM_TTL_SECONDS (6 h) → pas d'accumulation.
  • Timers à la restauration (sûr, aucune reprise auto) : round 1 timertimer-end ; catégorie active → résultat finalisé ; codebreaker verrouillé.
  • Infra : docker-compose.yml ajoute un service redis (+ volume append-only) et REDIS_URL.

Conformité aux critères

survie au redémarrage contrôlé ✓ · tokens/rôles/scores/manche/statut/config restaurés ✓ · timers reconstruits de façon sûre (gelés/expirés) ✓ · expiration TTL ✓ · tests sauvegarde/restauration/expiration/données invalides ✓ · docs/Implémentation.md documente le stockage et ses limites ✓

Vérification

  • typecheck OK ; node:test 244 pass (1 skipped, +12 tests #44), Jest 13/13, Vitest 83/83 ; coverage exit 0
  • Impl Redis validée contre un vrai Redis (save → loadAll → delete) : REDIS OK
  • npm run smoke vert sans REDIS_URL (persistance désactivée proprement), build OK, check:assets OK, validate:questions OK (360), e2e Playwright 3/3

Limites : redémarrage contrôlé mono-instance ; un crash brutal peut perdre jusqu'à PERSIST_INTERVAL_MS. Le partage multi-instance relève de #45.

Closes #44 Persistance durable **optionnelle (Redis)** : les parties survivent à un redémarrage contrôlé du serveur, sans déplacer l'autorité de jeu hors du serveur. ## Décision Backend **Redis** (choisi), **désactivé par défaut** : sans `REDIS_URL`, le serveur reste 100 % en mémoire → tests, CI et smoke inchangés. ## Fonctionnement (`server/src/modules/room/`) - **Sérialisation** (`room.serialize.ts`) : salle → JSON ; exclut `timerInterval`, socket ids, minuteries de grâce. Donnée invalide → `null` (jamais de crash). - **Stockage** (`persistence.ts`) : clé `lgc:room:<code>` (avec `EX` = TTL) + index SET `lgc:rooms` (énumération sans `SCAN`). Interface abstraite → testable sans Redis réel. - **Sauvegarde** (`room.store.ts`) : snapshot périodique (`PERSIST_INTERVAL_MS`, 3 s) des salles **connectées** ; flush de **toutes** les salles au `SIGTERM`/`SIGINT` ; suppression immédiate à `closeRoom`. - **Restauration** : `restoreRooms()` au démarrage avant d'accepter les connexions ; clients rebranchés via `room:resume`. - **Expiration** : salles orphelines non rafraîchies → clé expirée après `ROOM_TTL_SECONDS` (6 h) → pas d'accumulation. - **Timers à la restauration** (sûr, aucune reprise auto) : round 1 `timer`→`timer-end` ; catégorie active → résultat finalisé ; codebreaker verrouillé. - **Infra** : `docker-compose.yml` ajoute un service `redis` (+ volume append-only) et `REDIS_URL`. ## Conformité aux critères survie au redémarrage contrôlé ✓ · tokens/rôles/scores/manche/statut/config restaurés ✓ · timers reconstruits de façon sûre (gelés/expirés) ✓ · expiration TTL ✓ · tests sauvegarde/restauration/expiration/données invalides ✓ · `docs/Implémentation.md` documente le stockage et ses limites ✓ ## Vérification - typecheck OK ; `node:test` 244 pass (1 skipped, +12 tests #44), Jest 13/13, Vitest 83/83 ; coverage exit 0 - **Impl Redis validée contre un vrai Redis** (save → loadAll → delete) : REDIS OK - `npm run smoke` vert **sans** `REDIS_URL` (persistance désactivée proprement), build OK, check:assets OK, validate:questions OK (360), e2e Playwright 3/3 > Limites : redémarrage **contrôlé** mono-instance ; un crash brutal peut perdre jusqu'à `PERSIST_INTERVAL_MS`. Le partage multi-instance relève de #45.
Add optional Redis persistence so active rooms, participants, reconnect tokens,
roles, scores, round/status and config survive a controlled restart. Rooms are
serialized to JSON (timers and socket ids excluded), snapshotted on an interval
for connected rooms and flushed on shutdown, then restored before accepting
connections; orphaned rooms expire via a Redis TTL. Restore applies a safe timer
policy (no countdown auto-resumes). Disabled (in-memory) when REDIS_URL is unset,
so tests, CI and the smoke run unchanged. docker-compose ships a redis service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(impl): document the durable Redis persistence (#44)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
a3d57c9da5
Describe the optional Redis storage, serialization, snapshot/flush/restore flow,
TTL expiry, the restore-time timer policy and the single-instance limits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(persistence): prune expired Redis room index entries (#44)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
92562d7579
Reliodas merged commit 1004f64cac into develop 2026-06-25 21:29:43 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: jeux/le-grand-concours#69
No description provided.