Files
Froidefond Julien 2d266f89f9 feat(perf): implement performance optimizations for session handling
- Introduced a new configuration file `config.yaml` for specifying project context and artifact rules.
- Added `.openspec.yaml` files for tracking changes related to performance improvements.
- Created design documents outlining the context, goals, decisions, and migration plans for optimizing session performance.
- Proposed changes include batching database queries, debouncing event refreshes, purging old events, and implementing loading states for better user experience.
- Added tasks and specifications to ensure proper implementation and validation of the new features.

These enhancements aim to improve the scalability and responsiveness of the application during collaborative sessions.
2026-03-10 08:06:47 +01:00

3.7 KiB

Context

L'application charge les collaborateurs de session via resolveCollaborator appelé en séquence dans une boucle (N+1). Le hook useLive déclenche router.refresh() sur chaque événement SSE reçu, sans groupement, causant des re-renders en cascade si plusieurs événements arrivent simultanément. La fonction cleanupOldEvents existe dans session-share-events.ts mais n'est jamais appelée, laissant les événements s'accumuler indéfiniment. L'absence de loading.tsx sur les routes principales empêche le streaming App Router de s'activer. Les modals (ShareModal) sont inclus dans le bundle initial alors qu'ils sont rarement utilisés.

Goals / Non-Goals

Goals:

  • Éliminer le N+1 sur resolveCollaborator avec un fetch batché
  • Grouper les refreshes SSE consécutifs avec un debounce
  • Purger les événements SSE au fil de l'eau (après chaque createEvent)
  • Activer le streaming de navigation avec loading.tsx sur les routes à chargement lent
  • Réduire le bundle JS initial en lazy-loadant les modals

Non-Goals:

  • Refactorer l'architecture SSE (sujet Phase 2)
  • Changer la stratégie de cache/revalidation (sujet Phase 2)
  • Optimiser les requêtes Prisma profondes (sujet Phase 3)
  • Modifier le comportement fonctionnel existant

Decisions

1. Batch resolveCollaborator par collect + single query

Décision : Dans session-queries.ts, collecter tous les userId des collaborateurs d'une liste de sessions, puis faire un seul prisma.user.findMany({ where: { id: { in: [...ids] } } }), et mapper les résultats en mémoire.

Alternatives : Garder le N+1 mais ajouter un cache mémoire par requête → rejeté car ne résout pas le problème structurellement.

2. Debounce via useRef + setTimeout natif

Décision : Dans useLive.ts, utiliser useRef pour stocker un timer et setTimeout / clearTimeout pour debounce à 300ms. Pas de dépendance externe.

Alternatives : Bibliothèque lodash.debounce → rejeté pour éviter une dépendance pour 5 lignes.

3. cleanupOldEvents inline dans createEvent

Décision : Appeler cleanupOldEvents à la fin de chaque createEvent (fire-and-forget, pas d'await bloquant). La purge garde les 50 derniers événements par session (seuil actuel).

Alternatives : Cron externe → trop complexe pour un quick win ; interval côté API SSE → couplage non souhaité.

4. loading.tsx avec skeleton minimaliste

Décision : Créer un loading.tsx par route principale (/sessions, /weather, /users) avec un skeleton générique (barres grises animées). Le composant est statique et ultra-léger.

5. next/dynamic avec ssr: false sur les modals

Décision : Wrapper ShareModal (et CollaborationToolbar si pertinent) avec next/dynamic({ ssr: false }). Le composant parent gère le loading state.

Risks / Trade-offs

  • Debounce 300ms → légère latence perçue sur les mises à jour collaboratives. Mitigation : valeur configurable via constante.
  • cleanupOldEvents fire-and-forget → si la purge échoue, les erreurs sont silencieuses. Mitigation : logger l'erreur sans bloquer.
  • Batch resolveCollaborator → si la liste de sessions est très grande (>500), la requête IN peut être lente. Mitigation : acceptable pour les volumes actuels ; paginer si nécessaire (Phase 3).
  • next/dynamic ssr: false → les modals ne sont pas rendus côté serveur. Acceptable car ils sont interactifs uniquement.

Migration Plan

Chaque optimisation est indépendante et déployable séparément. Pas de migration de données. Rollback : revert du commit concerné. L'ordre recommandé : (1) batch resolveCollaborator, (2) cleanupOldEvents, (3) debounce useLive, (4) loading.tsx, (5) next/dynamic.