Aller au contenu

RDS Proxy devant un Postgres ou MySQL write-heavy — quand il réduit la latence et quand il l'ajoute

RDS Proxy règle le problème de troupeau frénétique après basculement et la tempête de connexions Lambda. Il ajoute aussi 1–3ms par requête et une couche à déboguer. Voici quand ça vaut la peine, la config exacte, et l'authentification IAM qui vous mord la deuxième journée.

Le Postgres client sur RDS tourne à 60% CPU avec max_connections=300, et l’application lance des FATAL: sorry, too many clients already pendant le pic de trafic à 09:00. Le réflexe est de pousser max_connections à 600 et de passer à autre chose. Six semaines plus tard, la base est à 90% CPU avec la plupart des cycles brûlés sur le spawn de processus Postgres et les changements de contexte, et l’application timeoute toujours. La vraie correction est un pool de connexions devant la base, et sur RDS le chemin de moindre résistance est RDS Proxy. C’est un pool géré qui s’intercale entre l’application et le cluster, multiplexe les connexions et gère le basculement de manière transparente. Il ajoute aussi 1–3ms par requête et une couche d’IAM qui se mal configure environ une fois sur deux.

Ce qui se passe

Un modèle de connexion direct application-vers-Postgres suppose que l’application maintient un petit pool, garde les connexions le temps d’une requête et les libère proprement. Cette hypothèse s’effondre dans trois topologies courantes :

  • Fan-out Lambda ou Fargate. Des centaines de workers à courte durée de vie qui ouvrent chacun une connexion fraîche. Postgres passe plus de temps à forker des backends qu’à exécuter des requêtes.
  • Connect/disconnect par requête. PHP-FPM, certaines configurations Ruby, tout ce qui n’a pas de connexions persistantes. Chaque requête paie le coût du handshake TCP + TLS + auth.
  • Troupeau frénétique au basculement. Quand le writer bascule, toutes les instances applicatives se reconnectent en même temps. Le nouveau writer accepte la tempête de connexions et atteint immédiatement max_connections alors qu’il chauffe encore son cache. L’application voit des erreurs FATAL pendant 30–60 secondes après le basculement supposément complété.

RDS Proxy règle les trois. Il maintient un pool chaud de connexions backend vers la base, accepte jusqu’à MaxConnectionsPercent du max_connections du cluster côté client, et les multiplexe sur un pool plus restreint. Pendant un basculement, RDS Proxy maintient la connexion client ouverte, met brièvement en file d’attente les transactions en cours et reprend contre le nouveau writer dès qu’il est joignable — l’application voit une pause de 5–15 secondes au lieu d’une tempête FATAL.

Le coût est réel. Chaque requête transite par Proxy, ce qui ajoute de la latence — typiquement 1–3ms en intra-région, davantage si l’application traverse une AZ pour atteindre Proxy. Pour des charges où la requête typique fait elle-même 0,5ms, cette surcharge est significative. Pour des charges où la requête typique fait 20ms, elle est invisible.

L’autre coût est opérationnel. RDS Proxy utilise l’auth IAM ou la rotation Secrets Manager, ce qui veut dire une nouvelle couche de trust policy à déboguer. Environ la moitié des onboardings Proxy que nous faisons rencontrent une lacune de permission IAM Secrets Manager avant la deuxième semaine.

Quand Proxy est la bonne réponse :

  • Workers Lambda ou serverless qui frappent Postgres ou MySQL sur RDS / Aurora.
  • Applications qui ne peuvent pas exécuter un pgbouncer local (runtimes gérés, Fargate sans pattern sidecar).
  • Charges write-heavy avec basculements fréquents (par exemple Multi-AZ sur Postgres avec patches d’engine hebdomadaires).
  • Charges où le comportement de connexion de l’application est en rafales et difficile à contrôler.

Quand Proxy est la mauvaise réponse :

  • Requêtes sub-milliseconde critiques où 2ms de surcharge de pool représente 30% d’augmentation de latence.
  • Patterns de connexion monolocataires avec un nombre de connexions stable bien en deçà de max_connections — ajouter un pool n’achète rien.
  • Applications qui dépendent de fonctionnalités épinglées à la session côté Postgres (SET LOCAL, advisory locks, prepared statements créés au niveau session) et qui ne tolèrent pas le comportement d’épinglage de session de Proxy.

Comment vérifier

Confirmez d’abord le problème. La vue pg_stat_activity vous dit à quoi ressemble réellement votre pattern de connexion :

SELECT state, count(*), 
       max(now() - state_change) AS max_age
FROM pg_stat_activity
WHERE datname = 'production'
GROUP BY state
ORDER BY count(*) DESC;

Si le compte idle est grand par rapport à active, l’application garde des connexions dont elle n’a pas besoin — corrigez d’abord le pool applicatif. Si le compte idle in transaction est non-nul et croît, l’application fuit des transactions — corrigez d’abord cela. RDS Proxy ne sauvera pas une charge qui fuit des transactions ; Proxy s’épingle sur le même backend pendant la durée d’une session épinglée et la fuite remonte simplement d’une couche.

Vérifiez le nombre de connexions dans le temps :

aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions Name=DBInstanceIdentifier,Value=prod-db-1 \
  --start-time $(date -u -v-7d +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 60 \
  --statistics Maximum \
  --query 'Datapoints[].Maximum' \
  --output text | sort -n | tail -1

Si le maximum sur 1 minute approche max_connections, vous avez soit un problème de pool, soit un problème de tuning. S’il est en rafales (200 à 800 en 30 secondes), vous avez un problème de troupeau et Proxy convient.

Regardez les basculements récents dans le journal d’événements RDS :

aws rds describe-events \
  --source-identifier prod-db-1 \
  --source-type db-instance \
  --duration 10080 \
  --query 'Events[?contains(Message, `failover`)].[Date,Message]' \
  --output table

Si les basculements du dernier trimestre ont produit des incidents applicatifs (vérifiez votre backlog d’alertes), la valeur de transparence de basculement de Proxy est réelle pour vous.

La correction

Le setup Proxy de bout en bout. On suppose Postgres sur RDS, auth IAM, et Secrets Manager pour le vrai mot de passe de la base.

Créez le secret Secrets Manager avec les credentials de la base :

aws secretsmanager create-secret \
  --name rds/prod-db-1/proxy-creds \
  --secret-string '{"username":"app","password":"REDACTED"}'

Créez le rôle IAM Proxy avec la trust policy pour le service RDS :

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "rds.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}

Attachez une policy qui permet au rôle de lire le secret :

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret"
    ],
    "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:rds/prod-db-1/proxy-creds-*"
  }]
}

Créez le Proxy :

aws rds create-db-proxy \
  --db-proxy-name prod-db-1-proxy \
  --engine-family POSTGRESQL \
  --auth '[{"AuthScheme":"SECRETS","SecretArn":"arn:aws:secretsmanager:us-east-1:123456789012:secret:rds/prod-db-1/proxy-creds-XXXXX","IAMAuth":"REQUIRED"}]' \
  --role-arn arn:aws:iam::123456789012:role/rds-proxy-prod-db-1 \
  --vpc-subnet-ids subnet-aaa subnet-bbb subnet-ccc \
  --vpc-security-group-ids sg-proxy \
  --require-tls

Enregistrez la cible :

aws rds register-db-proxy-targets \
  --db-proxy-name prod-db-1-proxy \
  --db-instance-identifiers prod-db-1

Le rôle IAM de l’application a besoin de rds-db:connect sur la ressource Proxy :

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "rds-db:connect",
    "Resource": "arn:aws:rds-db:us-east-1:123456789012:dbuser:prx-XXXXX/app"
  }]
}

Le connection string côté application passe du endpoint RDS au endpoint Proxy, et le mot de passe passe du secret statique à un token IAM à courte durée généré au moment de la connexion :

TOKEN=$(aws rds generate-db-auth-token \
  --hostname prod-db-1-proxy.proxy-XXXXX.us-east-1.rds.amazonaws.com \
  --port 5432 \
  --region us-east-1 \
  --username app)

PGPASSWORD="$TOKEN" psql \
  "host=prod-db-1-proxy.proxy-XXXXX.us-east-1.rds.amazonaws.com \
   port=5432 dbname=production user=app sslmode=require"

Trois choses qui mordent à la deuxième journée :

L’épinglage de session. Proxy opère en deux modes par transaction — multiplexé (une connexion partagée entre requêtes) et épinglé (une connexion gardée pour un client jusqu’à sa déconnexion). Tout ce qui est scoped à la session épingle : SET sans LOCAL, LISTEN/NOTIFY, tables temporaires, advisory locks, curseurs WITH HOLD. Une charge qui épingle à chaque requête obtient zéro bénéfice de pooling. La métrique CloudWatch Proxy ClientConnectionsCurrentReused versus ClientConnections vous dit quelle fraction du travail multiplexe.

Comportement au basculement avec longues transactions. Proxy maintient la connexion client pendant le basculement, mais seulement pour les connexions à l’intérieur d’une transaction qui n’a encore rien fait, ou en dehors d’une transaction. Une transaction longue au moment du basculement est abandonnée — l’application voit une erreur. Gardez les transactions courtes ; n’exécutez pas une update batch de 30 secondes dans une transaction en production.

Le flag RequireTLS. Obligatoire pour l’auth IAM. Si le connection string applicatif omet sslmode=require, la connexion échoue avec une erreur vague. Défaut à sslmode=require partout ; ne laissez pas le connection string dériver.

Après la bascule, la métrique à surveiller est ClientConnections versus DatabaseConnections sur la ressource Proxy. Un ratio sain est 5:1 ou mieux — l’application ouvre beaucoup de connexions vers le Proxy, et le Proxy les multiplexe sur moins de connexions backend. Un ratio proche de 1:1 veut dire que Proxy ne fait pas de pooling et que la charge est essentiellement épinglée. C’est le signal d’aller enquêter sur le comportement de session de l’application avant de déclarer Proxy une victoire.


Stack Harbor conçoit et opère les topologies RDS / Aurora en production dans le cadre de Gestion AWS et de la couche Environnements infogérés. Si votre posture base de données se résume à « case Multi-AZ cochée + espoir », commencez par une consultation d’infrastructure.