Files
roadwave/docs/architecture/sequences/cache-geospatial.md
jpgiannetti 5e5fcf4714 refactor(docs): réorganiser la documentation selon principes DDD
Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend.

**Structure cible:**
```
docs/domains/
├── README.md (Context Map)
├── _shared/ (Core Domain)
├── recommendation/ (Supporting Subdomain)
├── content/ (Supporting Subdomain)
├── moderation/ (Supporting Subdomain)
├── advertising/ (Generic Subdomain)
├── premium/ (Generic Subdomain)
└── monetization/ (Generic Subdomain)
```

**Changements effectués:**

Phase 1: Création de l'arborescence des 7 bounded contexts
Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/
Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/
Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/
Phase 5: Création des README.md pour chaque domaine
Phase 6: Déplacement des features Gherkin vers domains/*/features/
Phase 7: Création du Context Map (domains/README.md)
Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation
Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh)
Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/)

**Configuration des tests:**
- Makefile: godog run docs/domains/*/features/
- scripts/generate-bdd-docs.py: features_dir → docs/domains

**Avantages:**
 Cohésion forte: toute la doc d'un domaine au même endroit
 Couplage faible: domaines indépendants, dépendances explicites
 Navigabilité améliorée: README par domaine = entrée claire
 Alignement code/docs: miroir de backend/internal/
 Onboarding facilité: exploration domaine par domaine
 Tests BDD intégrés: features au plus près des règles métier

Voir docs/REFACTOR-DDD.md pour le plan complet.
2026-02-07 17:15:02 +01:00

12 KiB
Raw Blame History

Diagramme de Séquence : Cache Géospatial Redis

Architecture du cache Redis Geospatial pour l'optimisation des requêtes de découverte de contenu géolocalisé.

Vue d'ensemble

Le cache Redis Geospatial permet d'accélérer la recherche de contenus audio à proximité d'une position GPS en évitant des calculs PostGIS coûteux sur PostgreSQL à chaque requête.

Performance :

  • Sans cache : ~200-500ms (calcul PostGIS sur 100K points)
  • Avec cache : ~5-10ms (filtrage Redis en mémoire)

Flux complet : Cold Start → Warm Cache

sequenceDiagram
    participant User as 📱 Utilisateur<br/>(Paris)
    participant Backend as 🔧 Backend Go
    participant Redis as 🔴 Redis Geospatial<br/>(Cache)
    participant PostgreSQL as 🗄️ PostgreSQL<br/>+ PostGIS
    participant CDN as 🌐 NGINX Cache (OVH VPS)

    Note over User,CDN: 🥶 Cold Start - Cache vide

    User->>Backend: GET /contents?lat=48.8566&lon=2.3522&radius=50km
    Backend->>Redis: EXISTS geo:catalog
    Redis-->>Backend: false (cache vide)

    Backend->>PostgreSQL: SELECT id, lat, lon, title, geo_level<br/>FROM contents WHERE active=true
    Note over PostgreSQL: Tous les contenus actifs<br/>de la plateforme (métadonnées)
    PostgreSQL-->>Backend: 100K contenus (métadonnées)

    Backend->>Redis: GEOADD geo:catalog<br/>lon1 lat1 "content:1"<br/>lon2 lat2 "content:2"<br/>... (100K entrées)
    Note over Redis: Stockage index spatial<br/>en mémoire (~20 MB)
    Redis-->>Backend: OK (100000)

    Backend->>Redis: EXPIRE geo:catalog 300
    Note over Redis: TTL 5 minutes

    Backend->>Redis: GEORADIUS geo:catalog<br/>2.3522 48.8566 50 km
    Note over Redis: Filtrage spatial instantané<br/>(index geohash)
    Redis-->>Backend: [content:123, content:456, ...]<br/>(~500 IDs dans rayon)

    Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE id IN (123, 456, ...)<br/>AND geo_level = 'gps_precise'
    Note over PostgreSQL: Récupération détails complets<br/>uniquement contenus proches
    PostgreSQL-->>Backend: Détails complets (500 contenus GPS)

    Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE city='Paris' OR dept='75'<br/>OR region='IDF' OR geo_level='national'
    Note over PostgreSQL: Contenus par niveau géographique
    PostgreSQL-->>Backend: Contenus ville/région/national

    Backend->>Backend: Scoring & mixage :<br/>- GPS proche : 70%<br/>- Ville : 20%<br/>- Région : 8%<br/>- National : 2%

    Backend-->>User: JSON: [{id, title, creator, audioUrl, score}, ...]<br/>(playlist mixée et scorée)

    Note over User,CDN: 🎵 Lecture audio (requêtes séparées)

    User->>CDN: GET /audio/content-123.m3u8
    CDN-->>User: Playlist HLS

    User->>CDN: GET /audio/content-123-segment-001.ts
    CDN-->>User: Segment audio Opus

    Note over User,CDN: 🔥 Warm Cache - Utilisateur 2 à Lyon (45km+)

    participant User2 as 📱 Utilisateur 2<br/>(Lyon)

    User2->>Backend: GET /contents?lat=45.7640&lon=4.8357&radius=50km
    Backend->>Redis: EXISTS geo:catalog
    Redis-->>Backend: true ✅ (cache chaud)

    Backend->>Redis: GEORADIUS geo:catalog<br/>4.8357 45.7640 50 km
    Note over Redis: Filtrage instantané<br/>sur cache existant
    Redis-->>Backend: [content:789, content:012, ...]<br/>(~300 IDs différents)

    Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE id IN (789, 012, ...)<br/>AND geo_level = 'gps_precise'
    PostgreSQL-->>Backend: Détails complets

    Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE city='Lyon' OR dept='69'<br/>OR region='Auvergne-RA' OR geo_level='national'
    PostgreSQL-->>Backend: Contenus ville/région/national

    Backend->>Backend: Scoring & mixage

    Backend-->>User2: JSON: [{id, title, creator, audioUrl, score}, ...]

Stratégie de cache

Cache du catalogue complet (approche choisie)

Principe : Au premier cache miss, charger TOUS les contenus géolocalisés en une seule fois dans Redis.

Avantages :

  • 1 seul cache miss au démarrage de l'instance
  • Toutes les requêtes suivantes servies par Redis (n'importe quelle position GPS)
  • Simple à gérer (1 seule clé Redis : geo:catalog)
  • Pas de duplication de données

Inconvénients :

  • ⚠️ Premier utilisateur subit le cold start (~500ms-1s)
  • ⚠️ Nécessite charger toute la base (acceptable : ~20 MB pour 100K contenus)

Alternatives non retenues :

  • Cache par zone géographique → cache miss fréquents, complexité gestion chevauchements
  • Cache à la demande → trop de cache miss, pas d'optimisation réelle

Détails techniques

1. Données stockées dans Redis

Clé Redis : geo:catalog

Structure :

GEOADD geo:catalog
  2.3522 48.8566 "content:12345"  # lon, lat, member
  4.8357 45.7640 "content:67890"
  ...

Taille mémoire :

  • ~200 bytes par entrée (ID + coordonnées + index geohash)
  • 100K contenus = ~20 MB
  • Négligeable pour Redis (plusieurs GB RAM disponibles)

TTL : 5 minutes (300 secondes)

  • Le contenu géolocalisé est quasi-statique (change peu)
  • Rechargement automatique toutes les 5 minutes si cache expiré
  • Permet de propager les nouveaux contenus rapidement

2. Niveaux géographiques

Le cache Redis ne contient que les contenus avec GPS précis (geo_level = 'gps_precise').

Les autres niveaux géographiques sont gérés par filtrage applicatif :

Niveau Stockage Requête
GPS précis Redis + PostgreSQL GEORADIUS puis SELECT WHERE id IN (...)
Ville PostgreSQL uniquement SELECT WHERE city = ?
Département PostgreSQL uniquement SELECT WHERE department = ?
Région PostgreSQL uniquement SELECT WHERE region = ?
National PostgreSQL uniquement SELECT WHERE geo_level = 'national'

3. Commandes Redis utilisées

# Vérifier existence du cache
EXISTS geo:catalog
# Retour : 0 (n'existe pas) ou 1 (existe)

# Charger le catalogue complet (cold start)
GEOADD geo:catalog 2.3522 48.8566 "content:1" 4.8357 45.7640 "content:2" ...
# Retour : nombre d'éléments ajoutés

# Définir TTL 5 minutes
EXPIRE geo:catalog 300

# Rechercher contenus dans un rayon
GEORADIUS geo:catalog 2.3522 48.8566 50 km
# Retour : ["content:123", "content:456", ...]

# Optionnel : obtenir distance et coordonnées
GEORADIUS geo:catalog 2.3522 48.8566 50 km WITHDIST WITHCOORD
# Retour : [["content:123", "12.5", ["2.35", "48.85"]], ...]

4. Algorithme de scoring

Le backend mixe les résultats selon une pondération :

Score final =
  (Pertinence GPS × 0.70) +
  (Pertinence Ville × 0.20) +
  (Pertinence Région × 0.08) +
  (Pertinence National × 0.02)

Critères de pertinence :

  • GPS : Plus proche = score élevé (distance inversée)
  • Ville/Région : Matching exact = score maximal
  • National : Score fixe faible (contenu générique)

Mixage playlist :

  1. Trier tous les contenus par score décroissant
  2. Appliquer diversité créateurs (pas 3 contenus du même créateur d'affilée)
  3. Injecter contenus sponsorisés/mis en avant (futurs)
  4. Retourner top 50 pour la session d'écoute

Métriques de performance

Temps de réponse typiques

Scénario Latence Détail
Cold start 500-1000ms Chargement 100K contenus dans Redis + requête
Warm cache 5-10ms GEORADIUS + SELECT WHERE id IN (...)
TTL expiré 500-1000ms Rechargement automatique

Charge serveurs

Composant Sans cache Avec cache
PostgreSQL CPU 60-80% 10-20%
Redis CPU N/A 5-15%
Throughput ~50 req/s ~500 req/s

Cas limites et optimisations futures

Cas limite 1 : Contenu très dense (Paris intra-muros)

Problème : 10K contenus dans rayon 5km → trop de résultats

Solution actuelle :

  • Limiter résultats Redis à 1000 premiers (tri par distance)
  • Scorer et filtrer côté application

Optimisation future :

  • Ajuster rayon dynamiquement selon densité
  • Utiliser GEORADIUS ... COUNT 500 pour limiter côté Redis

Cas limite 2 : Zones rurales (peu de contenu)

Problème : Rayon 50km retourne <10 contenus

Solution actuelle :

  • Augmenter poids contenus région/national dans le scoring
  • Suggérer contenus nationaux populaires

Optimisation future :

  • Augmenter rayon automatiquement jusqu'à obtenir min 20 contenus
  • GEORADIUS ... 100 km si rayon initial insuffisant

Cas limite 3 : Nombreux créateurs actifs (évolutivité)

Problème : Cache 100K → 1M contenus (200 MB Redis)

Solution actuelle :

  • 200 MB reste acceptable pour Redis

Optimisation future :

  • Sharding géographique : cache Europe, cache USA, etc.
  • Limiter cache aux contenus actifs 90 derniers jours

Invalidation du cache

Stratégies d'invalidation

Événement Action cache Détail
Nouveau contenu publié Lazy (TTL) Visible sous 5 minutes max
Contenu supprimé/modéré Lazy (TTL) Disparaît sous 5 minutes max
Mise à jour GPS contenu Lazy (TTL) Nouvelle position sous 5 minutes
Déploiement backend Flush volontaire DEL geo:catalog si schema change

Pas d'invalidation immédiate pour simplifier l'architecture (cohérence éventuelle acceptable).

Alternative future :

  • Pub/Sub Redis : notifier toutes les instances backend lors d'un changement
  • GEOADD geo:catalog 2.35 48.85 "content:new" pour ajout immédiat

Schéma simplifié

┌─────────────────────────────────────────────────────────┐
│                    Utilisateur                          │
│                 (Position GPS actuelle)                 │
└────────────────────────┬────────────────────────────────┘
                         │
                         │ GET /contents?lat=X&lon=Y&radius=Z
                         ▼
┌─────────────────────────────────────────────────────────┐
│                   Backend Go (Fiber)                    │
│                                                         │
│  1. Vérifier cache Redis                               │
│     ├─ Cache HIT → GEORADIUS rapide                    │
│     └─ Cache MISS → Charger catalogue complet          │
│                                                         │
│  2. Filtrer contenus GPS proches (Redis)               │
│  3. Récupérer contenus ville/région/national (PG)      │
│  4. Scorer et mixer selon pondération                  │
│  5. Retourner playlist                                 │
└────────────┬───────────────────────────┬────────────────┘
             │                           │
             │ GEORADIUS                 │ SELECT détails
             ▼                           ▼
┌─────────────────────┐    ┌───────────────────────────┐
│  Redis Geospatial   │    │  PostgreSQL + PostGIS     │
│  (Index spatial)    │    │  (Données complètes)      │
│                     │    │                           │
│  • geo:catalog      │    │  • contents (détails)     │
│  • TTL 5 min        │    │  • users                  │
│  • ~20 MB mémoire   │    │  • playlists              │
└─────────────────────┘    └───────────────────────────┘

Références