Les fondations qu'on ne reprend pas

Sur Le Chantier, presque tout se modifie. Une cloison se déplace, une gaine se dévie, un revêtement se change à la livraison si le maître d'ouvrage insiste. Les fondations, non. Elles sont coulées avant tout le reste, et l'ouvrage entier repose dessus.

Quand il faut malgré tout y revenir, on parle de reprise en sous-œuvre : des semaines de travaux sous un bâtiment déjà occupé, pour un coût sans rapport avec celui du coulage initial.

Le magasin de données occupe exactement cette place dans un système. C'est la décision de la stack qui résiste le plus au changement, parce qu'elle ne concerne pas un composant isolé : elle décide de la forme des données, du code d'accès, des requêtes, des sauvegardes, du plan de reprise et des réflexes de toute l'équipe.

Le billet précédent a traité le back, le front et le mobile. Le troisième pan de la stack méritait son propre billet, parce que ses critères n'ont rien à voir avec ceux d'un langage.

Changer de framework front coûte un projet. Changer de magasin de données en production coûte un programme, avec une migration à mener sans perdre une ligne.

Ce qu'un magasin de données doit garantir

Avant de comparer des moteurs, il faut savoir ce qu'on leur demande. Un terrain se juge sur sa portance, sa stabilité et son drainage, pas sur la marque du béton.

Cinq garanties reviennent quel que soit le système.

La durabilité : ce qui a été confirmé comme écrit reste écrit, même si la machine s'arrête à la seconde suivante. La concurrence : plusieurs écritures simultanées ne se marchent pas dessus. L'intégrité : la donnée relue est celle qui a été écrite, et elle respecte les règles qu'on a posées.

Viennent ensuite l'accès, c'est-à-dire retrouver une donnée sans balayer l'ensemble du stock, et la capacité, c'est-à-dire encaisser le volume prévu et sa croissance sur la durée de vie du système.

Les deux grandes familles du marché répondent à ces cinq points, mais elles ne les priorisent pas de la même façon. Le relationnel met l'intégrité en premier et paie ce choix en rigidité. Le NoSQL met la capacité et la souplesse en premier et paie ce choix en garanties.

Personne ne gagne sur tous les tableaux. Le magasin de données que tu choisis dit surtout laquelle de ces cinq garanties tu acceptes d'assouplir.

Le relationnel : le plan de coffrage

Un radier en béton armé se ferraille et se coffre avant d'être coulé. Tout est calculé et déclaré à l'avance : les sections, les aciers, les réservations. Une fois pris, ça ne bouge plus.

C'est le contrat du relationnel. Les données vivent dans des tables, chaque colonne a un type déclaré, chaque ligne respecte le même gabarit, et les liens entre tables sont matérialisés par des clés étrangères que le moteur fait respecter.

CREATE TABLE commande (
  id          BIGSERIAL PRIMARY KEY,
  client_id   BIGINT       NOT NULL REFERENCES client(id),
  statut      VARCHAR(20)  NOT NULL,
  total_cents INTEGER      NOT NULL CHECK (total_cents >= 0),
  cree_le     TIMESTAMPTZ  NOT NULL DEFAULT now()
);

Trois règles sont posées en quatre lignes : une commande ne peut pas exister sans client valide, son total ne peut pas être négatif, sa date de création ne peut pas manquer. Ces règles tiennent même si un développeur pressé écrit un script d'import bâclé six mois plus tard.

Les quatre lettres d'ACID

La vraie force du relationnel tient dans la transaction, et dans les quatre propriétés que la littérature résume par ACID.

L'atomicité : un ensemble d'opérations passe entièrement ou pas du tout. La cohérence : à la fin de la transaction, toutes les contraintes déclarées sont respectées, sinon rien n'est validé. L'isolation : une transaction en cours ne voit pas les états intermédiaires des autres. La durabilité : une fois le COMMIT confirmé, l'écriture survit à une coupure de courant.

BEGIN;
UPDATE compte SET solde = solde - 25000 WHERE id = 42;
UPDATE compte SET solde = solde + 25000 WHERE id = 77;
INSERT INTO ecriture (compte_debit, compte_credit, montant_cents)
VALUES (42, 77, 25000);
COMMIT;

Sans atomicité, une panne entre les deux UPDATE fait disparaître 250 euros. Sans isolation, un relevé de compte lu au mauvais moment affiche l'argent nulle part, ou en double.

Propriété Ce qu'elle garantit Ce qui casse sans elle
Atomicité Tout ou rien sur un groupe d'opérations Des écritures partielles après une panne
Cohérence Les contraintes tiennent à chaque validation Des lignes orphelines, des totaux impossibles
Isolation Pas de lecture d'un état intermédiaire Des calculs faux sur des données en cours de modification
Durabilité L'écriture confirmée survit à l'arrêt brutal Une confirmation client sans commande enregistrée

L'isolation est celle qu'on croit binaire alors qu'elle est réglable. La norme SQL définit quatre niveaux, du plus permissif au plus strict, et les moteurs ne partagent pas le même défaut : PostgreSQL démarre en read committed, InnoDB en repeatable read. Deux applications identiques posées sur deux moteurs ne se comportent donc pas pareil sous forte concurrence.

Dernier atout, souvent sous-estimé : SQL. Un langage normalisé, enseigné partout, compris par les outils de reporting, les ETL et la moitié des gens du métier. Une question qu'on n'avait pas prévue trouve sa réponse en une requête, sans toucher au code applicatif.

Le relationnel te fait payer d'avance : il faut décrire la structure avant d'écrire la première ligne. En échange, il tient les règles à ta place, pour toujours.

Le NoSQL : les fondations qu'on étend

Quand le terrain est irrégulier et que le programme n'est pas figé, on ne coule pas un radier unique. On pose des pieux, et on en ajoute là où le bâtiment s'étend.

Le NoSQL part de cette logique. Le schéma n'est plus déclaré au moteur : chaque enregistrement porte sa propre structure, généralement en JSON, et deux enregistrements voisins peuvent différer.

{
  "_id": "cmd-2026-000481",
  "client": { "id": 77, "nom": "Durand", "ville": "Nantes" },
  "lignes": [
    { "sku": "TB-114", "libelle": "Tournevis isole", "qte": 2, "prix_cents": 1290 }
  ],
  "statut": "expediee",
  "cree_le": "2026-03-14T09:21:00Z"
}

La commande entière tient dans un document. Une lecture suffit, sans jointure, et le client est recopié dans la commande plutôt que référencé. C'est de la dénormalisation assumée : on duplique pour lire vite.

Le prix se paie côté écriture. Si Madame Durand déménage, son nom de ville reste figé dans les milliers de documents déjà écrits. Parfois c'est un bug. Parfois c'est exactement ce qu'on veut, parce qu'une commande expédiée doit conserver l'adresse du jour de l'expédition.

Les quatre familles

« NoSQL » n'est pas une technologie, c'est une étiquette posée sur quatre modèles très différents.

Famille Modèle de stockage Ce qu'elle sert bien Exemples
Documents Documents JSON indexables Entités lues et écrites d'un bloc MongoDB, Couchbase
Clé-valeur Une clé, une valeur Accès direct, sessions, cache Redis, DynamoDB
Colonnes larges Lignes partitionnées par clé Écriture massive, séries temporelles Cassandra, HBase
Graphe Nœuds et relations de premier ordre Parcours de relations profondes Neo4j

Après le schéma souple, le second trait commun de ces familles est le passage à l'échelle horizontal. Ces moteurs sont conçus pour répartir la donnée sur plusieurs machines par partitionnement, avec des réplicas, plutôt que pour grossir sur une seule.

Cette distribution a une conséquence directe, formalisée par le théorème CAP : en cas de partition réseau entre les nœuds, il faut choisir entre refuser de répondre et répondre avec une donnée potentiellement périmée. Beaucoup de moteurs NoSQL choisissent de répondre, d'où la cohérence à terme : l'écriture se propage, et pendant un court instant, deux lecteurs peuvent voir deux valeurs différentes.

Pour un compteur de vues, c'est sans importance. Pour un solde bancaire, c'est un incident.

Deux nuances honnêtes, parce que la frontière s'est brouillée depuis dix ans. PostgreSQL et MySQL stockent et indexent du JSON nativement. MongoDB propose des transactions multi-documents depuis sa version 4.0. Le clivage s'est déplacé : il porte moins sur les fonctionnalités que sur ce que le moteur garantit par défaut, et sur ce qu'il fait quand le volume déborde d'une machine.

Le NoSQL ne supprime pas le schéma, il le déplace dans ton code. Ce qui n'est plus déclaré au moteur devra être vérifié par l'application, sur chaque chemin d'écriture, sans exception.

Le vrai critère de choix

Sur Le Chantier, ce n'est ni le maçon ni l'architecte qui décide du type de fondation : c'est l'étude de sol, croisée avec les charges que l'ouvrage va transmettre. La préférence de l'entreprise ne pèse rien face à une nappe phréatique à deux mètres.

Le raisonnement est le même ici. Six questions suffisent à trancher, et aucune ne porte sur la popularité du moteur.

Critère La question à poser Penche relationnel Penche NoSQL
Structure Tous les enregistrements ont-ils les mêmes champs ? Structure stable, typée, connue Champs variables d'un objet à l'autre
Relations Combien d'entités croiser pour répondre à une question métier ? Les données se croisent en permanence Chaque entité se lit d'un bloc
Cohérence Une lecture peut-elle renvoyer une valeur vieille d'une seconde ? Non, transaction obligatoire Oui, sans conséquence métier
Volumétrie Combien de téraoctets dans cinq ans, et combien d'écritures par seconde ? Ce qu'un serveur et ses réplicas encaissent Au-delà, avec partitionnement natif
Requêtes Les questions posées sont-elles connues d'avance ? Requêtes ad hoc, reporting, analyse Chemins d'accès peu nombreux et stables
Évolution À quelle fréquence la forme des données change-t-elle ? Changements rares, migration assumée Changements fréquents, versions coexistantes

La ligne « cohérence » est celle qui ferme le débat le plus vite. Demande au métier, pas à l'équipe technique, si une donnée affichée peut être fausse pendant une seconde. La réponse est franche dans les deux sens, et elle élimine la moitié des options.

La ligne « requêtes » est celle qu'on oublie le plus souvent. Un magasin documentaire répond très bien aux questions prévues quand on a conçu ses documents, et très mal à celle que la direction posera dans un an.

Un système réel a rarement un seul profil de données. Rien n'interdit deux magasins, à condition d'assumer que ce sont aussi deux exploitations, deux sauvegardes et deux compétences à maintenir.

Ce que la volumétrie change concrètement

L'étude de sol donne des charges admissibles, en tonnes par mètre carré. Sans ce chiffre, le calcul de fondation n'existe pas. Côté données, l'équivalent se calcule au brouillon, et il se calcule tôt.

Prenons une plateforme de commande, avec les chiffres arrachés au métier lors de l'étape des exigences non-fonctionnelles.

  • 12 000 commandes par jour, à environ 2 Ko par commande, en-tête et lignes comprises.
  • 40 événements de suivi par commande (statut, transporteur, notification), environ 1,5 Ko chacun.
  • Reprise de trois ans d'historique au démarrage.

Le calcul des commandes : 12 000 × 2 Ko, soit 24 Mo par jour, environ 8,8 Go par an. Sur huit ans d'exploitation, on reste sous les 80 Go. C'est un volume de poche pour n'importe quel moteur relationnel.

Le calcul des événements : 12 000 × 40 = 480 000 événements par jour, à 1,5 Ko, soit environ 720 Mo par jour et 263 Go par an. Avec la reprise des trois années d'historique, le système démarre déjà avec 0,8 To, et franchit les 2 To à cinq ans.

Deux profils, deux ordres de grandeur, deux réponses. Le premier appelle un relationnel classique, avec ses transactions. Le second appelle un magasin d'événements en ajout seul (append-only), partitionné par date, avec une politique de rétention décidée dès le premier jour.

flowchart LR A["Service commande"] -->|"écriture transactionnelle"| B["Base relationnelle
commandes, clients, stock"] A -->|"événement de suivi"| C["Magasin d'événements
append-only, partitionné"] B --> D["Requêtes métier
et reporting"] C --> E["Historique, rejeu,
analyse de parcours"] classDef rel fill:#b5651d,stroke:#8a4d16,color:#f5f2ec; classDef nosql fill:#2c3338,stroke:#1f2428,color:#f5f2ec; classDef app fill:#f5f2ec,stroke:#b5651d,color:#1a1a1a; class B rel; class C nosql; class A,D,E app;

Le volume ne change pas que le choix du moteur. Il change la façon d'écrire les requêtes, la planification du stockage et la durée des opérations d'exploitation.

Ce que ça touche À 10 Go À 10 To
Choix du moteur Presque indifférent Contrainte principale
Écriture des requêtes Un index manquant se voit peu Un index manquant fait tomber le service
Stockage Un disque, une instance Partitionnement, archivage, rétention
Restauration Quelques minutes Plusieurs heures, à tester pour de vrai
Coût Ligne négligeable Poste de budget à défendre
Une sauvegarde dont personne n'a chronométré la restauration n'est pas une sauvegarde. À 10 To, la question « combien de temps pour repartir » est une exigence, pas un détail d'exploitation.

Le piège du choix par défaut

Une entreprise de gros œuvre peut couler le même radier sur tous ses chantiers, parce que c'est ce qu'elle sait faire. Sur les trois quarts des terrains, ça tient très bien. Sur le quatrième, ça fissure au bout de deux hivers.

Le piège se referme dans les deux sens. Le réflexe « on met du Postgres, on verra plus tard » ignore une volumétrie annoncée qui débordera d'une machine en dix-huit mois. Le réflexe inverse, « on part sur du NoSQL parce que ça scale », embarque une comptabilité dans un moteur qui ne garantit pas ce que la comptabilité exige.

Le second est le plus coûteux, parce qu'il se découvre tard. La cohérence à terme ne provoque pas d'erreur : elle produit des chiffres légèrement faux, qu'on remarque au premier rapprochement bancaire.

flowchart TD A["Profil des données à stocker"] --> B{"Structure stable et connue ?"} B -->|Non| N1["Piste documentaire
schéma porté par le code"] B -->|Oui| C{"Transactions sur
plusieurs entités ?"} C -->|Oui| R1["Relationnel"] C -->|Non| D{"Volumétrie au-delà
d'un serveur unique ?"} D -->|Non| R1 D -->|Oui| E{"Cohérence immédiate
exigée par le métier ?"} E -->|Oui| R2["Relationnel partitionné
ou distribué, coût assumé"] E -->|Non| N2["NoSQL distribué
clé-valeur ou colonnes larges"] N1 --> F["Vérifier les requêtes attendues
avant de figer le modèle"] N2 --> F R1 --> F R2 --> F classDef rel fill:#b5651d,stroke:#8a4d16,color:#f5f2ec; classDef nosql fill:#2c3338,stroke:#1f2428,color:#f5f2ec; classDef q fill:#f5f2ec,stroke:#b5651d,color:#1a1a1a; class R1,R2 rel; class N1,N2 nosql; class A,B,C,D,E,F q;

L'arbre se termine toujours au même endroit : lister les requêtes que le système devra servir. Un modèle de données se valide contre des questions réelles, pas contre un diagramme d'entités.

Le choix par défaut n'est pas une faute en soi. La faute, c'est de ne pas savoir qu'on l'a fait, et de ne l'avoir écrit nulle part.

Scénario B : Uber, de Postgres à MySQL

En juillet 2016, l'équipe d'ingénierie d'Uber publie « Why Uber Engineering Switched from Postgres to MySQL ». Le billet détaille pourquoi une infrastructure bâtie sur PostgreSQL a été déplacée vers MySQL, sur lequel l'entreprise a construit sa propre couche de partitionnement, Schemaless.

Le raisonnement porte sur l'organisation physique des données, pas sur les fonctionnalités SQL.

Premier point : l'amplification d'écriture. Dans PostgreSQL, une mise à jour crée une nouvelle version de la ligne, et les index doivent pointer vers cette nouvelle version. Sur des tables portant beaucoup d'index, une modification d'un seul champ entraîne un travail bien supérieur à ce que la requête laisse imaginer. Dans InnoDB, les index secondaires référencent la clé primaire, donc seuls les index concernés par les colonnes modifiées sont touchés.

Deuxième point : la réplication. La réplication en flux de PostgreSQL transporte le journal d'écriture au niveau physique. L'amplification d'écriture se répercute donc sur le volume répliqué, ce qui pèse entre centres de données. Le billet rapporte aussi un incident de corruption survenu lors d'une bascule de maître, causé par un bug de Postgres 9.2 corrigé depuis, et la difficulté à changer de version majeure quand la réplication est physique.

Troisième point : la gestion des connexions. PostgreSQL sert historiquement chaque connexion par un processus dédié, avec le coût mémoire correspondant, là où MySQL utilise des fils d'exécution. Au nombre de connexions qu'Uber devait tenir, ce détail d'implémentation devient un problème de dimensionnement.

Deux précautions de lecture, parce que ce billet a été discuté publiquement à sa sortie. La communauté PostgreSQL a répondu, notamment sur les mises à jour dites HOT, qui évitent de toucher les index quand aucune colonne indexée ne change et que la page a de la place. Et il s'agit du profil d'accès d'Uber en 2016, pas d'un classement général entre deux moteurs.

C'est précisément ce qui rend le cas utile ici. Les deux moteurs comparés sont relationnels et parlent le même langage. La décision s'est jouée sur le profil d'écriture, la topologie de réplication et le nombre de connexions, c'est-à-dire sur des chiffres tirés de l'exploitation réelle.

Aucun moteur n'est « meilleur ». Il y a un profil d'accès, une volumétrie, une topologie, et un moteur qui s'y adapte mieux que les autres. Le reste est de la préférence.

Points clés

  • Le magasin de données est la fondation. C'est la décision de la stack la plus coûteuse à reprendre en production, parce qu'elle engage la donnée, le code d'accès et l'exploitation.
  • ACID est un service rendu, pas une contrainte gratuite. Atomicité, cohérence, isolation, durabilité : ce que le moteur ne garantit pas, ton code devra le garantir sur chaque chemin d'écriture.
  • Six critères tranchent. Structure, relations, cohérence, volumétrie, requêtes attendues, fréquence d'évolution du schéma. La popularité du moteur n'en fait pas partie.
  • La volumétrie se calcule au brouillon, tôt. Volume du jour un, croissance annuelle, durée de rétention. Le résultat change le moteur, les requêtes et le temps de restauration.
  • Uber a tranché sur son profil d'accès. Deux moteurs relationnels, un choix décidé par l'amplification d'écriture, la réplication et les connexions, pas par un classement.

Dans la série

Palier 2 : Dresser les plans. Domaine : Data.

Pour aller plus loin