Software Engineering5 mars 20268 min read

Les schémas de montée en charge des bases de données avant le sharding

Le sharding est coûteux, permanent et difficile à inverser. Voici les schémas de montée en charge à épuiser d'abord, dans l'ordre où nous les mobilisons sur de vrais systèmes en production.

Par Innovation T Team


On parle du sharding comme d'un rite de passage, le moment où une base de données devient enfin adulte. En pratique, c'est l'une des décisions les plus coûteuses et les moins réversibles que vous puissiez prendre, et la plupart des équipes s'y attellent des années avant d'en avoir réellement besoin. Voici l'ordre dans lequel nous procédons quand une base de données commence à souffrir, et pourquoi chaque échelon vous fait gagner du temps avant de vous engager sur la solution la plus lourde.

Pourquoi le sharding est un dernier recours

Le sharding consiste à répartir vos données sur plusieurs instances de base de données indépendantes, de sorte qu'aucune machine ne détienne l'ensemble complet. Cela ressemble à une augmentation de capacité, et ça l'est. C'est aussi une taxe sur presque tout ce que vous construirez ensuite.

Une fois que les données vivent sur des shards distincts, les jointures inter-shards cessent d'être gratuites, les transactions qui s'étendent sur plusieurs shards nécessitent une coordination au niveau applicatif, et chaque requête doit savoir à quel shard s'adresser. Rééquilibrer un shard surchargé est un projet, pas un changement de configuration. Des analyses qui tenaient auparavant en une seule requête deviennent une opération de diffusion et regroupement (scatter-gather) entre les nœuds. D'après notre expérience, les équipes qui font du sharding trop tôt passent plus de temps d'ingénierie à contourner leur propre topologie qu'elles n'en ont jamais consacré au problème de performance qui l'a déclenché.

L'objectif de cet article n'est donc pas de vous expliquer comment faire du sharding. C'est de vous expliquer comment ne pas encore en avoir besoin. Chacun des schémas ci-dessous est moins coûteux à adopter et moins coûteux à annuler.

Mesurez avant d'agir

Avant de toucher à l'architecture, obtenez des chiffres honnêtes. Ajuster la mauvaise couche, c'est ainsi que des équipes ajoutent des réplicas de lecture pour résoudre un problème qui n'était en réalité qu'un index manquant.

Collectez au minimum :

  • Les journaux de requêtes lentes avec les plans d'exécution réels, et non des suppositions sur les requêtes qui seraient lentes.
  • La latence p95 et p99 par point d'accès, pas des moyennes. Les moyennes masquent les requêtes qui font fuir les utilisateurs.
  • Le nombre de connexions dans le temps, y compris les connexions inactives et en attente.
  • Le ratio lecture/écriture. Une charge de travail à 95 % de lectures et une charge à 60 % de lectures appellent des échelons différents.
  • La taille des tables et des index, ainsi que le taux de croissance du nombre de lignes par mois.

Si vous ne pouvez pas voir ces données aujourd'hui, c'est votre première tâche. Vous ne pouvez pas faire monter en charge ce que vous ne pouvez pas mesurer, et la moitié du temps les données révèlent une solution qui coûte un après-midi plutôt qu'un trimestre.

L'échelle de la montée en charge

Voici l'ordre dans lequel nous grimpons, du moins coûteux et du plus sûr au plus intrusif. Progressez de haut en bas et arrêtez-vous dès que vos chiffres redeviennent sains.

  1. Corrigez les requêtes et les index.
  2. Dimensionnez et regroupez correctement vos connexions.
  3. Ajoutez de la mise en cache là où le schéma de lecture le permet.
  4. Déchargez les lectures vers des réplicas.
  5. Faites monter en charge le primaire verticalement.
  6. Partitionnez les grandes tables au sein d'une seule base de données.
  7. Séparez par domaine (magasins distincts ou CQRS).
  8. Seulement ensuite, faites du sharding.

1. Les requêtes et les index d'abord

Le travail au rendement le plus élevé n'est presque jamais architectural. C'est un index manquant sur une colonne de WHERE ou de JOIN, une requête N+1 qui se déclenche des centaines de fois par requête, ou un SELECT * qui tire de larges lignes alors que trois colonnes suffiraient.

Exécutez EXPLAIN ANALYZE (Postgres) ou son équivalent sur vos requêtes les plus lentes. Repérez les balayages séquentiels sur les grandes tables, les tris qui débordent sur le disque et les boucles imbriquées sur de grands volumes de lignes. Ajoutez des index couvrants pour que la base réponde directement depuis l'index. Attention toutefois à la sur-indexation : chaque index que vous ajoutez ralentit les écritures et consomme de la mémoire, alors indexez pour les requêtes que vous exécutez réellement, puis supprimez celles qui ne servent jamais.

Cet échelon réduit régulièrement la latence p99 d'une marge importante pour un coût d'infrastructure quasi nul. Faites-le avant toute autre chose.

2. Le pooling de connexions et les limites

Les bases de données gèrent bien beaucoup moins de connexions simultanées que la plupart des gens ne le supposent. Une machine Postgres à l'aise avec 100 connexions actives peut s'effondrer à 500, car chaque connexion représente une réelle charge de mémoire et d'ordonnancement. Les couches applicatives serverless et à autoscaling aggravent la situation : chaque nouvelle instance ouvre son propre pool, et le nombre de connexions explose.

Placez un pooler en amont (PgBouncer pour Postgres, ProxySQL pour MySQL, ou l'équivalent managé de votre fournisseur cloud). Utilisez un pooling au niveau des transactions pour que les connexions soient partagées de façon agressive. Plafonnez délibérément la taille du pool, et dimensionnez les pools de la couche applicative en tenant compte de la limite réelle de la base de données, et non du nombre par défaut de votre framework applicatif.

3. La mise en cache là où les lectures se répètent

Si les mêmes données sont lues bien plus souvent qu'elles ne changent, un cache placé devant la base absorbe la charge. Redis ou Memcached pour les recherches clé-valeur fréquentes, la mise en cache HTTP et CDN pour tout ce qui peut être servi légèrement obsolète, et les vues matérialisées pour les agrégations coûteuses qui n'ont pas besoin d'être en temps réel.

La partie difficile de la mise en cache est l'invalidation, alors soyez explicite sur votre stratégie : une expiration basée sur le temps pour les données qui peuvent être un peu obsolètes, et une invalidation basée sur les événements pour les données qui doivent être fraîches à l'écriture. Mettez en cache les lectures coûteuses, pas tout, et mesurez toujours votre taux de succès (hit rate). Un cache qui tourne à un taux de succès de 40 % ajoute souvent de la latence au lieu de la retirer.

4. Les réplicas de lecture

Quand les lectures dominent et que vous avez déjà mis en cache les gains évidents, répliquez. Un primaire gère les écritures, un ou plusieurs réplicas de lecture gèrent les requêtes, et l'application achemine en conséquence. Les bases de données managées dans le cloud rendent cela presque aussi simple qu'une case à cocher.

Le compromis, c'est le décalage de réplication (replication lag). Un réplica est à cohérence à terme, si bien qu'un utilisateur qui vient d'enregistrer une modification pourrait lire un réplica pas encore à jour et voir des données obsolètes. Acheminez vers le primaire les lectures qui doivent être immédiatement cohérentes (un utilisateur qui lit son propre formulaire tout juste soumis), et envoyez vers les réplicas tout ce qui tolère un léger délai (tableaux de bord, listes, recherche). Les requêtes de reporting et d'analyse en particulier ont leur place sur un réplica pour ne jamais entrer en concurrence avec les écritures de production.

5. La montée en charge verticale

Parfois, la solution la moins coûteuse est une machine plus grande. Le matériel moderne est colossal : des instances dotées de centaines de gigaoctets de RAM et de dizaines de cœurs sont courantes, et un seul nœud bien réglé peut porter des charges de travail que les équipes supposent réservées à un cluster. Ajouter de la RAM pour que l'ensemble de travail tienne en mémoire fait souvent plus que n'importe quel changement de topologie.

La montée en charge verticale a un plafond et une facture, ce n'est donc pas l'état final. Mais comme moyen de gagner six à douze mois pendant que vous construisez proprement l'échelon suivant, c'est souvent le bon choix. Assurez-vous simplement que votre fournisseur prend en charge le redimensionnement avec un temps d'arrêt minimal avant de vous appuyer dessus, et gardez un œil sur la courbe de coûts. Notre guide pratique d'optimisation des coûts cloud explique comment empêcher cette dépense de vous échapper discrètement.

6. Le partitionnement au sein d'une seule base de données

Le partitionnement divise une grande table en morceaux physiques plus petits tout en la conservant comme une seule table logique et en restant à l'intérieur d'une seule base de données. Ce n'est pas du sharding : il n'y a pas de coordination distribuée, et vos requêtes changent à peine.

Le gain classique est le partitionnement basé sur le temps sur une table d'événements, de journaux ou de commandes : partitionnez par mois, et les requêtes filtrées sur une plage de dates ne balayent que les partitions pertinentes (élagage de partitions, partition pruning). Supprimer d'anciennes données devient une suppression de partition instantanée au lieu d'un DELETE lent et gonflant. Vous pouvez aussi partitionner par une clé comme le locataire (tenant) ou la région. Le partitionnement offre une bonne partie des bénéfices que les gens attendent du sharding tout en conservant la simplicité opérationnelle d'une seule base de données.

7. La séparation par domaine

Avant de répartir une table sur plusieurs machines, envisagez de répartir votre schéma sur quelques-unes. Si la facturation, l'analytique et le produit principal vivent tous dans une seule base de données et se disputent les mêmes ressources, les séparer par contexte délimité donne à chacun de l'espace pour respirer et monter en charge selon ses propres termes.

Cela s'accorde naturellement avec les frontières de services. Si vous allez déjà dans cette direction, notre guide sur le passage du monolithe aux microservices explique comment tracer ces coutures sans créer un désordre distribué. Une séparation CQRS (un magasin d'écriture normalisé plus un magasin optimisé pour la lecture, souvent un moteur de recherche ou un réplica dénormalisé) est la même idée appliquée à un seul domaine à fort trafic.

Quand le sharding a réellement du sens

Parfois, vous en avez sincèrement besoin. Les signaux honnêtes :

  • Une seule table a dépassé ce qu'un nœud peut contenir ou indexer efficacement, même après partitionnement.
  • Le débit d'écriture sur le primaire est le goulot d'étranglement, et les réplicas n'aident pas car ils ne font monter en charge que les lectures.
  • Vous disposez d'une clé de sharding naturelle et stable (identifiant de locataire, identifiant d'utilisateur, région) qui maintient presque toutes les requêtes à l'intérieur d'un seul shard.
  • Vous avez déjà gravi tous les échelons ci-dessus et vous avez encore un mur devant vous.

Si c'est votre cas, faites du sharding délibérément : choisissez une clé de sharding qui correspond à vos schémas d'accès, prévoyez le rééquilibrage dès le premier jour, et appuyez-vous sur un système qui gère la distribution à votre place (Citus, Vitess, ou une base de données nativement distribuée) plutôt que de coder l'acheminement à la main dans le code applicatif.

Une liste de contrôle avant le sharding

Parcourez cette liste avant que quiconque n'ouvre un document de conception de sharding :

  1. Les 20 requêtes les plus lentes sont-elles indexées et exemptes de balayages séquentiels sur les grandes tables ?
  2. Les schémas N+1 ont-ils été éliminés dans les points d'accès les plus sollicités ?
  3. Un pooler de connexions est-il en place avec des tailles de pool délibérées ?
  4. Les lectures répétées sont-elles mises en cache, avec un taux de succès que vous avez réellement mesuré ?
  5. Les requêtes à forte lecture et de reporting ont-elles été déplacées vers des réplicas ?
  6. La montée en charge verticale a-t-elle été essayée ou au moins chiffrée ?
  7. Les grandes tables de séries temporelles ou à forte insertion sont-elles partitionnées ?
  8. Avez-vous séparé les domaines sans rapport dans leurs propres magasins ?

Si vous ne pouvez pas cocher la plupart de ces cases, le sharding ne résoudra pas votre problème. Il ajoutera un problème de systèmes distribués par-dessus celui que vous avez déjà.

Comment Innovation T peut vous aider

La plupart des douleurs de base de données pour lesquelles on nous appelle ne sont pas un problème de capacité, c'est un problème d'instrumentation et de requêtes déguisé en problème de capacité. Chez Innovation T, nos équipes d'ingénierie logicielle et cloud commencent là où pointent les chiffres : nous profilons la charge de travail réelle, corrigeons les requêtes et les index qui font réellement mal, puis remontons l'échelle (pooling, mise en cache, réplicas, partitionnement) afin que vous n'ajoutiez de la complexité que là où elle est méritée. Quand le sharding ou une base de données distribuée est vraiment la réponse, nous concevons avec vous la clé de sharding et le chemin de migration pour qu'ils tiennent pendant des années, pas des mois.

Si votre base de données commence à ressembler à un plafond, jetez un œil à nos services ou prenez contact. Nous préférons vous aider à éviter un shard que nettoyer après un shard précipité.

#base de données#montée en charge#performance#architecture

Prêt à construire avec Innovation T ?

Qu'il s'agisse de sécurité, de croissance ou d'ingénierie, notre équipe peut vous aider à livrer dans les meilleures conditions.