Scaler les bases de données : replicas de lecture, sharding et partitionnement

Scaler les bases de données : replicas de lecture, sharding et partitionnement

Presque toutes les histoires de scaling que j ai vécues finissent à la base de données. Vous pouvez mettre du cache, des files, ajouter une centaine de nœuds applicatifs stateless, et tout ça aide jusqu au moment où une seule base de données primaire est la chose que tous ces nœuds attendent. À ce stade, vous devez scaler la couche de données elle même, et c est un autre genre de problème parce que les données ont du poids. Déplacer du calcul est facile. Déplacer et découper des données sans les perdre ni les corrompre, c est là que vit la vraie ingénierie.

J y pense comme à une échelle. Chaque barreau est plus puissant et plus douloureux que le précédent, et vous ne devriez monter que jusqu où vous en avez réellement besoin.

D abord, épuiser les gains faciles

Avant tout changement d architecture, je m assure que la base ne fait pas simplement du travail inutile. La cause la plus fréquente d une base qui semble trop petite, c est une base à qui il manque des index, qui fait des scans séquentiels sur de grandes tables, ou qui se fait marteler par des requêtes qui auraient dû être cachées. L épuisement du pool de connexions se déguise constamment en problème de scaling. Un schéma bien indexé sur une instance bien dimensionnée encaisse bien plus qu on ne le croit, et je couvre les fondations dans les bonnes pratiques de schéma de base de données. Ne shardez pas une base à qui il manque juste un index.

Les replicas de lecture, le premier vrai levier

La plupart des applications lisent bien plus qu elles n écrivent. Timelines, fiches produit, tableaux de bord, résultats de recherche, tout ça ce sont des lectures. Donc le premier mouvement structurel est presque toujours les replicas de lecture. Vous gardez une primaire qui accepte toutes les écritures, et vous diffusez ses changements vers une ou plusieurs copies replicas qui servent les lectures. Désormais votre capacité de lecture scale avec le nombre de replicas pendant que les écritures restent sur la primaire.

Le piège, c est le retard de réplication. Un replica est une copie toujours un peu en retard, généralement de quelques millisecondes mais parfois de quelques secondes sous charge. Cela crée une classe de bug subtile : un utilisateur écrit quelque chose, est redirigé, la lecture part vers un replica pas encore à jour, et sa propre modification semble avoir disparu. La solution, c est le routage read your writes. Après une écriture, envoyez les lectures de cet utilisateur vers la primaire pendant un court instant, ou pour tout ce où l utilisateur s attend à voir sa propre action immédiatement.

def get_connection(query_type, just_wrote=False):
    if query_type == "write" or just_wrote:
        return primary_pool.get()
    return replica_pool.get()  # round robin sur les replicas

# Apres mise a jour d un profil, lire depuis la primaire brievement
update_profile(user_id, data)
profile = read_profile(user_id, just_wrote=True)

Les replicas vous donnent aussi autre chose que de la capacité. Ils sont un secours à chaud. Si la primaire meurt, vous pouvez promouvoir un replica, ce qui fait des replicas un élément de votre disponibilité et pas seulement de votre performance.

Le partitionnement, découper sensément une grosse table

Les replicas multiplient votre capacité de lecture mais chaque replica détient encore tout le jeu de données, et les écritures vont toujours toutes à une primaire. Quand une seule table atteint des centaines de millions de lignes, la table elle même devient le problème. Les index s alourdissent, le vacuum et la maintenance ralentissent, et les requêtes qui touchent toute la table traînent. Le partitionnement découpe une table logique en plusieurs morceaux physiques tout en gardant une seule primaire.

Le type le plus utile est le partitionnement par plage de temps. La plupart des grandes tables sont en ajout massif et ordonnées dans le temps : événements, logs, commandes, messages. Si vous partitionnez par mois, une requête sur la semaine dernière ne touche qu une seule partition, et supprimer les vieilles données devient un détachement instantané d une partition entière au lieu d un delete massif.

CREATE TABLE events (
    id        BIGSERIAL,
    user_id   BIGINT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL,
    payload   JSONB
) PARTITION BY RANGE (created_at);

CREATE TABLE events_2026_06 PARTITION OF events
    FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');

CREATE TABLE events_2026_07 PARTITION OF events
    FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');

Ce qu il faut comprendre, c est que le partitionnement reste un seul serveur de base de données. Il aide pour la taille des tables, la maintenance, et les requêtes qui peuvent élaguer vers une seule partition. Il ne fait rien pour le débit d écriture de la machine dans son ensemble, parce que toutes les partitions vivent sur la même machine.

Le sharding, le dernier barreau et le plus lourd

Quand une seule primaire ne suit plus les écritures quelle que soit la taille de la machine, vous devez répartir les données sur plusieurs serveurs de base de données indépendants. C est le sharding. Chaque shard est sa propre base détenant un sous ensemble des données, et aucune machine ne les a toutes. C est le mouvement qui scale enfin les écritures horizontalement, et c est aussi celui qui vous coûte le plus.

Tout dépend de la clé de sharding, la colonne que vous utilisez pour décider sur quel shard vit une ligne. Choisissez bien et la plupart des requêtes touchent un seul shard. Choisissez mal et vous créez des shards chauds qui prennent tout le trafic pendant que d autres restent inactifs, ou vous forcez les requêtes à s éventer sur tous les shards et à rassembler les résultats, ce qui est lent et fragile.

  • Le sharding par hachage répartit les lignes uniformément en hachant la clé. Excellent pour une distribution uniforme, mauvais pour les requêtes par plage parce que les lignes liées se dispersent partout.
  • Le sharding par plage garde les clés liées ensemble, ce qui est bon pour les scans par plage mais tend à créer des points chauds sur la plage la plus récente.
  • Le sharding par annuaire garde une table de correspondance explicite entre clés et shards. Le plus flexible, il permet de rééquilibrer, mais l annuaire lui même devient quelque chose à scaler et à protéger.

La clé de sharding doit correspondre à la façon dont vous interrogez réellement. Si vous gérez une application multi tenant, sharder par identifiant de tenant est généralement idéal parce que presque chaque requête est déjà cantonnée à un tenant, donc elle atterrit naturellement sur un seul shard. Si vous avez un jour besoin d une requête sans la clé de sharding, vous regardez un scatter gather sur tous les shards, et vous devez concevoir durement pour éviter ça sur les chemins chauds.

Ce que vous abandonnez en shardant

Je veux être franc sur les coûts, parce que le sharding se fait romancer. Les jointures inter shards cessent en pratique d exister. Vous dénormalisez ou vous joignez dans la couche applicative. Les transactions qui couvrent plusieurs shards exigent une machinerie de transactions distribuées lente et complexe, donc vous reconcevez pour garder chaque transaction dans un seul shard. Les identifiants globalement uniques ont besoin d un schéma qui ne dépend pas d une séquence unique, donc vous passez aux UUID ou à un générateur de type snowflake. Rééquilibrer quand un shard se remplit est un vrai projet, pas un changement de config. Et chacun de ces coûts est permanent. Une fois que vous shardez, vous vivez avec.

C est exactement pour ça que le sharding est le dernier barreau. Vous n y montez qu après avoir poussé les replicas, le partitionnement, le cache et les files aussi loin qu ils peuvent aller.

Comment j enchaîne le tout

Le chemin que je suis presque à chaque fois ressemble à ceci. Indexer et régler jusqu à épuiser les gains faciles. Ajouter des replicas de lecture et router les lectures hors de la primaire. Partitionner les tables géantes pour que la maintenance et l élagage restent sains. Utiliser le cache et les files, que je couvre dans les patterns de scalabilite backend, pour soulager à la fois les lectures et les écritures. Et seulement quand la primaire d écriture elle même est le plafond dur, je shard, et je traite le choix de la clé de sharding comme la décision la plus importante de tout l effort.

La leçon d ensemble, c est que scaler une base est une séquence de compromis, pas une mise à niveau unique. Chaque barreau achète de la capacité et facture de la complexité. Les ingénieurs qui se mettent en difficulté sont ceux qui sautent directement au pattern le plus puissant parce qu il sonne impressionnant, et qui passent ensuite deux ans à payer une complexité distribuée qu ils auraient pu différer longtemps avec deux replicas et un bon index.

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *