La plupart des débats sur les bases de données commencent comme de la philosophie et finissent comme un rapport d’incident. Votre application va bien — jusqu’à ce que vous ayez besoin de deux choses à la fois : la sémantique Postgres et la disponibilité globale. Là, la facture arrive. Parfois c’est une facture de performance. Parfois c’est une facture d’exploitation. Parfois c’est un « pourquoi la page de paiement plante seulement à São Paulo ? ».
Si vous hésitez entre « un Postgres unique, bien géré » et « Postgres distribué, basé sur Raft, partout », voici un point de vue ancré en production : ce qui casse, ce qui coûte cher, ce qui est brillant et ce qui est excessif.
Ce que vous choisissez vraiment (ce ne sont pas des fonctionnalités)
Sur le papier, PostgreSQL et YSQL de YugabyteDB peuvent tous deux ressembler à « SQL avec tables, index, transactions, JSON et un planificateur de requêtes ». Ce n’est pas la décision. La décision est :
- Souhaitez-vous monter en charge principalement en améliorant une instance unique (machine plus grosse, disques plus rapides, requêtes optimisées, réplicas de lecture, partitionnement), ou
- Souhaitez-vous monter en charge en distribuant le stockage et le consensus du SGBD entre nœuds, en acceptant une complexité de base plus élevée pour obtenir tolérance aux pannes et croissance horizontale.
PostgreSQL est la référence pour « simple qui peut être sophistiqué ». Il vous permet d’exploiter un système de production propre où le travail de performance concerne surtout les index, le vacuum, la mémoire et éviter les comportements idiots comme des pics de connexions.
YugabyteDB est une base de données SQL distribuée avec une API compatible Postgres (YSQL) construite sur une couche de stockage distribuée (DocDB) et le consensus Raft. Elle vise à être la solution quand vos exigences ressemblent à une lettre de rançon : « doit survivre à la perte de nœud, doit scaler les écritures, doit fonctionner sur plusieurs zones/régions, doit être Postgres-ish, doit rester ACID. »
Le tranchant : une base de données distribuée change la physique de votre appli. Beaucoup de requêtes deviennent « un peu en réseau ». Certaines deviennent « beaucoup en réseau ». Vous commencerez aussi à vous soucier du placement, des leaders, des splits de tablets, et de la différence entre transactions locales et globales. Vous pouvez garder votre SQL, mais vous échangez un type d’expertise contre un autre.
Règle empirique que je défendrai : Si votre plus grande douleur est « on ne parvient pas à maintenir Postgres en ligne » ou « on atteint constamment le CPU sur un seul writer », ne commencez pas par du SQL distribué. Commencez par du Postgres banal bien fait. Si votre plus grande douleur est « notre activité exige de l’active-active entre zones/régions avec un faible RPO et pas de goulot d’unicité d’écriture », YugabyteDB (ou similaire) devient une option réelle.
Bref historique et faits qui comptent en production
Un peu de contexte aide à couper à travers le marketing. Voici des faits concrets qui changent la façon dont vous exploitez ces systèmes :
- Les racines de PostgreSQL remontent à POSTGRES (1986), bien avant que « cloud native » ne soit un concept. Le projet PostgreSQL moderne publie des versions fiables depuis des décennies, et la culture valorise la correction plutôt que la nouveauté.
- MVCC dans Postgres (contrôle de concurrence multi-version) explique pourquoi les lecteurs ne bloquent pas les écrivains (en grande partie). C’est aussi la raison de l’existence du vacuum et pourquoi les « tuples morts » sont une vraie préoccupation opérationnelle.
- La réplication en streaming (basée sur WAL) est devenue courante à l’ère Postgres 9.x, permettant des standbys chauds pratiques. Cela a façonné le modèle HA par défaut : un primary, des replicas, orchestration de basculement.
- La réplication physique de Postgres se fait au niveau de l’instance, tout ou rien. C’est excellent pour un basculement de cluster entier, pas pour sharder la charge d’écriture entre plusieurs primaires sans un partitionnement applicatif soigné.
- Raft est devenu le moteur de consensus des systèmes distribués modernes dans les années 2010. Il est populaire parce qu’il est plus simple à raisonner que Paxos, pas parce qu’il est gratuit. Chaque écriture paie un tribut de coordination.
- YugabyteDB utilise un moteur de stockage distribué (DocDB) inspiré des designs LSM-tree. Cela signifie compactions, SSTables et des comportements IO différents du stockage heap de Postgres.
- « Distributed SQL » est un deuxième essai sur une vieille idée. Les bases de données shared-nothing et les transactions distribuées ne sont pas nouvelles ; ce qui est nouveau, c’est une meilleure automatisation, un meilleur réseau et la volonté de dépenser plus de CPU pour simplifier le design applicatif.
- Google Spanner (2012) a normalisé l’idée d’ACID distribué globalement avec une forte cohérence, au prix d’un ingénierie temporelle/consensus soignée. Beaucoup de systèmes (et la catégorie marché) ont emprunté cette ambition, même s’ils diffèrent par l’implémentation.
- Les extensions Postgres sont une superpuissance (PostGIS, pg_stat_statements, etc.). Dans les systèmes « compatibles Postgres », le support des extensions est souvent partiel, et cela change ce que vous pouvez instrumenter et comment vous résolvez des problèmes.
Blague #1 : Une base de données distribuée, c’est juste une base de données normale qui a découvert que la téléportation est peu fiable et maintenant en garde rancune.
Différences d’architecture qui changent votre vie de pager
PostgreSQL : une réalité primaire, plus des replicas
En production classique Postgres, vous avez un primaire écrivable. Les replicas suivent via WAL. Le basculement promeut un replica. Les lectures peuvent être déportées, mais les écritures sont mono-nœud.
Ce n’est pas une faiblesse ; c’est un design qui garde le noyau plus simple et prévisible. Avec de bons disques, un schéma sensé et du pooling de connexions, Postgres peut faire beaucoup. Et les modes de défaillance sont bien compris : saturation disque, latence de réplication, bloat, mauvais comportement d’autovacuum, tempêtes de checkpoint, requêtes problématiques et pics de connexions.
Le gain opérationnel est que vous pouvez généralement répondre à « où est la vérité ? » par « sur le primaire ». Cela semble anecdotique jusqu’à ce que vous déboguiez un heisenbug à 3 h du matin.
YugabyteDB : stockage distribué, leaders de tablet et consensus partout
YugabyteDB scinde les données en tablets (shards). Chaque tablet est répliquée (typiquement RF=3). Une tablet a un leader et des followers. Les écritures passent par Raft pour être commit. Les lectures peuvent être servies par les leaders ou les followers selon les réglages de cohérence et les chemins de requête.
Le résultat : vous pouvez perdre des nœuds et continuer à servir du trafic. Vous pouvez augmenter le stockage et le débit d’écriture en ajoutant des nœuds. Mais vous avez aussi introduit :
- Des problèmes de placement des leaders (la latence dépend d’où sont les leaders).
- Des problèmes de tablets chauds (un shard est bombardé et devient le goulot).
- Du travail en arrière-plan (compactions, fractionnement de tablets, rééquilibrage) qui concurrence le trafic au premier plan.
- Plus de façons d’être « up » mais lent (un quorum existe, mais la latence de queue est moche).
Le modèle opérationnel change. Vous ne gérez plus seulement un processus de base de données ; vous gérez un petit système distribué avec son propre plan de contrôle et ses sémantiques de défaillance.
Différences de moteur de stockage : heap+WAL vs réalité LSM-ish
Postgres stocke les lignes dans des fichiers heap, utilise WAL pour la durabilité, et s’appuie sur le vacuum pour récupérer l’espace parce que MVCC garde les anciennes versions jusqu’à ce qu’il soit sûr de les supprimer.
Le stockage sous-jacent de YugabyteDB se comporte davantage comme un moteur LSM-tree : les écritures sont log-structurées, puis compactées. Cela implique souvent une amplification d’écriture et une dette de compaction qui peuvent devenir des problèmes de performance. Ce n’est pas « pire », c’est différent. Vos disques et votre monitoring doivent refléter cette différence.
Transactions : local vs distribué, là où votre latence va vivre
Les transactions Postgres sont locales à l’instance. Le locking et les règles de visibilité sont tous en processus. Le réseau ne fait pas partie de la latence de commit.
Dans un système SQL distribué, certaines transactions touchent plusieurs tablets. Cela signifie une coordination supplémentaire. Si ces tablets sont dans des zones/régions différentes, le chemin de commit inclut maintenant la latence WAN. La base peut rester correcte. Vos utilisateurs seront toujours agacés.
Implication pratique : Dans YugabyteDB, la modélisation des données et les choix de localité (tablespaces/placement, clés de partitionnement, patterns d’accès) peuvent faire la différence entre « assez rapide » et « pourquoi chaque requête est 80 ms plus lente qu’hier ».
Latence, cohérence et coût : le triangle inévitable
Parlons des contraintes sur lesquelles vous ne pouvez pas négocier :
Latence : la base compte plus que les moyennes
Dans Postgres, un simple lookup indexé peut prendre quelques centaines de microsecondes à quelques millisecondes sur du matériel correct, en supposant des hits dans le cache et un système calme. La latence queue tend à venir des blocages IO, de la contention de locks ou du travail garbage comme un autovacuum en retard.
Dans les systèmes distribués, la latence de base inclut la coordination. Même si la médiane paraît correcte, votre p95/p99 peut augmenter lorsque le leadership bouge, quand un nœud a un petit GC, quand les compactions font monter l’IO, ou quand une transaction multi-tablet frappe le participant le plus lent.
Cohérence : ce que vos responsables produit entendent vs ce que votre code fait
Postgres vous donne une forte cohérence à l’intérieur de l’instance, et la réplication est généralement asynchrone sauf si vous configurez la réplication synchrone. Cela signifie que vous choisissez entre « pas de perte de données sur défaillance du primaire » et « ne pas ajouter de latence à chaque commit ». Vous pouvez activer la synchrone, mais c’est une décision métier déguisée en fichier de config.
YugabyteDB utilise typiquement la réplication synchrone dans le groupe Raft d’une tablet pour les écritures. C’est une posture de durabilité par défaut plus forte, mais la latence dépend du placement des réplicas. Vous pouvez aussi configurer des compromis de cohérence pour les lectures. Votre système vous permettra de le rendre rapide ; il vous permettra aussi de le rendre étrange.
Coût : vous payez la résilience avec des machines et du travail supplémentaires
Un cluster Postgres bien géré peut ressembler à : un primaire costaud, deux replicas, un pooler de connexions et de bonnes sauvegardes. Votre levier d’échelle est « un primaire plus gros » jusqu’à ce que ce ne soit plus possible.
Un cluster YugabyteDB veut plusieurs nœuds même pour de petites charges, parce que la redondance fait partie du design. Avec un facteur de réplication 3, votre coût brut de stockage est environ 3x (plus overhead), et vous avez besoin de marge pour le rééquilibrage et les compactions. Vous achetez de la douceur opérationnelle sous défaillance, mais vous payez en nœuds, en réseau et en complexité.
Blague #2 : La bonne chose avec les algorithmes de consensus, c’est qu’ils garantissent l’accord — en grande partie sur la durée de votre déploiement.
Une idée paraphrasée sur la fiabilité (une citation, honnête)
Idée paraphrasée, attribuée à John Allspaw : « Le comportement réel de votre système est ce qu’il fait sous stress et en cas de panne, pas ce que promet le diagramme d’architecture. »
Compatibilité Postgres : ce que « style Postgres » vous apporte — et ce qu’il n’apporte pas
YSQL de YugabyteDB parle le protocole wire Postgres et vise les sémantiques Postgres. C’est précieux : drivers existants, ORMs et beaucoup de transferts de connaissances SQL.
Mais « compatible » n’est pas « identique », et les systèmes de production vivent dans les différences. Voici où les équipes trébuchent :
- Extensions : Dans Postgres, les extensions résolvent de vrais problèmes (observabilité, types, indexation). Dans YugabyteDB, le support des extensions est plus limité. Si vous dépendez d’une extension spécifique, vous risquez de réécrire au lieu de migrer.
- Planificateur et caractéristiques de performance : Même si une requête est SQL valide, les coûts d’exécution diffèrent. Les scans distribués et les jointures distribuées peuvent se comporter comme « correct en staging, cher en prod » quand la taille des données dépasse un seuil.
- Nuances d’isolation et de verrouillage : L’objectif est un comportement Postgres-like, mais le contrôle de concurrence distribué a des contraintes. Lisez les petits caractères sur les niveaux d’isolation et les retries de transaction.
- Outils opérationnels : Postgres dispose d’un riche écosystème d’outils. YugabyteDB a ses propres outils, métriques et workflows opérationnels. Vous aurez une bonne visibilité, mais vous devrez réapprendre certains réflexes.
Si votre application utilise du SQL standard, des index standards, et que votre plus gros problème est de dépasser un primaire, YugabyteDB peut sembler magique. Si votre appli s’appuie largement sur des fonctionnalités et extensions spécifiques à Postgres, le Postgres distribué peut ressembler à un déménagement où vous découvrez que votre canapé ne passe pas dans le nouvel ascenseur.
Tâches pratiques : commandes, sorties, décisions (12+)
Voici les vérifications réelles que j’exécute (ou demande à quelqu’un d’exécuter) quand je décide si Postgres suffit, si un cluster YugabyteDB est sain, ou pourquoi quelque chose est lent. Chaque élément inclut : commande, ce que signifie la sortie, et quelle décision prendre.
Task 1 — Postgres : confirmer l’état de la réplication et la latence
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select application_name, state, sync_state, write_lag, flush_lag, replay_lag from pg_stat_replication;"
application_name | state | sync_state | write_lag | flush_lag | replay_lag
------------------+-----------+------------+-----------+-----------+------------
pg-replica-1 | streaming | async | 00:00:00 | 00:00:00 | 00:00:01
pg-replica-2 | streaming | async | 00:00:00 | 00:00:00 | 00:00:02
(2 rows)
Signe : Les replicas sont en streaming ; le replay lag est de l’ordre de secondes faibles. Décision : L’échelle de lecture et la posture de basculement paraissent normales. Si le lag augmente, investiguez le réseau/disque sur les replicas ou les requêtes longues retardant le replay.
Task 2 — Postgres : trouver les pires requêtes par temps total
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select queryid, calls, total_time, mean_time, rows, left(query, 80) as q from pg_stat_statements order by total_time desc limit 5;"
queryid | calls | total_time | mean_time | rows | q
----------+-------+------------+-----------+-------+--------------------------------------------------------------------------------
98122311 | 12000 | 955432.12 | 79.61 | 12000 | select * from orders where customer_id = $1 order by created_at desc limit 50
11220091 | 1500 | 440010.55 | 293.34 | 1500 | update inventory set qty = qty - $1 where sku = $2
(2 rows)
Signe : Une requête domine le temps total ; une autre a une latence moyenne élevée. Décision : Optimisez/indexez la première si elle est chaude ; investiguez la contention ou l’index manquant pour l’update. Si c’est déjà « suffisant », vous n’avez peut‑être pas besoin de SQL distribué.
Task 3 — Postgres : vérifier la pression de bloat et la santé du vacuum
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select relname, n_dead_tup, n_live_tup, last_vacuum, last_autovacuum from pg_stat_user_tables order by n_dead_tup desc limit 5;"
relname | n_dead_tup | n_live_tup | last_vacuum | last_autovacuum
----------------+------------+------------+----------------------+----------------------
order_events | 8200000 | 51000000 | | 2025-12-30 09:01:12
sessions | 1900000 | 12000000 | 2025-12-29 03:11:55 | 2025-12-30 08:57:48
(2 rows)
Signe : Les tuples morts sont nombreux ; l’autovacuum fonctionne mais peut être dépassé. Décision : Ajustez les seuils d’autovacuum par table, ajoutez des index utiles au vacuum, et vérifiez les transactions longues. Ne « résolvez » pas cela en migrant la base.
Task 4 — Postgres : voir les verrous actifs et qui bloque
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select blocked.pid as blocked_pid, blocker.pid as blocker_pid, blocked.query as blocked_query, blocker.query as blocker_query from pg_locks blocked join pg_stat_activity blocked_sa on blocked_sa.pid = blocked.pid join pg_locks blocker on blocker.locktype = blocked.locktype and blocker.database is not distinct from blocked.database and blocker.relation is not distinct from blocked.relation and blocker.page is not distinct from blocked.page and blocker.tuple is not distinct from blocked.tuple and blocker.pid != blocked.pid join pg_stat_activity blocker_sa on blocker_sa.pid = blocker.pid where not blocked.granted;"
blocked_pid | blocker_pid | blocked_query | blocker_query
------------+-------------+-----------------------------------+--------------------------------
29411 | 28703 | update inventory set qty = qty-1 | vacuum (verbose, analyze) inventory
(1 row)
Signe : Le vacuum bloque un update (ou l’inverse). Décision : Ajustez les paramètres de coût du vacuum, planifiez des fenêtres de maintenance, ou redesign des patterns de transaction. Si votre appli est sensible aux verrous, le distribué ne supprimera pas miraculeusement la contention.
Task 5 — Postgres : confirmer la pression de checkpoint
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time from pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | checkpoint_write_time | checkpoint_sync_time
------------------+-----------------+-----------------------+----------------------
1021 | 980 | 7123456 | 502311
(1 row)
Signe : De nombreux checkpoints demandés ; les temps d’écriture/sync sont élevés. Décision : Tuning shared_buffers, checkpoint_timeout, checkpoint_completion_target, et assurez-vous que le disque peut encaisser les rafales d’écriture. Encore : corrigez les fondamentaux avant de changer de base de données.
Task 6 — Postgres : vérifier les tempêtes de connexions et le besoin de pooler
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select state, count(*) from pg_stat_activity group by 1 order by 2 desc;"
state | count
-----------+-------
idle | 420
active | 65
idle in transaction | 12
(3 rows)
Signe : Des centaines de sessions idle ; quelques « idle in transaction » (mauvais). Décision : Ajouter/confirmer un pooler de connexions (PgBouncer), corriger la gestion des transactions côté appli, et définir des timeouts pour statements/idle. Beaucoup de plaintes « Postgres est lent » signifient en réalité « trop de connexions ».
Task 7 — YugabyteDB : vérifier l’état général du cluster
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_all_tablet_servers
UUID RPC Host/Port State
3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 yb-tserver-1:9100 ALIVE
c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 yb-tserver-2:9100 ALIVE
a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 yb-tserver-3:9100 ALIVE
(3 rows)
Signe : Tous les tablet servers sont vivants. Décision : Si l’un manque ou est DEAD, arrêtez-vous là et stabilisez le cluster avant l’analyse de performance ; « lent » signifie souvent « réplication dégradée ».
Task 8 — YugabyteDB : inspecter les leaders de tablet et la santé de réplication
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_tablets appdb.orders
Tablet-UUID Range Leader-UUID RF
b8c9d1110c3b4f9ea0d0000000000001 [hash 0x00, 0x55) 3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 3
b8c9d1110c3b4f9ea0d0000000000002 [hash 0x55, 0xaa) c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 3
b8c9d1110c3b4f9ea0d0000000000003 [hash 0xaa, 0xff) a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 3
Signe : Le leadership est équilibré entre les tservers. Décision : Si les leaders se concentrent sur un nœud, attendez-vous à des hotspots et à un CPU inégal. Rééquilibrez les leaders ou revisitez le placement/partitionnement.
Task 9 — YugabyteDB : vérifier les tablets sous-répliquées
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_under_replicated_tablets
Tablet-UUID Table
(0 rows)
Signe : Aucune tablet sous-répliquée. Décision : Si cette commande renvoie des entrées, corrigez la réplication d’abord (disque, réseau, santé des nœuds). La sous-réplication augmente la latence et le risque.
Task 10 — YugabyteDB (YSQL) : trouver les requêtes lentes au niveau SQL
cr0x@server:~$ ysqlsh -h yb-tserver-1 -U yugabyte -d appdb -c "select queryid, calls, total_time, mean_time, left(query, 80) from pg_stat_statements order by mean_time desc limit 5;"
queryid | calls | total_time | mean_time | left
----------+-------+------------+-----------+--------------------------------------------------------------------------------
77190012 | 3300 | 222000.11 | 67.27 | select * from orders where customer_id = $1 order by created_at desc limit 50
(1 row)
Signe : Même histoire que Postgres : les chemins chauds de l’appli sont visibles. Décision : Avant d’accuser le « distribué », vérifiez l’indexation et la forme des requêtes. Les systèmes distribués punissent plus sévèrement les scans complets.
Task 11 — YugabyteDB : valider si une requête est locale ou distribuée (explain)
cr0x@server:~$ ysqlsh -h yb-tserver-1 -U yugabyte -d appdb -c "explain (analyze, dist, costs off) select * from orders where customer_id = 42 order by created_at desc limit 50;"
QUERY PLAN
------------------------------------------------------------------------
Limit (actual time=8.211..8.227 rows=50 loops=1)
-> Index Scan using orders_customer_created_idx on orders (actual time=8.210..8.221 rows=50 loops=1)
Storage Read Requests: 3
Storage Write Requests: 0
Storage Execution Time: 6.902 ms
Planning Time: 0.312 ms
Execution Time: 8.411 ms
(8 rows)
Signe : « Storage Read Requests: 3 » indique plusieurs lectures de tablet. Décision : Si ce nombre augmente avec la taille des données, concevez pour la localité (clé de partition hash, colocation des tables liées, ou évitez les jointures cross-partition).
Task 12 — YugabyteDB : vérifier la pression de compaction/LSM via disque et symptômes IO
cr0x@server:~$ iostat -xz 1 3
avg-cpu: %user %nice %system %iowait %steal %idle
18.22 0.00 6.11 21.40 0.00 54.27
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme0n1 120.0 8200.0 0.0 0.0 4.10 68.3 950.0 64200.0 0.0 0.0 24.80 67.6 9.10 98.5
Signe : Le disque est saturé ; le write await est élevé. Dans un moteur LSM-ish, les compactions peuvent générer des écritures soutenues. Décision : Ajoutez de la capacité IO, tunez les compactions, limitez le travail en arrière-plan, ou ajoutez des nœuds pour répartir la charge. Si votre budget ne permet pas des disques rapides, le stockage distribué sera pénible.
Task 13 — Vérification réseau : latence cross-zone/région
cr0x@server:~$ ping -c 5 yb-tserver-3
PING yb-tserver-3 (10.10.3.21) 56(84) bytes of data.
64 bytes from 10.10.3.21: icmp_seq=1 ttl=62 time=18.9 ms
64 bytes from 10.10.3.21: icmp_seq=2 ttl=62 time=19.4 ms
64 bytes from 10.10.3.21: icmp_seq=3 ttl=62 time=18.7 ms
64 bytes from 10.10.3.21: icmp_seq=4 ttl=62 time=20.1 ms
64 bytes from 10.10.3.21: icmp_seq=5 ttl=62 time=19.2 ms
--- yb-tserver-3 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 18.7/19.2/20.1/0.5 ms
Signe : ~19 ms RTT, confortable pour les humains, coûteux pour le consensus. Décision : Si des quorums Raft traversent ces liens, la latence de commit en héritera. Gardez les quorums dans un domaine à faible latence quand possible ; utilisez des stratégies de géo-partitionnement plutôt que de faire comme si la physique n’existait pas.
Task 14 — Postgres : vérifier que vos sauvegardes sont réelles (pas aspirationnelles)
cr0x@server:~$ pgbackrest --stanza=appdb check
stanza: appdb
status: ok
cipher: none
db (current)
wal archive min/max (0000000100002A9F000000D1/0000000100002A9F000000F4) ok
database size 145.3GB, backup size 52.1GB
Signe : L’archivage WAL fonctionne ; les backups sont cohérents. Décision : Si cela échoue, corrigez les sauvegardes avant toute montée en charge. La fiabilité n’est pas une fonctionnalité de la base ; c’est une habitude opérationnelle.
Task 15 — YugabyteDB : nombre de tablets et vérification du skew
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_tablet_servers
UUID Host Tablets Leaders
3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 yb-tserver-1 620 240
c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 yb-tserver-2 610 210
a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 yb-tserver-3 590 170
Signe : La distribution des tablets et des leaders est inégale. Décision : Inspectez les réglages du rebalancer et le mouvement des leaders. Le skew se traduit souvent directement par du CPU déséquilibré et de la latence queue.
Playbook de diagnostic rapide : trouver le goulot sans lire les feuilles de thé
Quand quelque chose est « lent », vous avez besoin d’un ordre de triage. Pas un runbook de dix pages. Un filtre en trois passes qui rétrécit vite le problème.
Première passe : le système est-il dégradé ou juste occupé ?
- Postgres : vérifiez la santé de la réplication et la saturation disque. Si le primaire est IO-bound, tout le reste est du bruit en aval.
- YugabyteDB : vérifiez la vivacité des nœuds et la sous-réplication. Un cluster peut être « up » alors que des groupes Raft sont malsains, et c’est là que la latence devient incontrôlable.
Deuxième passe : est-ce une requête/chemin ou tout le système ?
- Consultez pg_stat_statements (les deux systèmes au niveau YSQL) pour trouver les principaux consommateurs par temps moyen et total.
- Si une requête domine, vous avez probablement un problème de schéma/index/requête.
- Si tout est lent, vous avez probablement un goulot de ressources (CPU, IO, réseau) ou un comportement systémique (vacuum/compaction, tempêtes de connexions, déséquilibre de leaders).
Troisième passe : la latence vient-elle du calcul, de l’IO ou du réseau ?
- Calcul : CPU élevé, file d’attente de run ; cherchez des leaders chauds (YB) ou de mauvais plans (PG).
- IO : iowait élevé, await élevé, utilisation du périphérique proche de 100%.
- Réseau : RTT cross-zone, perte de paquets, ou un design qui force le quorum à traverser la distance.
Raccourci de décision : Si vous pouvez attribuer la p95 à la RTT réseau et aux chemins de commit distribués, vous avez besoin de changements de localité des données — pas « plus de nœuds ». Si vous l’attribuez à quelques requêtes, vous avez besoin d’index et de meilleurs patterns d’accès — pas d’une nouvelle base.
Trois mini-histoires d’entreprise issues du terrain
1) Incident causé par une mauvaise hypothèse : « les replicas de lecture nous sauveront »
Une SaaS de taille moyenne utilisait Postgres avec deux replicas de lecture. L’application était majoritairement en lecture, l’équipe a donc routé les « endpoints lourds » vers les replicas et s’est sentie satisfaite. Pendant un moment, tout alla bien. Puis ils lancèrent une fonctionnalité qui écrivait une ligne d’audit à chaque lecture (le service conformité était ravi).
L’hypothèse était que l’endpoint était « orienté lecture », il restait donc routé vers les replicas. Mais l’ORM écrivait des événements d’audit dans le même flux de requête. Sur les replicas, ces écritures échouaient, entraînaient des retries et retombaient sur le primaire. Le primaire vit soudain un afflux d’écritures, plus une flambée de churn de connexions due aux retries. Le CPU monta en flèche ; les attentes de locks suivirent ; la p95 devint p999.
L’incident fut bruyant parce que rien n’était « down ». Le primaire était vivant. La réplication était vivante. L’expérience utilisateur était simplement misérable. Les logs applicatifs étaient pleins d’avertissements trompeurs « could not execute statement » qui ressemblaient à des problèmes réseau transitoires.
La correction fut embarrassante de simplicité : routez tous les endpoints qui peuvent écrire — même « une petite écriture » — vers le primaire, et séparez l’ingestion des audits dans une pipeline asynchrone avec buffer. Ils ajoutèrent aussi une garde : côté appli, toute connexion DB marquée en lecture seule refusait les écritures au niveau du driver.
Conclusion : Échelonner les lectures avec des replicas fonctionne, mais « lecture seule » doit être fait respecter. Et si votre roadmap produit transforme discrètement des chemins de lecture en chemins d’écriture, votre architecture doit le détecter.
2) Optimisation qui se retourne contre vous : « augmentons le parallélisme et batchons tout »
Une plateforme e-commerce subit de la lenteur au checkout et décida « d’optimiser la base ». Ils augmentèrent les tailles de batch pour les mises à jour d’inventaire et lancèrent plus de workers en parallèle. L’idée : moins de transactions, plus de débit, moins d’overhead.
Sur Postgres, les transactions plus larges gardaient les verrous plus longtemps. Les deadlocks augmentèrent. L’autovacuum commença à prendre du retard parce que les transactions longues empêchaient le cleanup. Le bloat s’installa ; les index grossirent ; le hit rate du cache chuta ; l’IO augmenta. Le système devint plus lent pour tout le monde, pas seulement pour le checkout.
Plus tard, ils testèrent une option SQL distribuée (YugabyteDB) et appliquèrent l’instinct identique : plus de batches, plus de workers parallèles. Cette fois le retour de bâton fut différent. Des shards chauds se formèrent autour des SKUs populaires. Quelques tablets devinrent leaders pour les clés les plus sollicitées, et ces leaders furent martelés. Les compactions montèrent sur les nœuds chauds. La latence devint en piques d’une manière à laquelle l’équipe n’était pas habituée : « généralement bien, parfois atrocement mal ».
Ils se rétablirent en faisant l’inverse de ce qui semblait « efficace » : transactions plus petites, distribution plus fine des clés, et contrôles de contention par SKU. Ils introduisirent aussi l’idempotence et des politiques de retry raisonnables parce que les transactions distribuées demanderont des retries en cas de conflits.
Conclusion : « Batcher plus » n’est pas un truc universel de performance. Cela allonge la durée des verrous dans Postgres et intensifie les hotspots dans les systèmes distribués. Parfois l’optimisation déplace juste la douleur vers une forme plus coûteuse.
3) Pratique ennuyeuse mais correcte qui a sauvé la mise : restaurations répétées
Une entreprise proche des paiements utilisait Postgres pour les données du grand livre et testait YugabyteDB pour un service de profils utilisateurs globalement disponible. Ils n’étaient pas glamour sur les opérations. Ils étaient stricts.
Chaque mois, ils réalisaient un drill de restauration. Pas théorique. Un vrai « restaurer dans un nouvel environnement et exécuter une suite de vérification ». Ils validaient la continuité WAL, vérifiaient les comptes de lignes, et exécutaient un ensemble d’invariants : les soldes s’additionnent, les clés étrangères correspondent, les écritures récentes sont présentes.
Un jour, un bug du sous-système de stockage corrompit un sous-ensemble de blocs sur un replica. Le primaire allait toujours bien, mais le candidat au basculement était désormais douteux. Parce qu’ils s’entraînaient, l’équipe ne supposa pas. Ils retirèrent immédiatement le replica suspect de la promotion et restaurèrent un replica propre depuis la sauvegarde tout en gardant le primaire stable.
Deux mois plus tard, lorsqu’un incident séparé requit la promotion d’un replica pendant une fenêtre de maintenance ratée, ils surent exactement quels nœuds étaient sûrs et combien de temps prendrait la reconstruction. Personne n’improvisa sur les données de production. Personne ne fit preuve de créativité avec rm -rf.
Conclusion : Pratiquez les restaurations. C’est peu sexy, cela prend du temps, et cela évite le genre de panne « on a perdu les données et aussi notre dignité » qui coûte des carrières.
Erreurs courantes : symptôme → cause racine → correction
1) Symbole : pics de latence p95 Postgres toutes les quelques minutes
Cause racine : Rafales de checkpoints et saturation IO (souvent combinées à un checkpoint_timeout trop petit et un débit disque insuffisant).
Correction : Augmentez checkpoint_timeout, définissez checkpoint_completion_target, assurez-vous que WAL et données sont sur du stockage rapide, et surveillez que l’OS ne swappe pas.
2) Symbole : les tables Postgres continuent de grossir, la performance se dégrade en quelques jours
Cause racine : Le vacuum ne peut pas suivre (seuils autovacuum trop élevés, transactions longues, ou maintenance_work_mem insuffisant).
Correction : Tuner autovacuum par table chaude, raccourcir la durée des transactions, ajouter des index qui aident le vacuum, et surveiller n_dead_tup.
3) Symbole : « Nous avons ajouté des replicas de lecture mais les écritures sont plus lentes »
Cause racine : Explosion de connexions et mauvaise configuration du pool ; les replicas n’ont pas réduit le travail du primaire parce que l’appli touche encore le primaire pour des écritures et beaucoup de lectures.
Correction : Utilisez un pooler (PgBouncer), réduisez max connections, et déplacez les charges purement en lecture. Validez en mesurant CPU/IO du primaire avant/après.
4) Symbole : YugabyteDB est « up » mais la latence est terrible après perte d’un nœud
Cause racine : Tablets sous-répliquées ou re-réplication en cours ; les groupes Raft travaillent plus, et les leaders peuvent avoir bougé.
Correction : Vérifiez les tablets sous-répliquées, stabilisez disques/réseau, laissez le rééquilibrage se terminer, et évitez les gros changements de schéma pendant la récupération.
5) Symbole : un nœud YugabyteDB a un CPU élevé et les autres sont au repos
Cause racine : Concentration de leaders ou tablets hotspot due à des patterns d’accès clés biaisés.
Correction : Rééquilibrez les leaders, augmentez le nombre de tablets si nécessaire, et redesign des clés de partition pour répartir les écritures. Envisagez la colocation ou le geo-partitionnement pour la localité.
6) Symbole : écritures multi-région YugabyteDB « aléatoirement lentes »
Cause racine : Le quorum s’étend sur des liens à haute latence ; le chemin de commit inclut la RTT WAN, et le placement du leader peut être sous-optimal.
Correction : Gardez les quorums Raft dans une région/zone à faible latence quand possible, utilisez les lectures follower ou le geo-partitionnement pour les lectures, et fixez les attentes : les écritures globales fortes coûtent de la latence.
7) Symbole : l’application voit des retries de transaction fréquents dans YugabyteDB
Cause racine : Clés très contentieuses, lignes chaudes, ou transactions longues augmentant la probabilité de conflits.
Correction : Réduisez la contention (shard counters, évitez une ligne de séquence globale), raccourcissez les transactions, implémentez un retry avec jitter, et assurez l’idempotence.
8) Symbole : « Nous avons migré et maintenant les requêtes analytiques sont pires »
Cause racine : Les scans et jointures distribués sont coûteux ; la localité des données et l’indexation ne sont pas alignées sur les patterns analytiques.
Correction : Séparez l’OLTP de l’analytics, utilisez des vues matérialisées/ETL, ou gardez l’analytics sur Postgres/entreposage. Le OLTP distribué n’est pas un moteur d’analytics gratuit.
Listes de contrôle / plan étape par étape
Étape par étape : décider si Postgres suffit
- Mesurez, ne vibrez pas : activez pg_stat_statements et capturez 7–14 jours de forme de charge (top queries par temps total/moyen, nombre d’appels).
- Corrigez l’évident : index manquants, mauvais patterns ORM, requêtes N+1, « idle in transaction », et mauvaise config du pool de connexions.
- Maîtrisez le vacuum : tuning autovacuum par table pour les tables à churn élevé ; confirmez que les transactions longues ne bloquent pas le cleanup.
- Confirmez la marge disque : iostat et utilisation du système de fichiers ; vérifiez la pression de checkpoint et la saturation d’écriture WAL.
- Mettez en place une HA de façon ennuyeuse : au moins un replica, basculement automatisé, sauvegardes avec drills de restauration, et un pooler.
- Échelonnez les lectures correctement : replicas pour lecture seule, caching quand c’est sûr, et réduction des allers-retours.
- Échelonnez les écritures honnêtement : si un primaire devient la limite, évaluez le partitionnement/sharding côté appli ou considérez le SQL distribué.
Étape par étape : décider si YugabyteDB est justifié
- Écrivez les non-négociables : « survivre à une perte de zone », « active-active », « montée en charge des écritures », « RPO/RTO ». Si vous ne pouvez pas les formaliser, vous magasinez pour une montée d’adrénaline.
- Modélisez le budget de latence : quelle p95 vous visez, et quelle RTT existe entre les emplacements ? Si votre chemin de commit traverse 20–50 ms RTT, vous le ressentirez.
- Prototypez le chemin chaud : pas un benchmark générique. Vos 5 requêtes principales réelles, vos formes de transaction réelles, vos index réels.
- Concevez pour la localité : choisissez des clés de partition et du placement pour que la plupart des transactions restent locales. Traitez les transactions distribuées cross-region comme un cas spécial.
- Planifiez les retries : implémentez l’idempotence et la gestion des retries côté appli. Les systèmes distribués ne demandent pas ; ils imposent.
- Prévoyez l’ops : monitoring pour le skew des leaders, les splits de tablets, les compactions et la santé des nœuds. Attribuez des responsabilités ; « le fournisseur s’en occupera » n’est pas un plan on-call.
Checklist de migration (si nécessaire)
- Inventoriez les fonctionnalités Postgres utilisées : extensions, types personnalisés, triggers, réplication logique, procédures stockées.
- Identifiez les écarts de compatibilité tôt ; réécrivez les parties risquées en premier.
- Mettez en place des écritures duales ou CDC en staging ; validez la correction avec des invariants.
- Réalisez des tests de charge incluant la panne : tuez un nœud, ajoutez de la latence, remplissez les disques.
- Définissez des critères de rollback et répétez-les. Si le rollback est « on verra », vous ne vous en sortirez pas.
FAQ
1) YugabyteDB est‑il « juste Postgres mais distribué » ?
Non. YSQL vise la compatibilité Postgres au niveau SQL et protocole, mais le modèle de stockage et de réplication est différent. Cela change les performances et les modes de défaillance.
2) Puis‑je obtenir de l’active‑active avec Postgres ?
Pas dans le sens simple « multi-writer avec forte cohérence ». Vous pouvez faire de la réplication logique, des patterns multi-primary, ou du sharding côté appli, mais chacun a des bords tranchants et de la gestion de conflits. Pour de véritables écritures distribuées sans sharding applicatif, vous entrez dans le territoire du SQL distribué.
3) YugabyteDB sera‑t‑il automatiquement plus rapide que Postgres ?
Non. Pour beaucoup de charges OLTP qui tiennent sur un bon primaire Postgres, Postgres sera plus rapide et plus simple. YugabyteDB vous achète de la résilience et une montée en charge horizontale, pas une réduction de latence gratuite.
4) Quelle est la raison la plus courante d’échec des migrations vers YugabyteDB ?
Supposer que « compatible Postgres » signifie « remplacement plug-and-play », puis découvrir qu’une extension, un pattern de requête ou une attente opérationnelle ne se traduit pas proprement. La compatibilité est réelle, mais pas magique.
5) Quand le SQL distribué est‑il réellement le bon choix ?
Quand vos exigences métier demandent une haute disponibilité à travers des domaines de panne, l’échelle d’écriture au‑delà d’un nœud, et que vous ne pouvez ou ne voulez pas sharder dans l’application. Aussi quand le coût d’une indisponibilité dépasse le coût des nœuds supplémentaires.
6) Comment les retries changent‑ils la conception applicative dans les systèmes distribués ?
Vous avez besoin d’idempotence pour les opérations d’écriture et d’une logique de retry structurée avec jitter et tentatives bornées. Sinon, les retries transforment des conflits transitoires en essaims fracassants.
7) Multi‑région signifie‑t‑il toujours « des utilisateurs partout avec une faible latence » ?
Non. Multi‑région peut signifier « disponible partout », pas « rapide partout ». Les écritures fortement cohérentes à travers les régions payent la latence WAN. Pour obtenir une faible latence partout, vous avez généralement besoin de stratégies de localité et parfois d’une cohérence relaxée pour les lectures.
8) Puis‑je utiliser YugabyteDB pour l’analytics aussi ?
Vous pouvez exécuter des requêtes analytiques, mais les moteurs OLTP distribués rendent souvent les gros scans et jointures coûteux. Beaucoup d’équipes gardent l’OLTP dans Postgres ou YugabyteDB et déplacent l’analytics vers un système dédié.
9) Que devrais‑je monitorer différemment entre eux ?
Postgres : progression du vacuum, bloat, verrous, checkpoints, latence de réplication, nombre de connexions. YugabyteDB : santé des tablets, distribution des leaders, sous-réplication, dette de compaction, rééquilibrage et latence inter-nœuds.
10) Si je suis petit aujourd’hui, dois‑je « préparer l’avenir » avec YugabyteDB ?
Généralement non. Faites d’abord de Postgres une plateforme ennuyeusement excellente : schéma propre, discipline des requêtes et hygiène opérationnelle. Les migrations sont toujours plus difficiles que prévu.
Prochaines étapes pratiques
Si vous voulez une décision qui tient en production, faites ceci dans l’ordre :
- Rendez votre Postgres actuel banalement excellent : pg_stat_statements, pooling de connexions, tuning du vacuum, timeouts raisonnables, sauvegardes vérifiées, drills de restauration.
- Quantifiez le mur de scalabilité : est‑ce le CPU, l’IO, la contention de locks ou le débit d’un writer unique ? Apportez des graphiques, pas des opinions.
- Formulez l’exigence de disponibilité en termes opérationnels : quels domaines de panne devez‑vous survivre, quels RPO/RTO, et quel budget de latence côté utilisateur.
- Si vous avez vraiment besoin d’écritures distribuées et de résilience multi‑domaines : prototypez YugabyteDB avec votre charge réelle, mesurez la latence queue, et pratiquez la panne (perte de nœud, pression disque, injection de latence).
- Choisissez ce que vous pouvez exploiter : la meilleure base est celle que votre équipe peut garder rapide, correcte et calme à 2 h du matin.
Postgres reste le choix par défaut parce qu’il est prévisible et profondément compris. YugabyteDB est séduisant quand l’énoncé du problème est véritablement distribué. Si vous n’êtes pas sûr du problème que vous avez, vous avez probablement un problème Postgres.