Initial commit
This commit is contained in:
319
docs/architecture/sequences/cache-geospatial.md
Normal file
319
docs/architecture/sequences/cache-geospatial.md
Normal file
@@ -0,0 +1,319 @@
|
||||
# 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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
- [ADR-005 : Base de données](../../adr/005-base-de-donnees.md)
|
||||
- [Redis Geospatial Commands](https://redis.io/docs/data-types/geospatial/)
|
||||
- [PostGIS Documentation](https://postgis.net/documentation/)
|
||||
- [Règles métier : Découverte de contenu géolocalisé](../../regles-metier/03-decouverte-contenu.md)
|
||||
Reference in New Issue
Block a user