Vol. 8 · № 02 · Un atelier d'architectureAn architecture workshop

Sharding, ou la donnée qu'on découpe et la clé qu'on ne peut plus changerSharding, or the data you split and the key you can no longer change

Quand la donnée ne tient plus sur une machine, on la découpe en shards répartis sur plusieurs nœuds. La promesse : une capacité sans plafond. Le piège : la clé de partition décide pour toujours quelles requêtes restent rapides — celles qui en sortent se dispersent sur toutes les machines (scatter-gather), et les transactions se brisent. La règle : on ne shard qu'un plafond mesuré, et on choisit la clé d'après ce qu'on lit le plus — car on ne pourra plus la changer sans tout migrer.When data no longer fits on one machine, you split it into shards spread across several nodes. The promise: capacity with no ceiling. The trap: the partition key decides forever which queries stay fast — those that step off it scatter across every machine (scatter-gather), and transactions break. The rule: you shard only a measured ceiling, and choose the key by what you read most — because you can no longer change it without migrating everything.

AudienceAudience
Dev tenté de shard « pour que ça scale », sans avoir mesuré le plafond ni choisi la cléDev tempted to shard 'so it scales', without measuring the ceiling or choosing the key
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026June 2026
≈ 18 min●●●○ScalabilitéShardingPartitionnementDistribuéTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Une machine ne suffit plus. On découpe la donnée — mais le découpage choisit ce qui restera rapide.One machine no longer suffices. You split the data — but the split decides what stays fast.

Le numéro précédent gardait une seule source de vérité et s'en faisait des copies pour lire vite. Mais que faire quand la source elle-même ne tient plus — quand la donnée est trop grosse pour une seule machine, ou les écritures trop nombreuses pour un seul nœud ? Le cache n'aide plus : il accélère la lecture, pas la capacité d'écriture ni le stockage. On grossit d'abord la machine — plus de RAM, plus de disque, plus de CPU : c'est le scaling vertical, simple, et il a un plafond, et un prix qui explose près du plafond. Au-delà, il faut découper la donnée en morceaux — des shards — répartis sur plusieurs machines, chacune ne portant qu'une part du tout : c'est le sharding, le scaling horizontal. La promesse est belle : capacité et débit qui croissent en ajoutant des machines. Le piège est dans le découpage : on choisit une clé de partition, et cette clé décide pour toujours quelles requêtes resteront rapides — et lesquelles devront traverser toutes les machines. Le réflexe complet n'est donc pas « répartis sur N machines », c'est « choisis la clé selon laquelle on lit le plus, car c'est elle qu'on ne pourra plus changer » — et, règle du volume, on n'y entre qu'avec un chiffre : un volume ou un débit mesuré qui dépasse vraiment une seule machine.The previous issue kept a single source of truth and copied it to read fast. But what to do when the source itself no longer fits — when the data is too big for one machine, or the writes too many for a single node? The cache no longer helps: it speeds reads, not write capacity nor storage. You first grow the machine — more RAM, more disk, more CPU: that's vertical scaling, simple, and it has a ceiling, and a price that explodes near the ceiling. Beyond it, you must split the data into pieces — shards — spread across several machines, each carrying only a part of the whole: that's sharding, horizontal scaling. The promise is fine: capacity and throughput that grow by adding machines. The trap is in the split: you choose a partition key, and that key decides forever which queries stay fast — and which must cross every machine. So the full reflex isn't 'spread it over N machines', it's 'choose the key you read by the most, because it's the one you can no longer change' — and, the volume's rule, you only enter with a number: a measured volume or throughput that truly exceeds one machine.

Le code naïf — on répartit par modulo, on lit par autre chose que la cléThe naive code — split by modulo, read by something other than the key
# une seule base sature. le réflexe : « répartis sur N machines ». la moitié du geste seulement.
SHARDS = [db0, db1, db2, db3]
def shard_for(user_id):
    return SHARDS[hash(user_id) % len(SHARDS)]   # 💥 modulo N : ajouter une machine REHACHE tout

def get_orders(user_id):
    return shard_for(user_id).query(             # ✓ requête par user_id : une seule machine, rapide
        "SELECT * FROM orders WHERE user_id = ?", user_id)

def orders_in_region(region):
    # 💥 la région n'est pas la clé de shard → on doit interroger TOUTES les machines
    return merge(s.query("SELECT * FROM orders WHERE region = ?", region) for s in SHARDS)
# un sharding sans clé bien choisie n'est pas du scaling : c'est un éparpillement qu'on paie à chaque requête.
Grossir la machine vs multiplier les machinesGrow the machine vs multiply the machines
scaling verticalvertical scaling
simple · un plafond · prix qui explosesimple · a ceiling · price explodes
⇄
vsvs
scaling horizontalhorizontal scaling
sans plafond · mais distribuéno ceiling · but distributed

Grossir une machine ne change rien à la nature du système : il reste simple, transactionnel, joignable d'une requête. Multiplier les machines change tout : la donnée n'est plus en un seul endroit, donc une requête qui ne suit pas la clé de partition doit demander à plusieurs nœuds et recoller leurs réponses ; et une transaction qui touche deux shards perd l'atomicité qu'une seule base offrait gratuitement. Le sharding n'achète pas de la capacité sans contrepartie : il l'échange contre la perte du « tout au même endroit ». Décider de shard, c'est accepter ce troc — et le minimiser en alignant les requêtes sur la clé.Growing a machine changes nothing about the system's nature: it stays simple, transactional, reachable in one query. Multiplying machines changes everything: the data is no longer in one place, so a query that doesn't follow the partition key must ask several nodes and stitch their answers; and a transaction touching two shards loses the atomicity a single database gave for free. Sharding doesn't buy capacity without a counterpart: it trades it for the loss of 'everything in one place'. Deciding to shard is accepting that trade — and minimizing it by aligning queries on the key.

Le réflexe — clé, routage, requêtes alignéesThe reflex — key, routing, aligned queries

Le sharding se conçoit en trois temps. La clé de partition, d'abord : l'attribut selon lequel on découpe — user_id, tenant_id, zone géographique. Elle doit avoir une forte cardinalité (beaucoup de valeurs distinctes) et une distribution équilibrée, sinon une machine héritera de la moitié des données. Le routage, ensuite : comment, à partir d'une clé, on trouve la bonne machine. Le modulo (hash % N) est le plus simple mais rehache tout dès qu'on ajoute une machine ; le hachage cohérent (voisin du #2) ne déplace qu'une fraction des données au rééquilibrage. Les requêtes, enfin : tant qu'on interroge par la clé, on touche une seule machine et tout va vite ; dès qu'on interroge par autre chose, on doit diffuser la requête à tous les shards et fusionner — le scatter-gather, lent et coûteux. Sauter le choix de la clé, c'est se condamner à ce scatter-gather sur les requêtes les plus fréquentes.Sharding is designed in three beats. The partition key, first: the attribute you split by — user_id, tenant_id, geographic zone. It must have high cardinality (many distinct values) and a balanced distribution, otherwise one machine inherits half the data. Routing, next: how, from a key, you find the right machine. Modulo (hash % N) is the simplest but rehashes everything once you add a machine; consistent hashing (#2's neighbor) moves only a fraction of the data on rebalance. Queries, finally: as long as you query by the key, you hit one machine and everything is fast; the moment you query by something else, you must broadcast the query to all shards and merge — the scatter-gather, slow and costly. Skipping the key choice condemns you to that scatter-gather on the most frequent queries.

La règle du volume : jamais sans un chiffre.The volume's rule: never without a number.

Le sharding est le scaling de dernier recours, pas le premier — et la hiérarchie compte. Si le goulot est en lecture, un réplica en lecture ou un cache le résout sans rien découper, en gardant le système simple. Si la base sature en volume ou en écriture, on grossit d'abord la machine : le scaling vertical est sans rival tant qu'on n'a pas touché son plafond. On ne shard que lorsqu'une seule machine, même la plus grosse, ne peut plus tenir le jeu de données actif ou encaisser le débit d'écriture — et qu'on l'a mesuré. Car le sharding a un coût permanent : requêtes croisées, transactions distribuées perdues, rééquilibrages, exploitation plus lourde. Surtout, la clé de partition est l'une des décisions les plus difficiles à défaire d'un système : la changer impose souvent de tout redéployer et migrer. On ne s'inflige pas ça sur une intuition. Avant tout shard : un chiffre — la taille, le débit, le plafond vertical atteint.Sharding is the scaling of last resort, not the first — and the hierarchy matters. If the bottleneck is reads, a read replica or a cache solves it without splitting anything, keeping the system simple. If the database saturates on volume or writes, you grow the machine first: vertical scaling is unrivaled until you've hit its ceiling. You only shard when a single machine, even the biggest, can no longer hold the active dataset or absorb the write throughput — and you've measured it. Because sharding has a permanent cost: cross-shard queries, lost distributed transactions, rebalances, heavier operations. Above all, the partition key is one of a system's hardest decisions to undo: changing it often forces a full redeploy and migration. You don't inflict that on a hunch. Before any shard: a number — the size, the throughput, the vertical ceiling reached.

№ 02La douleurThe pain4 min

La requête se disperse, la transaction se brise, et une machine porte la moitié du monde.The query scatters, the transaction breaks, and one machine carries half the world.

Découper la donnée a trois coûts, et aucun n'apparaît tant que les requêtes suivent sagement la clé — tous surgissent dès qu'on en sort. D'abord la requête se disperse : toute recherche qui n'est pas par la clé de partition doit interroger chaque machine et fusionner les réponses (scatter-gather), et elle n'est jamais plus rapide que le shard le plus lent. Ensuite la transaction se brise : une opération qui touche deux shards ne peut plus être atomique gratuitement — on retombe sur les sagas et l'idempotence du Vol 7, ou sur un coûteux commit distribué. Enfin la distribution dérape : si la clé est mal choisie — peu de valeurs distinctes, ou une valeur dominante —, une machine hérite d'une part disproportionnée du trafic et des données : c'est le hot shard, un goulot qu'on a recréé à l'intérieur même de la solution censée l'éliminer. Trois douleurs qu'une clé bien choisie réduit, mais que la mauvaise amplifie.Splitting the data has three costs, and none shows as long as queries dutifully follow the key — all surge the moment you step off it. First the query scatters: any search that isn't by the partition key must query every machine and merge the answers (scatter-gather), and it's never faster than the slowest shard. Then the transaction breaks: an operation touching two shards can no longer be atomic for free — you fall back to Vol 7's sagas and idempotency, or to a costly distributed commit. Finally the distribution skews: if the key is badly chosen — few distinct values, or one dominant value —, one machine inherits a disproportionate share of traffic and data: that's the hot shard, a bottleneck recreated inside the very solution meant to remove it. Three pains a good key reduces, but the wrong one amplifies.

№ 1 · La requête se disperse (scatter-gather)The query scatters (scatter-gather)

Toute requête qui ne porte pas la clé de partition doit être envoyée à tous les shards, qui répondent chacun de leur côté, puis l'application fusionne, trie, pagine. Le coût croît avec le nombre de machines, et la latence est celle du plus lent : un seul shard à la traîne ralentit toute la requête. Les tris, les agrégats, le COUNT global deviennent des opérations distribuées — chères là où une base unique les faisait sans y penser.Any query that doesn't carry the partition key must be sent to all shards, each answering on its own, then the app merges, sorts, paginates. The cost grows with the machine count, and the latency is the slowest one's: a single lagging shard slows the whole query. Sorts, aggregates, the global COUNT become distributed operations — costly where a single database did them without a thought.

№ 2 · La transaction se brise (cross-shard)The transaction breaks (cross-shard)

Sur une base unique, modifier deux lignes dans une transaction est atomique et gratuit. Réparties sur deux shards, ces deux lignes ne partagent plus de transaction : on perd le « tout ou rien ». Transférer d'un compte à un autre quand ils vivent sur des machines différentes ramène exactement les sagas, les compensations et les clés d'idempotence du Vol 7 — ou un commit distribué qui verrouille et ralentit. La cohérence forte entre shards n'est plus offerte ; elle se reconstruit, à la main et à un coût.On a single database, modifying two rows in a transaction is atomic and free. Spread across two shards, those two rows no longer share a transaction: you lose 'all or nothing'. Transferring from one account to another when they live on different machines brings back exactly Vol 7's sagas, compensations and idempotency keys — or a distributed commit that locks and slows. Strong consistency across shards is no longer given; it's rebuilt, by hand and at a cost.

№ 3 · La distribution dérape (hot shard)The distribution skews (hot shard)

Le sharding promet d'étaler la charge — à condition que la clé l'étale vraiment. Shard par pays quand 80 % du trafic est dans un seul, par date quand toutes les écritures du jour visent la dernière partition, par un identifiant à faible cardinalité : et une machine prend la moitié de la charge pendant que les autres dorment. On a déplacé le goulot sans le supprimer, et le rééquilibrer après coup — changer la clé — est l'une des migrations les plus douloureuses qui soient.Sharding promises to spread the load — provided the key truly spreads it. Shard by country when 80% of traffic is in one, by date when all of the day's writes hit the last partition, by a low-cardinality identifier: and one machine takes half the load while the others sleep. You've moved the bottleneck without removing it, and rebalancing after the fact — changing the key — is one of the most painful migrations there is.

Une clé mal choisie — la requête se disperse et une machine cuitA badly chosen key — the query scatters and one machine cooks
# clé de partition = pays. 80 % des clients sont en France → shard "FR" prend 80 % du trafic.
def shard_for(customer):
    return shard_by_country[customer.country]   # 💥 hot shard : "FR" cuit, "IS"/"LU" dorment

# le tableau de bord trie les commandes récentes, toutes régions confondues :
def recent_orders(limit=50):
    # 💥 region ≠ clé → on diffuse à TOUS les shards, chacun renvoie 50, on refusionne et retrie
    rows = scatter(s.query("SELECT * FROM orders ORDER BY created_at DESC LIMIT 50") for s in SHARDS)
    return sort(rows, by="created_at")[:50]      # latence = celle du shard le plus lent

# transfert entre deux clients sur deux shards différents :
def transfer(a, b, amount):
    shard_for(a).debit(a, amount)                # 💥 et si la 2ᵉ ligne échoue ? le débit reste,
    shard_for(b).credit(b, amount)               #    plus de transaction commune → incohérence
Le diagnostic.The diagnosis.

Trois questions à poser avant de shard. Sur les requêtes : « par quel attribut lit-on le plus souvent, et est-ce le même que celui par lequel on découpe ? » — si les deux divergent, les requêtes fréquentes deviendront des scatter-gather. Sur les transactions : « quelles opérations touchent plusieurs entités à la fois, et restent-elles sur le même shard ? » — sinon, il faudra des sagas ou un commit distribué. Sur la distribution : « la clé a-t-elle assez de valeurs distinctes, et sont-elles équilibrées dans le temps et l'espace ? » — sinon, un hot shard. Le réflexe : choisir la clé selon laquelle on lit et on transacte le plus, garder les entités liées sur le même shard (co-localisation), vérifier la cardinalité et l'équilibre avant de figer, et préférer un routage qui rééquilibre sans tout rehacher (hachage cohérent). La clé n'est pas un détail d'implémentation : c'est l'architecture.Three questions to ask before sharding. On queries: 'which attribute do you read by most often, and is it the same one you split by?' — if they diverge, frequent queries become scatter-gathers. On transactions: 'which operations touch several entities at once, and do they stay on the same shard?' — if not, you'll need sagas or a distributed commit. On distribution: 'does the key have enough distinct values, and are they balanced across time and space?' — if not, a hot shard. The reflex: choose the key you read and transact by most, keep related entities on the same shard (co-location), check cardinality and balance before freezing, and prefer routing that rebalances without rehashing everything (consistent hashing). The key isn't an implementation detail: it's the architecture.

№ 03Le routageThe routing5 min

Saturée, découpée, ciblée, dispersée — la requête est rapide tant qu'elle suit la clé.Saturated, split, targeted, scattered — the query is fast as long as it follows the key.

Le sharding ne se comprend pas comme « plus de machines », mais comme un routage. L'animation suit une seule décision — la clé de partition — à travers ses conséquences : la machine unique qui sature, le découpage par hash qui étale la charge, la requête par la clé qui ne touche qu'un shard, la requête hors clé qui doit tous les interroger, et la clé mal choisie qui recrée un goulot. Surveille la charge de chaque shard et les machines qu'éclaire la requête courante : c'est l'écart entre « un shard touché » et « tous les shards touchés » qui raconte pourquoi le choix de la clé est toute l'affaire.Sharding isn't understood as 'more machines', but as routing. The animation follows a single decision — the partition key — through its consequences: the single machine saturating, the hash split that spreads the load, the query by the key that hits one shard, the off-key query that must ask them all, and the badly chosen key that recreates a bottleneck. Watch each shard's load and the machines the current query lights up: it's the gap between 'one shard hit' and 'all shards hit' that tells why the key choice is the whole affair.

Animation — une clé de partition, du nœud saturé au hot shardAnimation — one partition key, from saturated node to hot shard
Une seule machine satureOne machine saturates · 1/5
requête : query: tout le traficall the traffic
↓
100%
nœud uniquesingle node
↑
100 % sur une machine — plafond atteint100% on one machine — ceiling reached
Tout vit sur un nœud : 100 % des données, 100 % des écritures. On a grossi la machine jusqu'à son plafond — plus de RAM, plus de CPU n'y feront rien. Le volume ou le débit dépasse ce qu'une seule machine peut tenir. C'est le seul point de départ légitime du sharding : un chiffre qui crève le plafond vertical, pas une intuition.Everything lives on one node: 100% of the data, 100% of the writes. You've grown the machine to its ceiling — more RAM, more CPU won't help. The volume or throughput exceeds what one machine can hold. It's sharding's only legitimate starting point: a number that breaks the vertical ceiling, not a hunch.

Routage par la clé, et le scatter-gather quand on en sort — 2 lignes à retrouverRouting by the key, and the scatter-gather when you step off it — 2 lines to recall
def route(user_id):
def get_orders(user_id):
    return route(user_id).query(                  # ALIGNÉ sur la clé → un seul shard, rapide
        "SELECT * FROM orders WHERE user_id = ?", user_id)

def recent_orders_global(limit):
    # HORS clé → scatter-gather : on demande à tous, on refusionne, on retrie    return sort(parts, by="created_at", desc=True)[:limit]   # latence = celle du shard le plus lent
# règle : on choisit la clé d'après la requête la PLUS fréquente, pour que le scatter reste l'exception.
2 ligne(s) à retrouver — écris-la de tête avant de cliquer2 line(s) to recall — write it in your head before clicking
Modulo vs hachage cohérent : le coût du rééquilibrage.Modulo vs consistent hashing: the cost of rebalancing.

Le routage le plus simple, hash(clé) % N, marche tant que N ne bouge pas — mais ajouter une machine change N, donc la cible de presque toutes les clés : il faut alors déplacer la quasi-totalité des données, une migration brutale en pleine production. Le hachage cohérent répond exactement à ce problème : il place machines et clés sur un même anneau, et chaque clé appartient à la machine suivante sur l'anneau. Ajouter ou retirer une machine ne déplace que les clés de son voisinage immédiat — environ 1/N des données, pas la totalité. C'est ce qui rend le rééquilibrage supportable, et donc le sharding évolutif. Mais aucun routage ne sauve une mauvaise clé : si la distribution des valeurs est déséquilibrée, le hot shard revient quelle que soit la fonction de hachage. Le routage gère l'ajout de machines ; la cardinalité de la clé gère l'équilibre. Les deux sont nécessaires.The simplest routing, hash(key) % N, works as long as N doesn't move — but adding a machine changes N, hence the target of almost every key: you must then move nearly all the data, a brutal migration in full production. Consistent hashing answers exactly this problem: it places machines and keys on the same ring, and each key belongs to the next machine on the ring. Adding or removing a machine moves only the keys of its immediate neighborhood — about 1/N of the data, not all of it. That's what makes rebalancing bearable, and therefore sharding scalable. But no routing saves a bad key: if the value distribution is skewed, the hot shard returns whatever the hash function. Routing handles adding machines; the key's cardinality handles balance. Both are necessary.

Modèle mentalMental model
L'imageThe image

Ranger une bibliothèque géante par initiale de l'auteur, sur des étagères séparées : chercher « Zola » est instantané — tu vas droit à l'étagère Z. Mais « tous les livres publiés en 1880 » t'oblige à fouiller chaque étagère.Shelving a giant library by the author's initial, on separate shelves: finding 'Zola' is instant — you go straight to shelf Z. But 'every book published in 1880' forces you to search every shelf.

La règleThe rule

Découper la donnée par une clé stable, à forte cardinalité et bien répartie ; une requête qui suit la clé touche un seul shard, une requête hors clé les touche tous.Split the data by a stable, high-cardinality, well-spread key; a query that follows the key hits one shard, an off-key query hits them all.

Où l'analogie casseWhere the analogy breaks

Une bibliothèque, tu peux la reclasser un dimanche par un autre critère. Une clé de partition, non : la changer impose de re-hacher et migrer tout le jeu de données en production. On range une bibliothèque pour aujourd'hui ; on choisit une clé de shard pour la vie du système — d'où la règle : d'après ce qu'on lira le plus, pas ce qui est commode sur le moment.A library, you can re-shelve on a Sunday by another criterion. A partition key, you can't: changing it forces re-hashing and migrating the whole dataset in production. You shelve a library for today; you choose a shard key for the system's life — hence the rule: by what you'll read most, not what's convenient right now.

№ 04Le piègeThe trap4 min

Shard trop tôt, ou shard sur la mauvaise clé.Shard too early, or shard on the wrong key.

Deux dérives encadrent le bon usage, et elles naissent du même défaut — ne pas avoir regardé le chiffre. La première shard trop tôt : le goulot était en lecture, qu'un réplica ou un cache aurait absorbé, ou bien le volume n'avait jamais approché le plafond d'une grosse machine. On a payé toute la complexité du distribué — scatter-gather, transactions perdues, exploitation — pour une capacité dont on n'avait pas besoin. La seconde shard sur la mauvaise clé : on découpe par un attribut à faible cardinalité ou monotone — la date, le pays, un statut —, et le trafic se concentre sur un shard pendant que les autres dorment. La première dérive ajoute du coût sans bénéfice ; la seconde recrée le goulot à l'intérieur de la solution, et le réparer impose de changer la clé, donc de tout migrer. Bascule le toggle : repère le sharding qu'aucun chiffre n'appelait, et la clé qu'aucun équilibre ne justifie.Two drifts frame correct use, and they spring from the same flaw — not having looked at the number. The first shards too early: the bottleneck was reads, which a replica or cache would have absorbed, or else the volume had never approached a big machine's ceiling. You paid all the distributed complexity — scatter-gather, lost transactions, operations — for capacity you didn't need. The second shards on the wrong key: you split by a low-cardinality or monotonic attribute — the date, the country, a status —, and traffic concentrates on one shard while the others sleep. The first drift adds cost with no benefit; the second recreates the bottleneck inside the solution, and fixing it forces changing the key, hence migrating everything. Flip the toggle: spot the sharding no number called for, and the key no balance justifies.

# on a MESURÉ : 4 To de données, 60 000 écritures/s — la plus grosse machine plafonne à 2 To / 20k.
# le scaling vertical est épuisé, les réplicas en lecture ne servent à rien (le goulot est l'ÉCRITURE).
# → sharding justifié. clé = tenant_id : forte cardinalité, et 95 % des requêtes portent ce tenant.
def route(tenant_id):
    return SHARDS[consistent_hash(tenant_id)]     # ✓ +1 machine ne déplace qu'1/N des données

def get_invoices(tenant_id):                      # ✓ aligné sur la clé → un seul shard
    return route(tenant_id).query("SELECT * FROM invoices WHERE tenant_id = ?", tenant_id)

def add_invoice(tenant_id, inv):                  # ✓ entité co-localisée avec son tenant
    route(tenant_id).insert("invoices", {**inv, "tenant_id": tenant_id})
# tenant + ses factures vivent sur le MÊME shard → la transaction reste locale, pas de cross-shard.
Pourquoi c'est justeWhy it's right

Le sharding n'existe ici que parce qu'un chiffre l'a imposé : 4 To et 60 000 écritures par seconde quand la plus grosse machine plafonne à 2 To et 20 000 — le vertical est épuisé, et comme le goulot est l'écriture, aucun réplica en lecture n'aurait aidé. La clé est choisie d'après ce qu'on lit le plus : tenant_id, forte cardinalité, présent dans 95 % des requêtes. Résultat, la quasi-totalité du trafic touche un seul shard et va aussi vite que sur une base unique. Mieux : les factures sont co-localisées avec leur tenant, donc les transactions restent locales — pas de cross-shard. Et le routage par hachage cohérent fait qu'ajouter une machine ne déplace qu'une fraction des données, pas le tout. C'est un sharding qui achète de la capacité réelle en gardant le scatter-gather et les transactions distribuées à l'état d'exception : le bénéfice, sans recréer le goulot.Sharding exists here only because a number forced it: 4 TB and 60,000 writes per second when the biggest machine caps at 2 TB and 20,000 — vertical is exhausted, and since the bottleneck is writes, no read replica would have helped. The key is chosen for what you read most: tenant_id, high cardinality, present in 95% of queries. As a result, nearly all traffic hits one shard and goes as fast as on a single database. Better: invoices are co-located with their tenant, so transactions stay local — no cross-shard. And consistent-hashing routing means adding a machine moves only a fraction of the data, not all of it. It's a sharding that buys real capacity while keeping scatter-gather and distributed transactions as the exception: the benefit, without recreating the bottleneck.

La règle qui tranche.The rule that settles it.

Ne shard qu'un plafond vertical mesuré, et choisis la clé d'après la requête la plus fréquente. Deux tests. Le besoin : « le volume ou le débit d'écriture dépasse-t-il ce qu'une seule grosse machine peut tenir, et l'ai-je vérifié plutôt que supposé ? » — si le goulot est en lecture, un réplica ou un cache d'abord ; le sharding ne se justifie que quand l'écriture ou le stockage saturent vraiment. La clé : « par quel attribut lit-on et transacte-t-on le plus, a-t-il une forte cardinalité, et est-il équilibré dans le temps et l'espace ? » — c'est lui la clé, parce que c'est lui qu'on ne pourra plus changer sans tout migrer. Un sharding sans réponse à la première question est une complexité gratuite ; un sharding sans réponse à la seconde est un hot shard programmé. Et co-localise ce qui se lit ensemble, pour que la transaction reste locale.Shard only a measured vertical ceiling, and choose the key by the most frequent query. Two tests. The need: 'does the volume or write throughput exceed what one big machine can hold, and have I verified it rather than assumed it?' — if the bottleneck is reads, a replica or cache first; sharding is justified only when writes or storage truly saturate. The key: 'which attribute do you read and transact by most, does it have high cardinality, and is it balanced across time and space?' — that's the key, because it's the one you can no longer change without migrating everything. A sharding with no answer to the first question is free complexity; a sharding with no answer to the second is a scheduled hot shard. And co-locate what's read together, so the transaction stays local.

Le piège jumeau : la requête qu'on n'avait pas prévue.The twin trap: the query you didn't foresee.

Une clé de partition optimise les requêtes qu'on connaissait le jour du découpage — et pénalise toutes les autres, à jamais. Le danger n'est pas la requête d'aujourd'hui, alignée par construction ; c'est celle que le produit réclamera dans six mois, par un axe qu'on n'avait pas anticipé : « toutes les commandes d'une région », « le top des produits, tous tenants confondus ». Chacune devient un scatter-gather permanent. Trois parades. D'abord, refuser de shard tant que les axes d'accès ne sont pas stables — on ne fige pas une clé sur un schéma de requêtes encore mouvant. Ensuite, servir les axes secondaires autrement : un index global maintenu à part, ou une vue dénormalisée alimentée en lecture seule (on rejoint le CQRS du Vol 5). Enfin, garder pour l'analytique un entrepôt séparé, plutôt que de faire saigner le système transactionnel en scatter-gather. La clé sert le chemin chaud ; les autres chemins, on les sert ailleurs.A partition key optimizes the queries you knew the day you split — and penalizes all others, forever. The danger isn't today's query, aligned by construction; it's the one the product will demand in six months, along an axis you didn't anticipate: 'all orders in a region', 'the top products across all tenants'. Each becomes a permanent scatter-gather. Three defenses. First, refuse to shard until the access axes are stable — you don't freeze a key on a still-shifting query pattern. Then, serve the secondary axes otherwise: a global index maintained separately, or a denormalized read-only view (you rejoin Vol 5's CQRS). Finally, keep a separate warehouse for analytics, rather than bleeding the transactional system with scatter-gather. The key serves the hot path; the other paths, you serve elsewhere.

№ 05Bilan & éditoWrap-up & editorial2 min

Plus de capacité, ou plus de simplicité — le sharding échange l'une contre l'autre.More capacity, or more simplicity — sharding trades one for the other.

Sharding subi vs sharding conçu, côte à côteSharding endured vs sharding designed, side by side
sharding subisharding enduredsharding conçusharding designed
La décisionThe decision« ça scalera mieux »'it'll scale better'un plafond vertical mesuréa measured vertical ceiling
La cléThe keydate, pays, statutdate, country, statusforte cardinalité, ce qu'on lit le plushigh cardinality, what you read most
Les requêtesQueriesscatter-gather par défautscatter-gather by defaultalignées sur la clé, un shardaligned on the key, one shard
Les transactionsTransactionscross-shard, casséescross-shard, brokenco-localisées, localesco-located, local
Ajouter une machineAdding a machinemodulo → tout rehachermodulo → rehash everythinghachage cohérent → 1/Nconsistent hashing → 1/N

Quand ne pas (encore) shardWhen not to shard (yet)

Le sharding a un loyer permanent : des requêtes croisées, des transactions distribuées, une exploitation plus lourde, et une clé qu'on ne pourra plus changer sans tout migrer. On garde une seule base — et c'est correct, souvent préférable — quand :Sharding has permanent rent: cross-shard queries, distributed transactions, heavier operations, and a key you can no longer change without migrating everything. You keep a single database — and that's fine, often preferable — when:

  • le scaling vertical n'est pas épuisé : une machine plus grosse tient encore le volume et le débit. Tant que le plafond n'est pas atteint et mesuré, grossir la machine reste plus simple, plus sûr et moins cher que distribuer (la règle : on n'entre dans la scalabilité qu'avec un chiffre).vertical scaling isn't exhausted: a bigger machine still holds the volume and throughput. Until the ceiling is reached and measured, growing the machine stays simpler, safer and cheaper than distributing (the rule: you enter scalability only with a number).
  • le goulot est en lecture : un réplica en lecture ou un cache (le #1) absorbe la charge sans rien découper. Sharder pour un problème de lecture, c'est payer la complexité de l'écriture distribuée pour rien. On ne shard que quand l'écriture ou le stockage saturent.the bottleneck is reads: a read replica or a cache (#1) absorbs the load without splitting anything. Sharding for a read problem means paying for distributed-write complexity for nothing. You shard only when writes or storage saturate.
  • les axes d'accès ne sont pas stables : si on ne sait pas encore par quoi on lira le plus dans six mois, figer une clé de partition revient à parier — et un mauvais pari se paie en scatter-gather permanent et en migration. On attend que le schéma de requêtes se stabilise avant de graver la clé.the access axes aren't stable: if you don't yet know what you'll read by most in six months, freezing a partition key is a bet — and a bad bet is paid in permanent scatter-gather and migration. You wait for the query pattern to stabilize before carving the key.

Le réflexe n'est donc pas « ça grossit, shardons » : c'est « ai-je atteint le plafond d'une seule machine en écriture ou en stockage, mesuré, et sais-je par quelle clé stable on lit le plus ? Alors shard, en co-localisant et en hachant de façon cohérente. Sinon, grossis la machine ou réplique en lecture — c'est plus simple, et la simplicité est une capacité aussi. »So the reflex isn't 'it's growing, let's shard': it's 'have I reached one machine's ceiling on writes or storage, measured, and do I know which stable key you read by most? Then shard, co-locating and consistent-hashing. Otherwise, grow the machine or replicate reads — it's simpler, and simplicity is a capacity too.'

Mot de l'éditeurFrom the editor

Le cache du #1 gardait une seule source de vérité et la recopiait pour lire vite. Le sharding va plus loin et plus douloureusement : il brise l'unité de la source elle-même. Là où le cache laissait intacte l'illusion d'« un seul endroit où vit la donnée », le sharding y renonce — et avec elle aux deux conforts qu'une base unique offrait sans qu'on les remarque : la requête qui joint tout d'un coup, et la transaction qui change deux choses atomiquement. C'est le prix de la capacité sans plafond. Et c'est la leçon que la série répète sous toutes ses formes : il n'existe pas de geste gratuit. Le débutant voit « plus de machines, plus de puissance » ; l'architecte voit une clé de partition qui décide, pour la vie du système, ce qui sera rapide et ce qui sera dispersé — et il la choisit avec le soin d'une décision qu'on ne révoque pas.Issue #1's cache kept a single source of truth and copied it to read fast. Sharding goes further and more painfully: it breaks the unity of the source itself. Where the cache left intact the illusion of 'one place where the data lives', sharding gives it up — and with it the two comforts a single database offered unnoticed: the query that joins everything at once, and the transaction that changes two things atomically. That's the price of capacity without a ceiling. And it's the lesson the series repeats in every form: there's no free gesture. The beginner sees 'more machines, more power'; the architect sees a partition key that decides, for the system's life, what will be fast and what will be scattered — and chooses it with the care of a decision you don't revoke.

Mais en distribuant la donnée, le sharding fait surgir une question qu'une base unique masquait : pour survivre à la panne d'une machine, on réplique chaque shard sur plusieurs nœuds — et dès qu'une donnée existe en plusieurs exemplaires, il faut les garder d'accord. Que se passe-t-il quand le réseau coupe entre ces copies ? Refuse-t-on de répondre pour rester cohérent, ou répond-on au risque de servir une version périmée ? Aucune réplication n'échappe à ce choix. C'est le sujet du #3, et la note finale de toute la série : le théorème CAP — la preuve qu'un système distribué ne peut, sous partition réseau, être à la fois cohérent et disponible, et qu'architecturer, c'est choisir lequel on sacrifie.But by distributing the data, sharding raises a question a single database hid: to survive one machine's failure, you replicate each shard across several nodes — and once data exists in several copies, you must keep them in agreement. What happens when the network cuts between those copies? Do you refuse to answer to stay consistent, or answer at the risk of serving a stale version? No replication escapes this choice. It's #3's subject, and the final note of the whole series: the CAP theorem — the proof that a distributed system cannot, under a network partition, be both consistent and available, and that to architect is to choose which one you sacrifice.

Les voisins que ce numéro débloqueThe neighbors this issue unlocks

  • Consistent hashing — le routage qui rend le sharding évolutif. Au lieu de hash % N — qui change la cible de presque toutes les clés dès qu'on ajoute une machine —, on place machines et clés sur un anneau, et chaque clé revient à la machine suivante. Ajouter ou retirer un nœud ne déplace qu'environ 1/N des données, leurs voisines immédiates sur l'anneau, et non la totalité. C'est ce qui transforme le rééquilibrage d'une migration brutale en une opération de routine — la condition pour qu'un cluster shardé puisse grandir en production.the routing that makes sharding scalable. Instead of hash % N — which changes the target of almost every key the moment you add a machine —, you place machines and keys on a ring, and each key falls to the next machine. Adding or removing a node moves only about 1/N of the data, its immediate ring-neighbors, not all of it. That's what turns rebalancing from a brutal migration into a routine operation — the condition for a sharded cluster to grow in production.
  • Replication & read replicas — le pendant du sharding, et souvent son alternative. Là où le sharding découpe la donnée pour la capacité, la réplication la copie pour la disponibilité et la lecture : un primaire pour les écritures, des réplicas qui reçoivent un flux de changements et servent les lectures. C'est la première chose à essayer quand le goulot est en lecture — bien avant de shard. Mais répliquer, c'est créer des copies qui peuvent diverger le temps que le flux les rattrape : la cohérence à terme, et le choix CAP, entrent par cette porte.sharding's counterpart, and often its alternative. Where sharding splits data for capacity, replication copies it for availability and reads: a primary for writes, replicas that receive a change stream and serve reads. It's the first thing to try when the bottleneck is reads — well before sharding. But replicating means creating copies that can diverge while the stream catches up: eventual consistency, and the CAP choice, enter through that door.
  • Co-location & shard key — la discipline qui garde les transactions locales. Co-localiser, c'est ranger sur le même shard les entités qu'on lit et modifie ensemble — un tenant et ses factures, un utilisateur et ses commandes — en les faisant partager la clé de partition. Une transaction qui reste dans un shard reste atomique, gratuitement, comme sur une base unique ; une transaction qui le franchit retombe sur les sagas du Vol 7. Choisir la clé de shard, c'est donc moins choisir comment répartir la donnée que choisir quelles opérations resteront simples.the discipline that keeps transactions local. Co-locating means placing on the same shard the entities you read and modify together — a tenant and its invoices, a user and their orders — by making them share the partition key. A transaction that stays within a shard stays atomic, for free, as on a single database; one that crosses it falls back to Vol 7's sagas. Choosing the shard key is therefore less about choosing how to spread the data than about choosing which operations stay simple.

Le kata — écris-le, ne le relis pasThe kata — write it, don't reread it

Le routage est le cœur du sharding, et le hachage cohérent est ce qui le rend évolutif : ajouter une machine ne doit pas rehacher tout le jeu de données. Copie ce fichier de tests dans ton langage et ton runner habituels et fais-les passer au vert sans revenir au numéro. Les deux derniers tests sont les sournois : ils comptent combien de clés changent de nœud quand on en ajoute ou retire un. Une implémentation en hash % N les fait tous échouer — presque toutes les clés bougent. Tout l'intérêt de l'anneau tient dans ce « < 1500, pas 4000 ».Routing is the heart of sharding, and consistent hashing is what makes it scalable: adding a machine mustn't rehash the whole dataset. Copy this test file into your usual language and runner and turn them green without coming back to the issue. The last two tests are the sneaky ones: they count how many keys change node when you add or remove one. A hash % N implementation fails them all — almost every key moves. The whole point of the ring lives in that '< 1500, not 4000'.

test_consistent_hash.py
# test_consistent_hash.py — la spec EST le harnais : fais-le passer au vert.
# un anneau de hachage cohérent : router une clé vers un nœud, et — surtout — ne déplacer
# qu'une FRACTION des clés quand on ajoute ou retire un nœud.

def test_route_est_deterministe():
    ring = Ring(["A", "B", "C"])
    assert ring.route("user:42") == ring.route("user:42")   # même clé → toujours le même nœud

def test_repartit_sur_tous_les_noeuds():
    ring = Ring(["A", "B", "C"])
    cibles = {ring.route(f"user:{i}") for i in range(1000)}
    assert cibles == {"A", "B", "C"}                          # les 3 nœuds reçoivent des clés

def test_charge_a_peu_pres_equilibree():
    ring = Ring(["A", "B", "C"])
    n = Counter(ring.route(f"user:{i}") for i in range(3000))
    assert all(700 < c < 1300 for c in n.values())           # ~1000 chacun, aucun nœud écrasé

def test_retirer_un_noeud_ne_touche_que_ses_cles():
    avant = Ring(["A", "B", "C"])
    apres = Ring(["A", "C"])                                   # B disparaît
    bouge = sum(avant.route(f"k{i}") != apres.route(f"k{i}") for i in range(3000))
    assert bouge < 1300                                        # seules les clés de B se déplacent

def test_ajouter_un_noeud_ne_deplace_qu_une_fraction():
    avant = Ring(["A", "B", "C"])
    apres = Ring(["A", "B", "C", "D"])                         # +1 nœud → ~1/4 des clés bougent
    bouge = sum(avant.route(f"k{i}") != apres.route(f"k{i}") for i in range(4000))
    assert bouge < 1500                                        # PAS 100 % : tout l'écart avec hash % N
Chez toi, cette semaine.In your codebase, this week.

Avant de rêver de shards, cherche le vrai goulot — et le futur scatter-gather. D'abord, la nature de la charge : SELECT ... FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20 (ou l'équivalent de ton SGBD) te dit si le temps part en lecture ou en écriture — si c'est la lecture, un réplica ou un cache, pas un shard. Ensuite, si tu shardes déjà (ou y penses), liste tes requêtes du top et demande, pour chacune : « porte-t-elle la clé de partition dans son WHERE ? » Celles qui ne la portent pas sont tes scatter-gather permanents — compte-les avant de graver la clé.Before dreaming of shards, find the real bottleneck — and the future scatter-gather. First, the nature of the load: SELECT ... FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20 (or your DB's equivalent) tells you whether time goes to reads or writes — if it's reads, a replica or a cache, not a shard. Then, if you already shard (or plan to), list your top queries and ask, for each: 'does it carry the partition key in its WHERE?' The ones that don't are your permanent scatter-gathers — count them before carving the key.