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.
This commit is contained in:
2
openspec/changes/perf-data-optimization/.openspec.yaml
Normal file
2
openspec/changes/perf-data-optimization/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-09
|
||||
59
openspec/changes/perf-data-optimization/design.md
Normal file
59
openspec/changes/perf-data-optimization/design.md
Normal file
@@ -0,0 +1,59 @@
|
||||
## Context
|
||||
|
||||
`src/services/weather.ts` utilise `findMany` sans `take` ni `orderBy`, chargeant potentiellement des centaines d'entrées pour calculer des tendances qui n'utilisent que les 30-90 derniers points. Les services de sessions utilisent `include: { items: true, shares: true, events: true }` pour construire les listes, alors que l'affichage carte n'a besoin que du titre, de la date, du comptage d'items et du statut de partage. `User.name` est filtré dans les recherches admin mais sans index SQLite. Les pages les plus visitées (`/sessions`, `/users`) recalculent leurs données à chaque requête.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Borner le chargement historique weather à une constante configurable
|
||||
- Réduire la taille des objets retournés par les queries de liste (select vs include)
|
||||
- Ajouter un index SQLite sur `User.name`
|
||||
- Introduire un cache Next.js sur les queries de liste avec invalidation ciblée
|
||||
|
||||
**Non-Goals:**
|
||||
- Changer la structure des modèles Prisma
|
||||
- Modifier le rendu des pages (les sélections couvrent tous les champs affichés)
|
||||
- Introduire un cache externe (Redis, Memcached)
|
||||
- Optimiser les pages de détail session (hors scope)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Constante WEATHER_HISTORY_LIMIT dans lib/types.ts
|
||||
|
||||
**Décision** : Définir `WEATHER_HISTORY_LIMIT = 90` dans `src/lib/types.ts` (cohérent avec les autres constantes de config). La query devient : `findMany({ orderBy: { createdAt: 'desc' }, take: WEATHER_HISTORY_LIMIT })`.
|
||||
|
||||
**Alternatives** : Paramètre d'URL ou env var → sur-ingénierie pour un seuil rarement modifié.
|
||||
|
||||
### 2. Select minimal pour les listes — interface ListItem dédiée
|
||||
|
||||
**Décision** : Pour chaque service de liste, définir un type `XxxListItem` dans `types.ts` avec uniquement les champs de la carte (id, title, createdAt, _count.items, shares.length). Utiliser `select` Prisma pour matcher exactement ce type.
|
||||
|
||||
**Alternatives** : Garder `include` et filtrer côté TypeScript → charge DB identique, gain nul.
|
||||
|
||||
### 3. Index @@index([name]) sur User
|
||||
|
||||
**Décision** : Ajouter `@@index([name])` dans le modèle `User` de `schema.prisma`. Créer une migration nommée `add_user_name_index`. Impact : SQLite crée un B-tree index, recherches `LIKE 'x%'` bénéficient de l'index (prefix match).
|
||||
|
||||
**Note** : `LIKE '%x%'` (contains) n'utilise pas l'index en SQLite — acceptable, le use case principal est la recherche par préfixe.
|
||||
|
||||
### 4. unstable_cache avec tags sur requêtes de liste
|
||||
|
||||
**Décision** : Wrapper les fonctions de service de liste (ex: `getSessionsForUser`, `getUserStats`) avec `unstable_cache(fn, [cacheKey], { tags: ['sessions-list:userId'] })`. Les Server Actions appellent `revalidateTag` correspondant après mutation.
|
||||
|
||||
Durée de cache : `revalidate: 60` secondes en fallback, mais invalidation explicite prioritaire.
|
||||
|
||||
**Alternatives** : `React.cache` → par-requête uniquement, pas de persistance entre navigations ; `fetch` avec cache → ne s'applique pas aux queries Prisma.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **select strict** → si un composant accède à un champ non sélectionné, erreur TypeScript au build (bonne chose — détecté tôt).
|
||||
- **unstable_cache** → API Next.js marquée unstable. Mitigation : isoler dans les services, wrapper facilement remplaçable.
|
||||
- **Index User.name** → légère augmentation de la taille du fichier SQLite et du temps d'écriture. Négligeable pour les volumes actuels.
|
||||
- **WEATHER_HISTORY_LIMIT** → les calculs de tendance doivent fonctionner avec N entrées ou moins. Vérifier que l'algorithme est robuste avec un historique partiel.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Migration Prisma `add_user_name_index` (non-destructif, peut être appliqué à tout moment)
|
||||
2. Ajout `WEATHER_HISTORY_LIMIT` + update query weather (indépendant)
|
||||
3. Refactoring select par service (vérifier TypeScript au build à chaque service)
|
||||
4. Ajout cache layer en dernier (dépend des tags définis en Phase 2 si applicable, sinon définir localement)
|
||||
29
openspec/changes/perf-data-optimization/proposal.md
Normal file
29
openspec/changes/perf-data-optimization/proposal.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## Why
|
||||
|
||||
Les requêtes Prisma des pages les plus fréquentées chargent trop de données : `weather.ts` ramène tout l'historique sans borne, les queries de la sessions page incluent des relations profondes inutiles pour l'affichage liste, et aucun cache n'est appliqué sur les requêtes répétées à chaque navigation. Ces optimisations réduisent la taille des payloads et le temps de réponse DB sans changer le comportement.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Weather historique borné** : ajouter `take` + `orderBy createdAt DESC` dans `src/services/weather.ts`, configurable via constante (défaut : 90 entrées)
|
||||
- **Select fields sur sessions list** : remplacer les `include` profonds par des `select` avec uniquement les champs affichés dans les cards de liste
|
||||
- **Index `User.name`** : ajouter `@@index([name])` dans `prisma/schema.prisma` + migration
|
||||
- **Cache sur requêtes fréquentes** : wraper les queries de liste sessions et stats utilisateurs avec `unstable_cache` + tags, invalidés lors des mutations
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `query-cache-layer`: Cache Next.js sur les requêtes de liste fréquentes avec invalidation par tags
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- Aucune modification de spec comportementale — optimisations internes transparentes
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/services/weather.ts` — ajout limite + orderBy
|
||||
- `src/services/` (tous les services de liste) — `include` → `select`
|
||||
- `prisma/schema.prisma` — ajout `@@index([name])` sur `User`
|
||||
- `prisma/migrations/` — nouvelle migration pour l'index
|
||||
- `src/services/` — wrapping `unstable_cache` sur queries fréquentes
|
||||
- `src/actions/` — ajout `revalidateTag` correspondants (complément Phase 2)
|
||||
@@ -0,0 +1,30 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Cached session list queries
|
||||
Frequently-called session list queries SHALL be cached using Next.js `unstable_cache` with user-scoped tags, avoiding redundant DB reads on repeated navigations.
|
||||
|
||||
#### Scenario: Session list served from cache on repeated navigation
|
||||
- **WHEN** a user navigates to the sessions page multiple times within the cache window
|
||||
- **THEN** the session list data SHALL be served from cache on subsequent requests
|
||||
- **THEN** no additional Prisma query SHALL be executed for cached data
|
||||
|
||||
#### Scenario: Cache invalidated after mutation
|
||||
- **WHEN** a Server Action creates, updates, or deletes a session
|
||||
- **THEN** the corresponding cache tag SHALL be invalidated via `revalidateTag`
|
||||
- **THEN** the next request SHALL fetch fresh data from the DB
|
||||
|
||||
### Requirement: Weather history bounded query
|
||||
The weather service SHALL limit historical data loading to a configurable maximum number of entries (default: 90), ordered by most recent first.
|
||||
|
||||
#### Scenario: Weather history respects limit
|
||||
- **WHEN** the weather service fetches historical entries
|
||||
- **THEN** at most `WEATHER_HISTORY_LIMIT` entries SHALL be returned
|
||||
- **THEN** entries SHALL be ordered by `createdAt` DESC (most recent first)
|
||||
|
||||
### Requirement: Minimal field selection on list queries
|
||||
Service functions returning lists for display purposes SHALL use Prisma `select` with only the fields required for the list UI, not full `include` of related models.
|
||||
|
||||
#### Scenario: Sessions list query returns only display fields
|
||||
- **WHEN** the sessions list service function is called
|
||||
- **THEN** the returned objects SHALL contain only fields needed for card display (id, title, createdAt, item count, share status)
|
||||
- **THEN** full related model objects (items array, events array) SHALL NOT be included
|
||||
30
openspec/changes/perf-data-optimization/tasks.md
Normal file
30
openspec/changes/perf-data-optimization/tasks.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## 1. Index User.name (migration Prisma)
|
||||
|
||||
- [ ] 1.1 Lire `prisma/schema.prisma` et localiser le modèle `User`
|
||||
- [ ] 1.2 Ajouter `@@index([name])` au modèle `User`
|
||||
- [ ] 1.3 Exécuter `pnpm prisma migrate dev --name add_user_name_index`
|
||||
- [ ] 1.4 Vérifier que la migration s'applique sans erreur et que `prisma studio` montre l'index
|
||||
|
||||
## 2. Weather: limiter le chargement historique
|
||||
|
||||
- [ ] 2.1 Ajouter la constante `WEATHER_HISTORY_LIMIT = 90` dans `src/lib/types.ts`
|
||||
- [ ] 2.2 Lire `src/services/weather.ts` et localiser la query `findMany` des entrées historiques
|
||||
- [ ] 2.3 Ajouter `take: WEATHER_HISTORY_LIMIT` et `orderBy: { createdAt: 'desc' }` à la query
|
||||
- [ ] 2.4 Vérifier que les calculs de tendances fonctionnent avec un historique partiel
|
||||
|
||||
## 3. Select fields sur les queries de liste
|
||||
|
||||
- [ ] 3.1 Lire les services de liste : `src/services/swot.ts`, `motivators.ts`, `year-review.ts`, `weekly-checkin.ts`, `weather.ts`
|
||||
- [ ] 3.2 Identifier les `include` utilisés dans les fonctions de liste (pas de détail session)
|
||||
- [ ] 3.3 Définir des types `XxxListItem` dans `src/lib/types.ts` avec uniquement les champs affichés en carte
|
||||
- [ ] 3.4 Remplacer les `include` profonds par `select` correspondant aux types `XxxListItem` dans chaque service
|
||||
- [ ] 3.5 Mettre à jour les composants de liste qui utilisaient les champs supprimés (vérifier les erreurs TypeScript)
|
||||
- [ ] 3.6 Vérifier `pnpm build` sans erreurs TypeScript
|
||||
|
||||
## 4. Cache layer sur requêtes fréquentes
|
||||
|
||||
- [ ] 4.1 Créer `src/lib/cache-tags.ts` si pas encore fait (sinon compléter) avec les helpers de tags : `sessionTag(id)`, `sessionsListTag(userId)`, `userStatsTag(userId)`
|
||||
- [ ] 4.2 Wrapper la fonction de liste sessions dans chaque service avec `unstable_cache(fn, [key], { tags: [sessionsListTag(userId)], revalidate: 60 })`
|
||||
- [ ] 4.3 Wrapper la fonction de stats utilisateurs (`getUserStats` ou équivalent) avec `unstable_cache`
|
||||
- [ ] 4.4 Vérifier que les Server Actions de création/suppression de session appellent `revalidateTag(sessionsListTag(userId))`
|
||||
- [ ] 4.5 Tester l'invalidation : créer une session → vérifier qu'elle apparaît immédiatement dans la liste
|
||||
Reference in New Issue
Block a user