Topic

Les erreurs de conception de bases dans les produits digitaux en croissance

Insights/ Systèmes de données & performance / Architecture de bases de données

08 févr. 2025 - 10 min de lecture

Les erreurs de conception de bases dans les produits digitaux en croissance
Écouter l’article00:00 / 12:46

L’essentiel de la douleur de base de données vient d’une poignée de décisions précoces

L’essentiel de la douleur de base de données avec laquelle vivent les produits digitaux en croissance vient d’une poignée de décisions de schéma prises tôt, et un peu au pif. Chacune semblait sans gravité à dix mille lignes. Aucune n’est sans gravité à dix millions. Quand le symptôme devient visible (requêtes lentes, migrations douloureuses, reporting qui prend plus de temps à générer qu’à lire, ORM qui ne se comporte plus bien), le choix d’origine qui en est la cause est enfoui sous trois ans de fonctionnalités qui en dépendent.

Cet article est un catalogue de travail des erreurs récurrentes de conception de schéma que je vois le plus souvent dans des produits digitaux en croissance. Il s’associe à l’article sur la plateforme web scalable, qui couvre les décisions de scalabilité opérationnelle un niveau au-dessus du schéma ; ici, l’angle est la couche modélisation en dessous, là où le coût de se tromper se manifeste deux ans plus tard et est rarement attribué à la cause d’origine.

Erreur 1 : des clés primaires faibles

Choisir la mauvaise clé primaire est l’erreur la plus difficile à corriger plus tard, parce que toutes les autres tables qui référencent celle-ci ont maintenant intégré ce choix. Trois mauvais choix reviennent.

Utiliser une clé naturelle (adresse e-mail, matricule, ISBN) comme clé primaire. L’utilisateur change son e-mail ; les clés étrangères sont silencieusement cassées. Le matricule est réattribué après un départ ; les références historiques pointent désormais vers la mauvaise personne. Les clés naturelles sont utiles comme contraintes d’unicité ; elles ne sont presque jamais utiles comme clés primaires.

Utiliser un entier auto-incrémenté séquentiel pour des entités qui seront exposées à l’extérieur. L’URL /orders/4521 dit au monde que la commande 4521 existe, qu’il y a au moins 4521 commandes, et que celui qui charge /orders/4520 verra la commande de quelqu’un d’autre si le contrôle d’accès est faible. Les IDs séquentiels fuient des informations métier et de la surface d’attaque ; les UUIDs (ou les ULIDs, quand l’ordre compte) coûtent quelques octets et suppriment la fuite.

Utiliser une clé primaire composite sur des colonnes mutables. La combinaison unique est correcte aujourd’hui, mais à la minute où l’une des colonnes devient mutable, les clés étrangères cassent. Une clé primaire de substitution avec une contrainte d’unicité sur la combinaison est presque toujours la forme plus propre.

La correction pour de nouvelles tables est simple : clés de substitution, immuables, opaques pour les consommateurs externes, avec les clés naturelles imposées comme contraintes d’unicité séparées. La correction pour les tables existantes qui se sont trompées est beaucoup plus coûteuse, ce qui est exactement pourquoi cette erreur mérite sa place en tête du catalogue.

Erreur 2 : des index par accident, pas par conception

Les index sont le travail de performance à plus fort effet de levier que l’équipe fera jamais, et ils sont aussi ce qu’on laisse le plus souvent au hasard. Deux modes d’échec reviennent.

Le premier, ce sont des index manquants sur les colonnes que les vraies requêtes utilisent réellement. Le schéma a des index sur les colonnes qui paraissaient importantes au moment de la création de la table, pas sur les colonnes que l’application filtre et joint maintenant. Six mois plus tard, le slow-query log est plein de scans complets sur des colonnes qui auraient dû être indexées dès le départ.

Le second, c’est sur-indexer toutes les colonnes « au cas où ». Chaque index accélère les lectures et ralentit les écritures. Une table avec douze index paie la taxe d’écriture douze fois à chaque insert. Les tables à fort volume d’écriture et à indexation désinvolte deviennent des goulots d’étranglement pour des raisons qui ressemblent à des problèmes de base mais sont en réalité des problèmes de discipline de schéma.

La correction, c’est de piloter l’indexation à partir des patterns de requêtes que l’application génère réellement, pas à partir de la lecture du schéma sur le papier. Une revue périodique du slow-query log et des statistiques d’usage des index attrape les deux problèmes tôt, pendant qu’ils sont encore peu coûteux à corriger.

Erreur 3 : normalisation prématurée, ou dénormalisation prématurée

Les deux extrêmes apparaissent régulièrement, souvent dans le même codebase, et les deux sont coûteux dans des sens opposés.

La normalisation prématurée casse chaque objet métier en un graphe de petites tables qu’aucune requête réelle ne va chercher en morceaux. Un « profil client » devient sept jointures. Une « page de paramètres » en devient douze. Le schéma est techniquement correct en troisième forme normale et opérationnellement un cauchemar ; l’application passe le plus clair de son temps à se rejoindre vers la forme dont elle avait besoin.

La dénormalisation prématurée fait l’inverse : des copies de champs prolifèrent à travers les tables pour des lectures qui n’ont pas été mesurées comme en ayant besoin, et désormais chaque écriture doit mettre à jour plusieurs endroits pour les garder cohérents. Le système a payé le coût de la dénormalisation sans le bénéfice, et les bugs d’incohérence qui suivent comptent parmi les plus difficiles à déboguer.

La correction, c’est de partir d’un schéma normalisé qui suit le domaine, puis de dénormaliser des champs précis quand la mesure (pas l’intuition) montre que le pattern de lecture le justifie. La dénormalisation est un arbitrage délibéré, pas un défaut.

Erreur 4 : les soft deletes partout par défaut

Ajouter deleted_at à toutes les tables parce que « on voudra peut-être récupérer les enregistrements supprimés » est une décision qui s’aggrave de trois façons douloureuses.

Les tables grossissent sans limite, y compris avec des enregistrements qui devraient avoir disparu. Chaque requête doit se souvenir de WHERE deleted_at IS NULL, et celle qui oublie renvoie des utilisateurs supprimés à des clients actifs. Les index deviennent plus gros et plus lents parce qu’ils indexent des enregistrements que personne ne veut vraiment. Et les demandes type RGPD « droit à l’oubli » deviennent des projets d’ingénierie parce que les enregistrements supprimés ne sont jamais réellement partis.

La correction, c’est de partir par défaut sur des suppressions dures et d’ajouter les soft deletes comme un choix délibéré pour les tables où la récupération est une vraie exigence produit. Quand les soft deletes sont le bon choix, imposez le filtre deleted_at au niveau de la couche de requête (une classe de requête de base, un scope d’ORM, une vue base de données) pour qu’aucune requête applicative n’ait à s’en souvenir.

Erreur 5 : les colonnes JSON comme fourre-tout

Les bases de données modernes supportent les colonnes JSON nativement, ce qui est une vraie capacité et une tentation irrésistible. La tentation, c’est de mettre tout ce qui « pourrait changer plus tard » dans un blob JSON et de qualifier le schéma de flexible.

Le coût se manifeste à trois endroits. Requêter le JSON est plus lent et plus laid que de requêter des colonnes typées, et le planificateur ne peut pas aider. Valider le JSON se fait dans le code applicatif, si tant est qu’il se fasse, et la qualité de schéma-comme-source-de-vérité est perdue. Migrer un champ hors du JSON vers sa propre colonne est beaucoup plus difficile que l’inverse, parce que tout lecteur du JSON doit être mis à jour.

La correction, c’est d’utiliser les colonnes JSON délibérément, pour des données dont la forme varie réellement (paramètres par tenant, payloads d’APIs tierces, logs d’audit), et de garder le reste typé. Le défaut pour un nouveau champ, c’est une colonne typée avec une contrainte ; la colonne JSON est l’exception, justifiée au cas par cas.

Erreur 6 : éviter les clés étrangères « pour la performance »

Un nombre étonnant de systèmes en croissance n’ont pas de clés étrangères, souvent parce que quelqu’un a lu un jour que « les clés étrangères nuisent à la performance d’écriture » ou parce que l’ORM ne les a pas générées par défaut. Le coût de s’en passer n’est pas visible jusqu’à ce que le schéma soit assez grand pour que l’intégrité référentielle compte.

Sans clés étrangères, les enregistrements orphelins s’accumulent silencieusement : une commande pointe vers un client qui n’existe plus, un commentaire vers un article supprimé. Les rapports affichent de mauvais totaux, les jointures renvoient des lignes sur lesquelles l’application crashe ensuite, et les scripts de nettoyage deviennent une corvée récurrente. Les suppressions en cascade doivent être implémentées dans le code applicatif, ce qui est fragile et facile à oublier quand un nouveau chemin vers la suppression est ajouté.

La correction, c’est d’utiliser les clés étrangères par défaut et de traiter l’absence de l’une comme une exception qui exige une justification, pas l’inverse. L’argument de performance d’écriture est réel pour des systèmes à très haut débit, mais la plupart des produits en croissance ne sont pas dans ce régime, et le bénéfice d’intégrité est important.

Erreur 7 : laisser l’ORM concevoir le schéma

Les ORMs sont des outils utiles qui produisent des schémas raisonnables pour des cas simples et des désastres accidentels pour tout le reste. Trois schémas reviennent.

Des requêtes N+1 cuites dans le schéma : l’ORM charge les relations en lazy, l’application itère, et une page qui devrait être une seule requête en devient trois cents. Le schéma et le pattern d’accès sont décalés, et l’ORM cache le décalage jusqu’à ce que la charge en production le révèle.

Le schéma comme effet de bord des classes de modèle : le schéma est ce que produit le générateur de migration, sans relecture. Les index manquent parce que personne ne les a ajoutés dans le fichier de modèle. Les contraintes sont absentes parce que la classe de modèle ne les a pas déclarées. Le schéma documente les choix désinvoltes de l’application plutôt qu’une conception délibérée.

Les associations polymorphes : l’ORM les offre, l’équipe les utilise, et la base perd la capacité d’imposer l’intégrité référentielle parce que la cible de la clé étrangère dépend d’une colonne de type. Ce qui devait être une abstraction propre devient une source récurrente d’enregistrements orphelins et de bugs de qualité de données.

La correction, c’est de traiter l’ORM comme un outil de requête et de mapping, pas comme un concepteur de schéma. Les migrations sont écrites ou relues délibérément, les index sont explicites, les contraintes sont déclarées, et les patterns N+1 sont attrapés en revue de code ou avec un middleware de comptage de requêtes avant qu’ils n’atteignent la production.

Conclusion

Les erreurs de conception de schéma sont prévisibles et elles s’aggravent. La même poignée de schémas revient produit après produit, et chacun est beaucoup moins cher à éviter en amont qu’à corriger plus tard, parce que le reste du système grandit autour de l’erreur avant que personne ne remarque le coût. La discipline n’est pas d’être parfait ; c’est de reconnaître les patterns récurrents, de traiter chaque décision de schéma comme une décision délibérée, et de payer le petit coût en amont pour que les fondations soient bonnes.

Le contexte plus large, dont la manière dont la conception de schéma se relie à la performance opérationnelle, à la stratégie d’indexation et aux systèmes de données qui poussent par-dessus, est rassemblé dans le cluster systèmes de données et performance. Et lorsque la question passe de « faisons-nous l’une de ces erreurs » à « nous reconnaissons le symptôme et il nous faut maintenant quelqu’un pour concevoir la stratégie d’index, le chemin de migration ou la correction du modèle de données », c’est exactement ce sur quoi mon accompagnement architecture de base de données et performance est centré.

- Haja Faniry

Services liés

Architecture de Bases de Données & Optimisation des Performances

Conception d’architecture de bases de données et optimisation des performances pour garantir des systèmes de données fiables et évolutifs.

Développement d'applications web

Développement d'applications web sur mesure pour entreprises, startups et organisations internationales.

Article précédent
Comment la performance web affecte les résultats business
Article suivant
Les signes que votre architecture de base de données doit être améliorée
Erreurs de conception de bases pour produits en croissance | Haja Faniry