Quand utiliser ElastiCache Redis : accélérer les lectures RDS avec un cache distribué

Votre base RDS répond en 200-400 ms sur des requêtes identiques relancées toutes les secondes — c'est le symptôme classique d'une absence de couche cache. Ajouter ElastiCache Redis devant RDS n'est pas une optimisation cosmétique : c'est un changement architectural qui déplace la charge de lecture du moteur relationnel vers un store en mémoire conçu pour ça.

TL;DR — ElastiCache Redis face à RDS : quand et pourquoi

SituationSans cacheAvec ElastiCache Redis
Lecture répétée du même enregistrementRequête SQL à chaque appelRéponse depuis la mémoire, RDS non sollicité
Pic de trafic soudainConnexions RDS saturéesCache absorbe la majorité des lectures
Données de session utilisateurStockage en base ou fichierTTL natif, accès O(1)
Résultats de calculs coûteuxRecalcul à chaque requêteRésultat mis en cache jusqu'à expiration
Données qui changent souventCache inutile ou dangereuxInvalidation explicite nécessaire

Comment fonctionne la couche cache Redis devant RDS

Redis est un store clé-valeur en mémoire. Quand votre application lit une donnée, elle interroge Redis en premier. Si la clé existe et n'a pas expiré (cache hit), la réponse revient sans toucher RDS. Si la clé est absente ou expirée (cache miss), l'application interroge RDS, stocke le résultat dans Redis avec un TTL, puis retourne la réponse. Ce pattern s'appelle cache-aside (ou lazy loading) — c'est le plus courant sur AWS.

Pensez à Redis comme à un comptoir de restauration rapide : les plats du jour sont prêts immédiatement. Si vous commandez quelque chose hors menu, le cuisinier (RDS) le prépare, et le comptoir le garde au chaud pour la prochaine commande identique.
graph LR App["Application"] --> RedisCheck{"Redis clé présente ?"} RedisCheck -- "Cache Hit" --> ReturnCache["Retourner valeur Redis"] RedisCheck -- "Cache Miss" --> RDS["Interroger RDS (requête SQL)"] RDS --> WriteCache["Écrire dans Redis avec TTL"] WriteCache --> ReturnDB["Retourner résultat"] App2["Application (écriture)"] --> UpdateRDS["UPDATE/DELETE sur RDS"] UpdateRDS --> Invalidate["Supprimer clé dans Redis"]
  1. Requête applicative — l'application interroge Redis avec une clé construite à partir des paramètres de la requête.
  2. Cache hit — Redis retourne la valeur sérialisée directement. RDS n'est pas contacté.
  3. Cache miss — Redis retourne nil. L'application exécute la requête SQL sur RDS.
  4. Mise en cache — le résultat RDS est écrit dans Redis avec un TTL défini par la logique métier.
  5. Invalidation — lors d'une écriture (UPDATE/DELETE), l'application supprime ou met à jour la clé Redis correspondante.

Quand ElastiCache Redis résout réellement le problème de lenteur RDS

Le cache Redis est efficace dans des conditions précises. Avant de provisionner un cluster, vérifiez que votre cas correspond à l'un de ces profils.

Données lues fréquemment, modifiées rarement

Les catalogues produits, les configurations applicatives, les données de référence (pays, devises, catégories) — ce sont les candidats idéaux. Le ratio lecture/écriture est élevé, donc le TTL peut être long sans risque de stale data significatif. Un TTL de 5 à 30 minutes est souvent raisonnable selon la tolérance métier.

Requêtes SQL coûteuses avec résultats stables

Une jointure sur 5 tables qui prend 800 ms mais retourne le même résultat pendant 10 minutes — mettez-la en cache. La clé Redis doit encoder tous les paramètres qui influencent le résultat. Si un seul paramètre change, la clé change, et vous évitez de servir un résultat incorrect.

Sessions utilisateur et tokens

Redis gère nativement l'expiration via TTL. Stocker les sessions en RDS crée une table à forte contention en écriture. Redis élimine ce problème structurellement — chaque SET avec EX est atomique et l'expiration est gérée par Redis lui-même sans job de nettoyage.

Quand le cache Redis ne résout pas le problème

Si vos lectures sont lentes parce que chaque requête porte des paramètres uniques (recherche full-text, filtres dynamiques par utilisateur), le taux de cache hit sera proche de zéro. Redis ne vous aidera pas — le problème est dans l'indexation RDS ou l'architecture de la requête. Vérifiez d'abord avec EXPLAIN ANALYZE avant de provisionner un cache.

Provisionner ElastiCache Redis sur AWS — étapes opérationnelles

Étape 1 — Créer le subnet group ElastiCache dans le même VPC que RDS

ElastiCache doit être dans le même VPC que votre application et accessible depuis les subnets privés. Le subnet group définit dans quels subnets les nœuds Redis peuvent être placés. C'est un prérequis bloquant — sans ça, la connectivité réseau ne fonctionnera pas.

aws elasticache create-cache-subnet-group \
  --cache-subnet-group-name mon-redis-subnet-group \
  --cache-subnet-group-description "Subnet group Redis production" \
  --subnet-ids subnet-0abc123456789def0 subnet-0def987654321abc0 \
  --region us-east-1

Étape 2 — Créer le security group Redis et autoriser le port 6379 depuis l'application

Le security group Redis ne doit accepter que le trafic entrant sur le port 6379 depuis le security group de vos instances applicatives. Ouvrir 0.0.0.0/0 sur un cache Redis en production est une erreur de configuration grave — Redis n'a pas d'authentification réseau par défaut sur les anciennes versions.

aws ec2 create-security-group \
  --group-name redis-sg \
  --description "Security group ElastiCache Redis" \
  --vpc-id vpc-0123456789abcdef0 \
  --region us-east-1

aws ec2 authorize-security-group-ingress \
  --group-id sg-0redis1234567890a \
  --protocol tcp \
  --port 6379 \
  --source-group sg-0app1234567890b \
  --region us-east-1

Étape 3 — Créer le cluster Redis (mode Serverless ou cluster classique)

AWS propose deux modes : ElastiCache Serverless (capacité gérée automatiquement) et les clusters avec nœuds explicites. Pour un premier déploiement avec charge prévisible, un cluster avec un nœud primary et un replica en mode non-cluster est plus simple à opérer et à monitorer. Activez le chiffrement en transit (--transit-encryption-enabled) et l'authentification par token (--auth-token) en production.

🔽 Cliquer pour voir la commande de création du cluster Redis
aws elasticache create-replication-group \
  --replication-group-id mon-redis-prod \
  --replication-group-description "Cache Redis production" \
  --cache-node-type cache.t4g.medium \
  --engine redis \
  --engine-version 7.0 \
  --num-cache-clusters 2 \
  --cache-subnet-group-name mon-redis-subnet-group \
  --security-group-ids sg-0redis1234567890a \
  --transit-encryption-enabled \
  --auth-token "VotreTokenSecretMinimum16Chars" \
  --at-rest-encryption-enabled \
  --automatic-failover-enabled \
  --region us-east-1

Étape 4 — Récupérer l'endpoint primaire pour la configuration applicative

Une fois le cluster en état available, récupérez l'endpoint. En mode non-cluster, utilisez l'endpoint du replication group (primary endpoint) pour les écritures et le reader endpoint pour les lectures — cela distribue la charge de lecture sur les replicas.

aws elasticache describe-replication-groups \
  --replication-group-id mon-redis-prod \
  --query 'ReplicationGroups[0].NodeGroups[0].PrimaryEndpoint' \
  --region us-east-1

Étape 5 — Implémenter le pattern cache-aside dans l'application

Le SDK AWS ne gère pas la logique cache-aside — c'est votre code applicatif qui l'implémente. Voici le pattern en Python avec redis-py. La clé doit être déterministe et unique par combinaison de paramètres. Le TTL est en secondes.

🔽 Cliquer pour voir l'implémentation cache-aside Python
import redis
import json
import hashlib

r = redis.Redis(
    host='mon-redis-prod.xxxxx.ng.0001.use1.cache.amazonaws.com',
    port=6379,
    ssl=True,
    password='VotreTokenSecretMinimum16Chars',
    decode_responses=True
)

def get_produit(produit_id: int, db_conn):
    cache_key = f"produit:{produit_id}"
    
    # Tentative cache hit
    cached = r.get(cache_key)
    if cached is not None:
        return json.loads(cached)
    
    # Cache miss — interroger RDS
    cursor = db_conn.cursor()
    cursor.execute("SELECT * FROM produits WHERE id = %s", (produit_id,))
    row = cursor.fetchone()
    
    if row is None:
        return None
    
    # Mettre en cache avec TTL de 10 minutes
    r.set(cache_key, json.dumps(row), ex=600)
    return row

def update_produit(produit_id: int, data: dict, db_conn):
    cursor = db_conn.cursor()
    cursor.execute(
        "UPDATE produits SET nom = %s, prix = %s WHERE id = %s",
        (data['nom'], data['prix'], produit_id)
    )
    db_conn.commit()
    
    # Invalider le cache après écriture
    r.delete(f"produit:{produit_id}")

Diagnostiquer l'efficacité du cache — métriques CloudWatch à surveiller

Provisionner Redis sans monitorer le taux de hit, c'est conduire sans tableau de bord. Ces métriques CloudWatch sont disponibles nativement pour ElastiCache.

graph TD CW["CloudWatch ElastiCache Metrics"] CW --> HitMiss["CacheHits / CacheMisses"] CW --> Conn["CurrConnections"] CW --> Evict["Evictions"] CW --> Lag["ReplicationLag"] HitMiss --> DiagHit["Taux hit < 80% : revoir TTL ou clés"] Conn --> DiagConn["Pic soudain : vérifier connection pool"] Evict --> DiagEvict["Évictions fréquentes : nœud sous-dimensionné"] Lag --> DiagLag["Lag élevé : lectures replica stale"]
  1. CacheHits / CacheMisses — ratio fondamental. Un taux de hit inférieur à 80% sur des données supposément stables indique un problème de stratégie de clés ou de TTL trop court.
  2. CurrConnections — nombre de connexions actives. Un pic soudain peut indiquer que l'application ne réutilise pas les connexions (absence de connection pooling).
  3. Evictions — nombre d'entrées expulsées avant expiration du TTL parce que la mémoire est pleine. Des évictions fréquentes signifient que le nœud est sous-dimensionné ou que la politique d'éviction (maxmemory-policy) n'est pas adaptée.
  4. ReplicationLag — délai entre le nœud primaire et les replicas. Un lag élevé peut entraîner des lectures stale depuis les replicas.
aws cloudwatch get-metric-statistics \
  --namespace AWS/ElastiCache \
  --metric-name CacheHits \
  --dimensions Name=CacheClusterId,Value=mon-redis-prod-0001-001 \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-15T01:00:00Z \
  --period 300 \
  --statistics Sum \
  --region us-east-1

Le piège classique : stale data et invalidation incohérente

Voici le scénario qu'on voit régulièrement en production : l'équipe active le cache, les temps de réponse s'améliorent, et deux semaines plus tard un bug remonte — des utilisateurs voient des prix incorrects. L'investigation montre que le code d'invalidation du cache n'est appelé que depuis un seul service, mais qu'un second service met aussi à jour la table produits directement en base sans passer par la couche d'invalidation.

Le symptôme : CloudWatch montre un taux de hit de 95%, RDS est soulagé, tout semble parfait. Mais les données servies sont fausses pour les enregistrements modifiés par le second service.

La cause réelle : l'invalidation de cache n'est pas colocalisée avec la logique d'écriture — elle est dans la couche service, pas dans la couche repository. Quand un second chemin d'écriture bypasse la couche service, le cache n'est jamais invalidé.

Le correctif : centraliser toute écriture sur une entité dans une seule couche repository qui garantit l'invalidation. Alternativement, utiliser un TTL court (30-60 secondes) pour les données à cohérence critique, en acceptant une fenêtre de stale data bornée plutôt qu'une incohérence indéterminée.

IAM et sécurité — accès applicatif à ElastiCache

ElastiCache Redis ne s'intègre pas directement avec IAM pour l'authentification des connexions Redis. L'accès réseau est contrôlé par les security groups (VPC). Pour l'authentification au niveau Redis, utilisez les mécanismes suivants selon la version :

  • Redis AUTH (toutes versions) — token partagé configuré via --auth-token à la création.
  • RBAC avec Redis 6+ — ElastiCache supporte les utilisateurs et groupes d'utilisateurs Redis pour un contrôle d'accès plus granulaire via aws elasticache create-user.

Pour les permissions IAM sur les opérations de gestion ElastiCache (création, modification, suppression de clusters), voici une politique minimale pour un rôle applicatif qui n'a besoin que de décrire les endpoints :

🔽 Cliquer pour voir la politique IAM minimale
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DescribeElastiCacheEndpoints",
      "Effect": "Allow",
      "Action": [
        "elasticache:DescribeReplicationGroups",
        "elasticache:DescribeCacheClusters"
      ],
      "Resource": "*"
    }
  ]
}

Conclusion — ElastiCache Redis et RDS : une architecture complémentaire

ElastiCache Redis ne remplace pas RDS — il absorbe les lectures répétitives pour que RDS puisse se concentrer sur ce qu'il fait bien : les transactions, les jointures complexes, et la cohérence des données. La décision d'ajouter un cache doit partir d'une mesure réelle du taux de répétition des requêtes, pas d'une intuition sur la lenteur.

Avant de provisionner, répondez à ces trois questions : quel est mon ratio lecture/écriture sur les tables concernées ? Quelle tolérance métier ai-je pour des données légèrement stale ? Est-ce que toutes mes écritures passent par un chemin centralisé qui peut invalider le cache ? Si vous ne pouvez pas répondre à la troisième, résolvez ça d'abord.

Pour aller plus loin, consultez la documentation officielle ElastiCache Redis et le guide Best Practices ElastiCache.

Glossaire

TermeDéfinition
Cache-aside (Lazy Loading)Pattern où l'application gère elle-même la lecture depuis le cache et le fallback vers la base de données. Le cache n'est peuplé qu'à la demande.
TTL (Time To Live)Durée en secondes après laquelle une clé Redis expire automatiquement. Contrôle la fraîcheur des données cachées.
Cache hit / Cache missHit : la clé demandée existe dans Redis. Miss : la clé est absente ou expirée, nécessitant un accès à RDS.
EvictionSuppression forcée d'entrées Redis avant expiration du TTL quand la mémoire allouée est saturée. Contrôlé par la politique maxmemory-policy.
Replication GroupUnité logique ElastiCache regroupant un nœud primaire et ses replicas. Fournit la haute disponibilité et le basculement automatique.

Commentaires

Posts les plus consultés de ce blog

Structure de base d'une politique IAM : Effect, Action, Resource et Condition expliqués

LSI vs GSI dans DynamoDB : choisir le bon index secondaire

EBS vs EFS pour plusieurs instances EC2 : partager un dossier entre 5 serveurs