💻 Développement

dev-caching-strategy

Stratégie de cache adaptée à chaque cas d'usage (Redis, Memcached, in-memory).

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-caching-strategy --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-caching-strategy -Launch"

🚀 Déjà installé ?

claude "/dev-caching-strategy"

Ou tapez /dev-caching-strategy dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

cacheRediscachingmise en cacheperformancecache invalidationCDNdistributed cache

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-caching-strategy ~/.claude/skills/

Payload du plugin : skills/dev-caching-strategy · source éditable : dev-skills/caching-strategy

📖 Manuel

Caching Strategy

Workflow

1. Qualifier le besoin

Avant de cacher quoi que ce soit, répondre à ces trois questions :

QuestionCache pertinent si…
Ratio lecture/écriture ?Lectures >> écritures (≥ 10:1)
Données partagées entre instances ?Oui → distributed cache obligatoire
Tolérance à la stale data ?Si cohérence stricte requise, invalidation event-based obligatoire

Cibles prioritaires : résultats de requêtes DB coûteuses, appels API tiers (limités en rate), calculs d'agrégation, sessions utilisateur.

2. Choisir le bon niveau de cache

Requête utilisateur
      │
      ▼
[Browser cache]          ← Cache-Control, ETag, Last-Modified
      │
      ▼
[CDN / Edge cache]       ← Cloudflare, Azure CDN, AWS CloudFront
      │
      ▼
[Application cache]      ← In-process (MemoryCache) ou distributed (Redis)
      │
      ▼
[Database query cache]   ← pg_bouncer result cache, MySQL query cache (désactivé > MySQL 8)
      │
      ▼
[Source of truth]        ← Base de données / API tierce

Règle de décision :

3. Sélectionner le pattern

PatternQuand l'utiliserTrade-off
Cache-aside (lazy)Contrôle maximal, données hétérogènesDouble aller DB au premier miss
Read-throughSimplification du code applicatifComplexité déportée vers le cache
Write-throughCohérence forte (lecture après écriture)Latence d'écriture augmentée
Write-behindThroughput d'écriture maximalRisque de perte de données si crash
Refresh-aheadDonnées populaires avec TTL courtPrématuré si prédiction inexacte

Pattern le plus courant en production : cache-aside + invalidation active sur mutation.

4. Concevoir les cache keys

Convention recommandée : {service}:{entité}:{id} ou {service}:{entité}:{id}:{variant}

users:profile:42
users:profile:42:fr          # variante locale
catalog:product:SKU-9901
catalog:search:hash(query)   # hash si la clé est longue

5. Définir l'expiration

Données système (config, feature flags)   → TTL 5-30 min ou invalidation event-based
Données utilisateur (profil, panier)      → TTL 5-15 min
Résultats de requêtes analytiques        → TTL 1-24 h
Résultats de recherche / catalogue       → TTL 5-30 min + purge sur update
Sessions                                  → TTL = durée de session + sliding expiration
Assets statiques (CDN)                   → Cache-Control: max-age=31536000, immutable

Eviction policies Redis (à configurer selon l'usage) :

6. Snippets copiables

Redis cache-aside — Python (redis-py)

import redis, json, hashlib

r = redis.Redis(host="localhost", port=6379, decode_responses=True)

def get_product(product_id: int) -> dict:
    key = f"catalog:product:{product_id}"
    cached = r.get(key)
    if cached:
        return json.loads(cached)
    product = db.query("SELECT * FROM products WHERE id = %s", product_id)
    r.setex(key, 300, json.dumps(product))  # TTL 5 min
    return product

def update_product(product_id: int, data: dict):
    db.execute("UPDATE products SET ... WHERE id = %s", product_id)
    r.delete(f"catalog:product:{product_id}")  # invalidation active

IDistributedCache — .NET 8

public async Task<UserProfile?> GetProfileAsync(int userId, CancellationToken ct)
{
    var key = $"users:profile:{userId}";
    var cached = await _cache.GetStringAsync(key, ct);
    if (cached is not null)
        return JsonSerializer.Deserialize<UserProfile>(cached);

    var profile = await _db.Users.FindAsync(userId, ct);
    if (profile is not null)
        await _cache.SetStringAsync(key, JsonSerializer.Serialize(profile),
            new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) }, ct);
    return profile;
}

HTTP Cache-Control (API REST)

Cache-Control: public, max-age=300, stale-while-revalidate=60
ETag: "abc123"
Vary: Accept-Language, Accept-Encoding

Output caching — ASP.NET Core 8

app.MapGet("/products/{id}", async (int id, IProductService svc) =>
    await svc.GetAsync(id))
   .CacheOutput(p => p.Expire(TimeSpan.FromMinutes(5)).Tag("products"));

// Purge ciblée :
await outputCache.EvictByTagAsync("products", ct);

7. Prévenir le cache stampede (thundering herd)

import threading

_locks = {}

def get_with_lock(key, loader_fn, ttl=300):
    cached = r.get(key)
    if cached:
        return json.loads(cached)
    lock = _locks.setdefault(key, threading.Lock())
    with lock:
        cached = r.get(key)  # double-check après lock
        if cached:
            return json.loads(cached)
        value = loader_fn()
        r.setex(key, ttl, json.dumps(value))
        return value

Alternative Redis native : SET key value NX EX 300 (SET if Not eXists).

8. Configurer Redis (production)

# redis.conf — points critiques
maxmemory 2gb
maxmemory-policy allkeys-lru
save ""                        # désactiver AOF/RDB si cache pur (pas de persistance)
requirepass <password>
bind 127.0.0.1                 # ne jamais exposer Redis sans auth sur réseau public

Cluster Redis (≥ 3 nœuds) pour HA ; Redis Sentinel pour failover automatique sans cluster.

9. Monitorer

Métriques indispensables :

redis-cli INFO stats | grep -E "hits|misses|evicted"
redis-cli INFO memory | grep used_memory_human
redis-cli --latency -h localhost

Anti-patterns et pièges

PiègeSymptômeCorrectif
Cache everythingMémoire saturée, hit rate basProfiler les hot paths avant de cacher
TTL trop longDonnées périmées servies longtempsInvalidation active sur mutation
TTL trop courtHit rate effondré, DB surchargéeAnalyser la fréquence réelle de changement
Thundering herdPics CPU/DB au restartMutex ou probabilistic early expiration
Cache stampede multi-serveursMême problème à grande échelleRedis SETNX ou Lua lock distribué
Cache sans namespaceInvalidation impossible sans flush totalConvention de keys dès le départ
Cacher des données PII sans chiffrementRGPD / fuite de donnéesChiffrer ou ne pas cacher
Redis exposé sans authVecteur d'attaque critiquerequirepass + TLS + bind local
Oublier Vary sur le CDNCache non segmenté par langue/deviceAjouter Vary: Accept-Language
Write-behind sans WALPerte de données au crashPréférer write-through pour les données critiques

Bonnes pratiques 2026