Jour 6 Day 6 · mardi 4 août 2026 Tuesday 4 August 2026 Data Fondamental
SQL vs NoSQL SQL vs NoSQL
ACID, CAP, sharding, index, N+1 : le débat SQL vs NoSQL est un classique absolu de l'entretien — et l'occasion rêvée de montrer qu'on raisonne en trade-offs plutôt qu'en slogans. ACID, CAP, sharding, indexes, N+1: the SQL vs NoSQL debate is an absolute interview classic — and the perfect opportunity to show you reason in trade-offs rather than slogans.
L’essentiel
Le modèle relationnel structure les données en tables au schéma explicite (colonnes typées, contraintes), requêtées en SQL déclaratif, recomposées par jointures, protégées par des transactions ACID. Il domine depuis 40 ans parce qu’il est d’une polyvalence redoutable.
« NoSQL » n’est pas une technologie mais une étiquette : des familles très différentes, nées dans les années 2000 chez Google et Amazon pour des besoins que le relationnel de l’époque servait mal — scaling horizontal massif, schéma flexible, modèles spécifiques (graphe, clé-valeur).
La question d’entretien n’est jamais « lequel est le meilleur » mais « lequel pour quel problème ». Réponse courte défendable : Postgres par défaut, du NoSQL quand un pattern d’accès précis le justifie — et savoir dire lequel.
Comment ça marche
Le relationnel repose sur la normalisation : chaque fait stocké une seule fois (une adresse se modifie à un endroit, pas douze), au prix de jointures pour recomposer les données. Les transactions garantissent ACID :
- Atomicity — tout ou rien : un virement débite ET crédite, jamais l’un sans l’autre.
- Consistency — les contraintes (clés étrangères, unicité, checks) restent vraies après chaque transaction.
- Isolation — les transactions concurrentes ne voient pas les états intermédiaires des autres (niveaux : read committed, repeatable read, serializable — plus c’est strict, plus c’est cher).
- Durability — après commit, la donnée survit à un crash (write-ahead log).
Les quatre familles NoSQL :
| Famille | Exemples | Idée clé | Cas d’usage typiques |
|---|---|---|---|
| Document | MongoDB | JSON imbriqués : ce qui est lu ensemble est stocké ensemble | Catalogues, profils, schéma mouvant |
| Clé-valeur | Redis, DynamoDB | GET/PUT par clé, latence minimale, pas de requêtes riches | Cache, sessions, paniers |
| Colonnes larges | Cassandra | Écritures massives distribuées ; tables modélisées d’après les requêtes | Time series, IoT, ingestion |
| Graphe | Neo4j | Nœuds et relations de première classe, traversées profondes efficaces (là où le SQL empile des self-joins) | Réseau social, recommandation, fraude |
La différence de philosophie tient en deux requêtes — recomposer par jointure vs lire un agrégat :
-- Relationnel : normalisé, chaque fait à un seul endroit
SELECT o.id, o.total, c.name, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_id -- recomposition
WHERE o.id = 42;
// Document : l'agrégat entier, lu en un seul accès
db.orders.findOne({ _id: 42 })
// → { _id: 42, total: 99.9,
// customer: { name: "Ada", email: "ada@ex.io" } }
// Dénormalisé : si l'email change, le mettre à jour partout
Le théorème CAP, sans le massacrer : dans un système distribué, quand une partition réseau survient (P — ce n’est pas un choix, ça arrive), il faut trancher entre Consistency (refuser de répondre plutôt que risquer une réponse fausse) et Availability (répondre, quitte à diverger puis réconcilier). Le « choisis 2 parmi 3 » est trompeur : le dilemme n’existe que pendant une partition ; le reste du temps, le vrai trade-off est latence contre cohérence (extension PACELC). Et CAP ne concerne pas une base sur un seul nœud.
Cohérence éventuelle : les répliques convergent « à terme » ; entre-temps, une lecture peut renvoyer une valeur périmée (le like qui disparaît puis revient). Beaucoup de systèmes offrent des garanties intermédiaires (read-your-writes, cohérence par session) ou réglables par requête (Cassandra : QUORUM vs ONE).
Scaling : vertical (machine plus grosse — simple, efficace longtemps, mais plafonne et coûte cher au sommet) vs horizontal (plus de machines). Deux outils : la réplication (leader-follower : les lectures se distribuent sur les replicas — attention au replica lag) et le sharding (partitionner les données par une shard key ; une mauvaise clé crée un hot shard, et les requêtes cross-shard coûtent cher). Le relationnel se réplique très bien ; c’est le sharding transactionnel qui est difficile (Citus, Vitess existent pour ça). Les deux outils en image :
Réplication (scaler les lectures)
écritures
│
┌────▼───┐
│ Leader │
└────┬───┘
async ────┤──── (replica lag !)
┌─────────┴─────────┐
┌────▼────┐ ┌────▼────┐
│Replica 1│ │Replica 2│ ◀── lectures
└─────────┘ └─────────┘
Sharding (scaler données & écritures, par shard key)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ shard A │ │ shard B │ │ shard C │
│ (a-h) │ │ (i-p) │ │ (q-z) │
└─────────┘ └─────────┘ └─────────┘
⚠️ Replica lag — l’utilisateur modifie son profil (écriture sur le leader), la page se recharge (lecture sur un replica en retard)… et affiche l’ancienne valeur. C’est exactement ce que les garanties « read-your-writes » corrigent : lire sur le leader juste après sa propre écriture.
Concepts clés à maîtriser
- Index B-tree : arbre équilibré, recherche en O(log n), sert l’égalité, les ranges (
WHERE created_at > …) et le tri. C’est le défaut de tous les SGBD relationnels. - Quand un index ne sert à rien : colonne à faible sélectivité (un booléen — le planner préfère un scan), fonction appliquée à la colonne (
WHERE lower(email) = …sans index d’expression),LIKE '%foo'(wildcard en tête), colonnes hors du préfixe d’un index composite.
💡 Un index n’est jamais gratuit — chaque INSERT/UPDATE doit le maintenir, et l’optimiseur l’ignore s’il ne filtre pas assez. On indexe d’après les requêtes réelles (WHERE, JOIN, ORDER BY), et on prouve avec
EXPLAIN ANALYZE— avant et après.
- ORM et N+1 : charger une liste (1 requête) puis accéder à une relation en lazy loading dans une boucle (N requêtes). Symptôme : page lente, log rempli de requêtes identiques. Fix : eager loading (
JOIN FETCH,include,select_related/prefetch_related). - Dénormalisation : dupliquer sciemment pour lire vite ; se paie à l’écriture (tenir les copies à jour). Le documentaire le fait par design ; le relationnel peut le faire ponctuellement (colonne calculée, vue matérialisée).
🎤 En entretien — « Postgres + JSONB » est la réponse pragmatique à 80 % des « il nous faut MongoDB » : une colonne JSON binaire, indexable (GIN), requêtable — la flexibilité documentaire dans un moteur ACID. Colonnes relationnelles pour le structuré, JSONB pour le variable. Formulé ainsi, on montre qu’on raisonne en trade-offs, pas en logos.
En entretien
« SQL ou NoSQL pour ce projet ? » — Dérouler une méthode, pas un slogan : quels patterns d’accès ? besoin de transactions multi-entités ? volume et croissance réels ? relations riches ? Conclure : relationnel par défaut ; clé-valeur pour du cache/session, document pour des agrégats autonomes à schéma mouvant, colonnes larges pour de l’ingestion massive, graphe pour des traversées profondes.
« Explique ACID avec un exemple. » — Le virement bancaire : débit + crédit atomiques ; contrainte de solde respectée ; deux virements concurrents isolés ; après commit, un crash ne perd rien. Bonus : citer les niveaux d’isolation et le fait que read committed (défaut Postgres) autorise certaines anomalies.
« C’est quoi le théorème CAP ? » — L’énoncé correct : pendant une partition réseau, choix entre cohérence et disponibilité ; en dehors, le trade-off est latence vs cohérence (PACELC). Bonus : beaucoup de bases sont réglables (Cassandra par niveau de cohérence par requête, MongoDB via write/read concern).
« Pourquoi ne pas mettre un index sur toutes les colonnes ? » — Chaque index a un coût en écriture et en stockage, et l’optimiseur n’utilise que ceux qui filtrent vraiment. On indexe d’après les requêtes réelles (WHERE, JOIN, ORDER BY), on vérifie avec EXPLAIN, on supprime les index inutilisés.
« C’est quoi le problème N+1 ? » — 1 requête pour la liste, puis 1 par élément à cause du lazy loading de l’ORM. Le détecter (logs SQL, APM), le corriger (eager loading), et retenir la leçon : l’ORM cache le SQL mais ne dispense pas de le comprendre.
Pièges & idées reçues
- « NoSQL = pas de schéma » — le schéma existe toujours ; il est juste implicite et éparpillé dans le code (schema-on-read). Le documentaire déplace la rigueur, il ne la supprime pas.
- « SQL ne scale pas » — read replicas, partitionnement, Vitess/Citus ; un Postgres bien indexé encaisse des dizaines de milliers de requêtes/s sur une seule machine. La plupart des projets n’atteindront jamais sa limite.
- « MongoDB n’a pas de transactions » — obsolète : transactions multi-documents depuis la 4.0 (avec un coût). Et les écritures sur un seul document ont toujours été atomiques.
- Choisir la shard key à la légère — la changer après coup signifie re-partitionner les données à chaud. C’est LA décision de design d’un système shardé.
- Invoquer CAP pour tout — une base mono-nœud n’est pas concernée ; et « AP » ne veut pas dire « perd des données », mais « répond pendant la partition, converge après ».
Pour aller plus loin
- Documentation Postgres — Indexes et JSON Types
- Use The Index, Luke — le meilleur tutoriel qui existe sur les index SQL
- MongoDB — Data Modeling : apprendre à penser en agrégats
- Martin Kleppmann, Designing Data-Intensive Applications — LA référence du domaine ; et jepsen.io pour ce que valent vraiment les garanties des bases distribuées
The essentials
The relational model structures data into tables with an explicit schema (typed columns, constraints), queried with declarative SQL, recomposed through joins, protected by ACID transactions. It has dominated for 40 years because it is formidably versatile.
“NoSQL” is not a technology but a label: very different families, born in the 2000s at Google and Amazon for needs the relational model of the time served poorly — massive horizontal scaling, flexible schemas, specific models (graph, key-value).
The interview question is never “which one is better” but “which one for which problem”. A short, defensible answer: Postgres by default, NoSQL when a specific access pattern justifies it — and being able to say which one.
How it works
The relational model relies on normalization: every fact stored once (an address is updated in one place, not twelve), at the cost of joins to recompose data. Transactions guarantee ACID:
- Atomicity — all or nothing: a transfer debits AND credits, never one without the other.
- Consistency — constraints (foreign keys, uniqueness, checks) remain true after each transaction.
- Isolation — concurrent transactions don’t see each other’s intermediate states (levels: read committed, repeatable read, serializable — the stricter, the more expensive).
- Durability — after commit, data survives a crash (write-ahead log).
The four NoSQL families:
| Family | Examples | Key idea | Typical use cases |
|---|---|---|---|
| Document | MongoDB | Nested JSON: what is read together is stored together | Catalogs, profiles, shifting schema |
| Key-value | Redis, DynamoDB | GET/PUT by key, minimal latency, no rich queries | Cache, sessions, carts |
| Wide-column | Cassandra | Massive distributed writes; tables modeled from your queries | Time series, IoT, ingestion |
| Graph | Neo4j | First-class nodes and relationships, efficient deep traversals (where SQL piles up self-joins) | Social network, recommendations, fraud |
The difference in philosophy fits in two queries — recomposing through a join vs reading an aggregate:
-- Relational: normalized, every fact in one place
SELECT o.id, o.total, c.name, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_id -- recomposition
WHERE o.id = 42;
// Document: the whole aggregate, read in a single access
db.orders.findOne({ _id: 42 })
// → { _id: 42, total: 99.9,
// customer: { name: "Ada", email: "ada@ex.io" } }
// Denormalized: if the email changes, update it everywhere
The CAP theorem, without butchering it: in a distributed system, when a network partition occurs (P — not a choice, it happens), you must choose between Consistency (refuse to answer rather than risk a wrong answer) and Availability (answer, even if it means diverging and reconciling later). The “pick 2 out of 3” framing is misleading: the dilemma only exists during a partition; the rest of the time, the real trade-off is latency versus consistency (the PACELC extension). And CAP doesn’t apply to a single-node database.
Eventual consistency: replicas converge “eventually”; in the meantime, a read can return a stale value (the like that disappears then comes back). Many systems offer intermediate guarantees (read-your-writes, session consistency) or per-request tuning (Cassandra: QUORUM vs ONE).
Scaling: vertical (a bigger machine — simple, effective for a long time, but it plateaus and gets expensive at the top) vs horizontal (more machines). Two tools: replication (leader-follower: reads spread across replicas — beware of replica lag) and sharding (partitioning data by a shard key; a bad key creates a hot shard, and cross-shard queries are expensive). The relational model replicates very well; it’s transactional sharding that is hard (Citus and Vitess exist for that). Both tools at a glance:
Replication (scale reads)
writes
│
┌────▼───┐
│ Leader │
└────┬───┘
async ────┤──── (replica lag!)
┌─────────┴─────────┐
┌────▼────┐ ┌────▼────┐
│Replica 1│ │Replica 2│ ◀── reads
└─────────┘ └─────────┘
Sharding (scale data & writes, by shard key)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ shard A │ │ shard B │ │ shard C │
│ (a-h) │ │ (i-p) │ │ (q-z) │
└─────────┘ └─────────┘ └─────────┘
⚠️ Replica lag — the user updates their profile (write on the leader), the page reloads (read from a lagging replica)… and shows the old value. This is exactly what “read-your-writes” guarantees fix: read from the leader right after your own write.
Key concepts to master
- B-tree index: a balanced tree, O(log n) lookup, serves equality, ranges (
WHERE created_at > …) and sorting. It’s the default in every relational DBMS. - When an index is useless: a low-selectivity column (a boolean — the planner prefers a scan), a function applied to the column (
WHERE lower(email) = …without an expression index),LIKE '%foo'(leading wildcard), columns outside the prefix of a composite index.
💡 An index is never free — every INSERT/UPDATE must maintain it, and the optimizer ignores it if it doesn’t filter enough. Index based on real queries (WHERE, JOIN, ORDER BY), and prove it with
EXPLAIN ANALYZE— before and after.
- ORMs and N+1: load a list (1 query) then access a relation via lazy loading in a loop (N queries). Symptom: slow page, log full of identical queries. Fix: eager loading (
JOIN FETCH,include,select_related/prefetch_related). - Denormalization: deliberately duplicating to read fast; you pay for it at write time (keeping copies in sync). Document stores do it by design; relational stores can do it selectively (computed column, materialized view).
🎤 In an interview — “Postgres + JSONB” is the pragmatic answer to 80% of “we need MongoDB”: a binary JSON column, indexable (GIN), queryable — document flexibility inside an ACID engine. Relational columns for the structured part, JSONB for the variable part. Phrasing it this way shows you reason in trade-offs, not logos.
In an interview
“SQL or NoSQL for this project?” — Walk through a method, not a slogan: what access patterns? multi-entity transaction needs? actual volume and growth? rich relationships? Conclude: relational by default; key-value for cache/sessions, document for self-contained aggregates with a shifting schema, wide-column for massive ingestion, graph for deep traversals.
“Explain ACID with an example.” — The bank transfer: atomic debit + credit; balance constraint upheld; two concurrent transfers isolated; after commit, a crash loses nothing. Bonus: mention isolation levels and the fact that read committed (Postgres default) allows certain anomalies.
“What is the CAP theorem?” — The correct statement: during a network partition, a choice between consistency and availability; outside of one, the trade-off is latency vs consistency (PACELC). Bonus: many databases are tunable (Cassandra per-request consistency levels, MongoDB via write/read concern).
“Why not put an index on every column?” — Every index costs writes and storage, and the optimizer only uses those that actually filter. Index based on real queries (WHERE, JOIN, ORDER BY), verify with EXPLAIN, drop unused indexes.
“What is the N+1 problem?” — 1 query for the list, then 1 per item due to the ORM’s lazy loading. Detect it (SQL logs, APM), fix it (eager loading), and remember the lesson: the ORM hides SQL but doesn’t excuse you from understanding it.
Pitfalls & misconceptions
- “NoSQL = no schema” — the schema always exists; it’s just implicit and scattered across the code (schema-on-read). Document stores relocate the rigor, they don’t remove it.
- “SQL doesn’t scale” — read replicas, partitioning, Vitess/Citus; a well-indexed Postgres handles tens of thousands of requests/s on a single machine. Most projects will never hit its limit.
- “MongoDB has no transactions” — outdated: multi-document transactions since 4.0 (at a cost). And single-document writes have always been atomic.
- Choosing the shard key carelessly — changing it afterwards means re-partitioning data live. It is THE design decision of a sharded system.
- Invoking CAP for everything — a single-node database isn’t concerned; and “AP” doesn’t mean “loses data”, it means “answers during the partition, converges afterwards”.
Going further
- Postgres documentation — Indexes and JSON Types
- Use The Index, Luke — the best tutorial in existence on SQL indexes
- MongoDB — Data Modeling: learning to think in aggregates
- Martin Kleppmann, Designing Data-Intensive Applications — THE reference in the field; and jepsen.io for what distributed databases’ guarantees are really worth