data / sql
Sharding : architecture et implémentation
Explication
Ce que vous allez apprendre
- Distinguer clairement le sharding (plusieurs serveurs) du partitionnement (un seul serveur)
- Choisir une clé de sharding pertinente pour garder ensemble les données jointes
- Comprendre pourquoi un simple modulo casse tout lors d'un resharding
- Découvrir le hachage cohérent comme solution à ce problème
- Connaître les deux approches de routage :
postgres_fdwet sharding applicatif
Dans quel contexte ?
Une plateforme SaaS multi-tenant atteint les limites physiques d'un seul serveur PostgreSQL : plus de CPU, plus de disque à ajouter sur cette machine. Il faut répartir les données de la table clients sur plusieurs serveurs distincts. Le choix de la clé de sharding (souvent client_id) devient alors une décision structurante à ne pas prendre à la légère, comme le détaille cette leçon.
D'abord, la limite du partitionnement déjà vu
Le partitionnement (leçon 19) découpe une table en tiroirs, mais tous ces tiroirs restent rangés dans le MÊME meuble, sur la même machine physique. Un jour, même la machine la plus puissante atteint ses limites de stockage ou de calcul.
La solution : distribuer sur plusieurs serveurs
Le sharding répond à cette limite en répartissant les données ENTRE plusieurs serveurs indépendants, chacun hébergeant une portion ("un shard"). Ce n'est plus un seul meuble avec plusieurs tiroirs : ce sont plusieurs meubles, dans des pièces différentes.
Étape 1 : la décision la plus importante, la clé de sharding
Il faut décider quel critère détermine sur quel serveur atterrit chaque ligne — souvent un hachage de l'identifiant client. Ce choix est structurant : si des données fréquemment jointes (une commande ET ses lignes) se retrouvent sur des shards différents, chaque jointure devient extrêmement coûteuse, car un JOIN natif ne peut pas franchir la frontière entre deux serveurs.
Il reste un problème : ajouter un serveur casse tout
Avec un simple modulo (hash(id) % nb_shards), ajouter un seul nouveau serveur change le résultat du modulo pour presque toutes les clés existantes. Il faudrait alors redéplacer la quasi-totalité des données, ce qui n'est pas praticable en production.
| Approche | Rôle | Limite |
|---|---|---|
| Partitionnement (leçon 19) | Découpe UNE table sur UN serveur | Ne résout pas la saturation physique |
| Sharding | Répartit les données sur PLUSIEURS serveurs | Pas de JOIN natif entre shards |
Modulo simple (hash(id) % n) | Répartition initiale | Casse tout lors d'un resharding |
| Hachage cohérent | Répartition stable | Ne redéplace qu'une petite fraction des clés |
Piège fréquent
Choisir une clé de sharding qui sépare des données fréquemment jointes (par exemple sharder commandes par id de commande et lignes_commande par id de ligne) rend chaque jointure quasiment impossible à faire efficacement, car un JOIN ne peut pas franchir la frontière entre deux serveurs.
Étape 2 : le hachage cohérent limite les dégâts
Le hachage cohérent résout précisément ce problème : lors d'un changement du nombre de shards, seule une petite fraction des clés doit être redéplacée, au lieu de presque toutes. C'est ce qui rend le resharding supportable en production.
Étape 3 : comment router une requête vers le bon shard
Deux approches existent : un routeur SQL (par exemple postgres_fdw, qui fédère plusieurs bases distantes) ou un sharding applicatif, où le client calcule lui-même le shard cible avant même d'ouvrir la connexion.
Piège à retenir
Le sharding introduit une vraie complexité (pas de JOIN inter-shards, transactions distribuées coûteuses avec le 2PC) : ce n'est pas un réflexe à appliquer par anticipation, mais une solution de dernier recours, réservée aux volumes qui dépassent réellement un serveur unique.
Vers la suite
Après avoir vu comment distribuer les données brutes, la prochaine leçon revient à un format de données bien précis : le JSONB, avec des outils encore plus avancés que ceux vus en leçon 20.
Commandes & code
Sharding : architecture et implémentation
Le sharding distribue une table sur PLUSIEURS serveurs, contrairement au partitionnement qui reste sur un seul.
-- Approche 1 : Foreign Data Wrapper -- fédère plusieurs bases PostgreSQL distantes
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER shard_1 FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'db-shard-1.interne', dbname 'clients_shard1', port '5432');
CREATE SERVER shard_2 FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'db-shard-2.interne', dbname 'clients_shard2', port '5432');
CREATE USER MAPPING FOR app_user SERVER shard_1
OPTIONS (user 'app_user', password 'secret');
CREATE USER MAPPING FOR app_user SERVER shard_2
OPTIONS (user 'app_user', password 'secret');
-- Table locale partitionnée dont chaque partition pointe vers un shard distant
CREATE TABLE clients (
id BIGINT NOT NULL,
nom VARCHAR(200),
region VARCHAR(50)
) PARTITION BY HASH (id);
CREATE FOREIGN TABLE clients_shard1 PARTITION OF clients
FOR VALUES WITH (MODULUS 2, REMAINDER 0)
SERVER shard_1;
CREATE FOREIGN TABLE clients_shard2 PARTITION OF clients
FOR VALUES WITH (MODULUS 2, REMAINDER 1)
SERVER shard_2;
-- Une requête sur "id" connu est routée UNIQUEMENT vers le bon shard (partition pruning distant)
SELECT * FROM clients WHERE id = 42;
-- Approche 2 : sharding applicatif -- le client calcule lui-même le shard cible
-- def shard_pour(client_id: int, nb_shards: int) -> int:
-- return hash(client_id) % nb_shards
-- L'application ouvre directement la connexion au bon shard, sans routeur SQL intermédiaire
-- Hachage cohérent (consistent hashing) : limite les clés déplacées lors d'un resharding,
-- contrairement à un simple modulo qui redistribue presque toutes les clés dès qu'on change nb_shards.
-- Principe : chaque shard occupe plusieurs points sur un anneau de hachage ; une clé est assignée
-- au premier point de shard rencontré en tournant sur l'anneau à partir de hash(clé).
-- Table de routage centrale (shard map), généralement mise en cache côté application
CREATE TABLE shard_map (
plage_debut BIGINT NOT NULL,
plage_fin BIGINT NOT NULL,
shard_hote VARCHAR(100) NOT NULL
);
-- Limite fondamentale : pas de JOIN natif entre deux shards différents
-- -> choisir une clé de sharding qui garde ENSEMBLE les données fréquemment jointes
-- (sharder par client_id garde commandes + lignes_commande du même client sur le même shard)
-- Transactions distribuées : PostgreSQL propose le 2PC entre shards, coûteux et rarement utilisé
-- en pratique -- on préfère la cohérence éventuelle gérée côté application
PREPARE TRANSACTION 'transfert_456'; -- phase 1 : chaque shard promet de committer
COMMIT PREPARED 'transfert_456'; -- phase 2 : validation définitiveRésumé
- Le sharding distribue horizontalement ENTRE plusieurs serveurs, contrairement au partitionnement qui reste local à un serveur.
postgres_fdw+ partitions étrangères permettent de router une requête vers le bon shard depuis SQL directement.- Le hachage cohérent limite le nombre de clés à redéplacer lors de l'ajout/retrait d'un shard.
- Choisir la clé de sharding en fonction des données fréquemment jointes ensemble : les JOIN inter-shards sont impossibles ou très coûteux.
- Le 2PC (
PREPARE TRANSACTION) existe pour les transactions distribuées mais reste coûteux : à réserver aux cas critiques.
Exercices pratiques
Mission : revoir une stratégie de sharding qui casse toutes les jointures
Objectif : Diagnostiquer une clé de sharding mal choisie qui sépare des données jointes, et raisonner sur le resharding et les transactions distribuées.
Contexte
La plateforme SaaS a shardé commandes par id de commande et lignes_commande par id de ligne, chacun réparti avec un simple hash(id) % nb_shards. Depuis, chaque page "détail de commande" (qui joint commandes et lignes_commande) est devenue extrêmement lente, et l'équipe infra redoute d'ajouter un troisième shard car elle sait que cela va redéplacer une grande partie des données existantes.