Bonnes pratiques de schema de base de donnees

Bonnes pratiques de schema de base de donnees

Le schema est la partie d’un systeme la plus difficile a changer une fois pleine de donnees. Vous pouvez reecrire un service en un week-end. Migrer une table d’un milliard de lignes sans interruption est un projet. Je passe donc du temps reel sur le schema en amont, car le cout d’une erreur ne fait que grandir. Voici les decisions que je ne regrette pas.

Laissez la base imposer les regles

Le code applicatif n’est pas l’endroit pour garantir l’integrite des donnees, car il y a toujours un autre chemin d’entree : un script de migration, une correction manuelle, un second service, un developpeur dans une console. La base est le seul point de passage par lequel tout transite, c’est donc la que les regles appartiennent. J’utilise NOT NULL agressivement, les cles etrangeres pour imposer les relations, les contraintes d’unicite pour empecher les doublons, et les contraintes de verification pour les plages de valeurs.

CREATE TABLE orders (
  id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  customer_id bigint NOT NULL REFERENCES customers(id),
  status      text   NOT NULL DEFAULT 'pending'
              CHECK (status IN ('pending','paid','shipped','cancelled')),
  total_cents integer NOT NULL CHECK (total_cents >= 0),
  created_at  timestamptz NOT NULL DEFAULT now()
);

Chaque contrainte ici est un bug qui ne pourra jamais atteindre la production. Un statut « shippd » est rejete au moment de l’ecriture au lieu de casser un rapport trois semaines plus tard.

Normalisez d’abord, denormalisez sur preuve

Je commence normalise : chaque fait vit a un seul endroit. Une donnee dupliquee est une verite dupliquee, et les copies divergent des que quelqu’un en met une a jour et oublie l’autre. La normalisation garde les ecritures simples et la justesse peu couteuse.

La denormalisation est une optimisation de performance, et comme toute optimisation je veux une mesure avant de la faire. Quand une requete precise est vraiment trop lente et que le profil pointe les jointures, alors je vais cacher une valeur calculee ou dupliquer une colonne, sachant que je prends en charge la synchronisation des copies. Le faire par anticipation, c’est ainsi qu’on obtient un schema plein de champs auxquels personne ne fait confiance.

Choisissez cles et types deliberement

Chaque table recoit une cle primaire synthetique, en general un bigint identite ou un UUID. J’evite les cles naturelles comme les adresses e-mail en cle primaire, car la seule chose que vous pouvez promettre d’une cle naturelle, c’est qu’elle changera, et changer une cle primaire referencee partout est un calvaire. Utilisez des types qui veulent dire quelque chose :

  • Stockez l’argent en centimes entiers, jamais en flottants. Le flottant et la monnaie sont de vieux ennemis.
  • Utilisez toujours un horodatage avec fuseau pour le temps, et stockez en UTC.
  • Prenez un enum natif ou une contrainte de verification au lieu de champs de statut en texte libre.
  • Utilisez le vrai type JSON de la base pour des donnees vraiment non structurees, pas un bloc de texte.

Indexez pour vos lectures, mais connaissez le cout

Un index rend les lectures rapides et les ecritures un peu plus lentes, et il prend de la place. J’indexe les cles etrangeres, les colonnes que je filtre regulierement, et les colonnes par lesquelles je trie. Je n’indexe pas tout, car un index inutilise n’est que du poids sur chaque insertion. La facon de savoir, c’est de regarder : la plupart des bases vous diront quels index restent inutilises, et ceux-la sont candidats au retrait.

Les index composites valent la peine d’etre compris, car l’ordre des colonnes compte. Un index sur (customer_id, created_at) aide une requete qui filtre par client et trie par date, mais ne fait rien pour une requete qui ne filtre que par date. Le meme etat d’esprit d’observabilite que j’ai decrit dans les bonnes pratiques d’observabilite s’applique ici : mesurez les vrais schemas de requete avant de deviner.

Traitez les migrations comme du code

Chaque changement de schema passe par un fichier de migration, verse dans la gestion de versions, relu comme tout autre changement. Aucune instruction ALTER manuelle lancee a la main en production, jamais, car l’environnement suivant ne l’aura pas et vous passerez une journee a chasser la difference. La meme discipline de relecture que dans les bonnes pratiques de revue de code s’applique, avec un soin supplementaire, puisqu’une mauvaise migration peut verrouiller une table ou perdre des donnees.

  • Faites des migrations vers l’avant et additives quand vous le pouvez. Ajoutez une colonne, remplissez-la, puis retirez l’ancienne plus tard dans une etape separee.
  • Evitez les changements qui prennent un long verrou exclusif sur une grande table en service.
  • Testez la migration sur une copie de donnees a la taille de la production avant de la lancer pour de vrai.

Un schema que vous pouvez faire evoluer en securite vaut plus qu’un schema parfait que vous avez peur de toucher. Construisez pour le changement, car le changement est la seule certitude.

Commentaires

Laisser un commentaire

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