Skip to content

Module 3 — PostgreSQL en base multi-modèle

Bonne lecture et bon apprentissage ! Junior TSAFACK – 13/09/2026 Temps de lecture estimé : 10 minutes


Le Module 1 a présenté 7 familles de bases de données. Le réflexe naturel est d’en déduire qu’il faut 7 technologies différentes pour les couvrir. Dans la pratique, ce n’est pas toujours vrai : PostgreSQL, grâce à son système d’extensions, peut couvrir plusieurs de ces familles à lui seul — sans changer de système, sans synchroniser deux bases séparées, avec un seul outil de sauvegarde et une seule expertise à maintenir dans l’équipe. Ce module teste cette affirmation plutôt que de la répéter.

Le type JSONB de PostgreSQL stocke un document JSON de façon binaire et indexable — ce qui manquait historiquement à PostgreSQL pour rivaliser avec un vrai document store comme MongoDB.

CREATE TABLE produits (
id SERIAL PRIMARY KEY,
nom TEXT NOT NULL,
attributs JSONB
);
INSERT INTO produits (nom, attributs) VALUES
('Clavier mecanique', '{"switches": "brown", "retroeclaire": true}'),
('Souris sans fil', '{"dpi": 1600, "couleurs": ["noir", "blanc"]}'),
('Ecran 27 pouces', '{"resolution": "2560x1440", "hz": 144}');
CREATE INDEX idx_produits_attributs ON produits USING GIN (attributs);

Trois documents, trois structures différentes — exactement ce qu’on ferait dans une collection MongoDB. Requêtes réellement exécutées :

$ SELECT nom, attributs FROM produits WHERE attributs->>'retroeclaire' = 'true';
nom | attributs
--------------------+---------------------------------------------
Clavier mecanique | {"switches": "brown", "retroeclaire": true}
$ SELECT nom, attributs FROM produits WHERE (attributs->>'dpi')::int > 1000;
nom | attributs
-------------------+----------------------------------------------
Souris sans fil | {"dpi": 1600, "couleurs": ["noir", "blanc"]}
$ SELECT nom FROM produits WHERE attributs -> 'couleurs' ? 'noir';
nom
------------------
Souris sans fil

Les opérateurs ->> (extraire une valeur en texte), -> (extraire en JSON), ? (le tableau JSON contient cette valeur) permettent d’interroger un document presque aussi naturellement qu’avec la syntaxe MongoDB — tout en restant dans le même moteur SQL que le reste de l’application, avec les mêmes transactions ACID, les mêmes sauvegardes, le même outil de supervision.

Un détail réel qui mérite d’être expliqué : demander le plan d’exécution d’une requête sur ce même index…

$ EXPLAIN SELECT nom FROM produits WHERE attributs @> '{"dpi": 1600}';
QUERY PLAN
---------------------------------------------------------
Seq Scan on produits (cost=0.00..1.04 rows=1 width=32)
Filter: (attributs @> '{"dpi": 1600}'::jsonb)

… révèle que PostgreSQL a choisi un balayage séquentiel (Seq Scan), pas l’index GIN pourtant créé. Ce n’est pas un bug : sur une table de 3 lignes, lire la table entière est objectivement moins coûteux que consulter un index puis retourner à la table — le planificateur de requêtes de PostgreSQL prend cette décision au cas par cas, à partir de statistiques réelles sur la table, pas d’une règle figée. Sur une vraie table de plusieurs millions de lignes, ce même index GIN serait presque certainement utilisé. La leçon : ne jamais supposer qu’un index est utilisé — toujours vérifier avec EXPLAIN, à une échelle de données représentative de la production.

pgvector ajoute à PostgreSQL un type de données vector et des opérateurs de distance (cosinus, euclidienne, produit scalaire) — la brique de base d’une recherche sémantique ou d’un système RAG, déjà évoqué dans le cours NLP, LLM & RAG de ce site.

CREATE EXTENSION vector;
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
contenu TEXT,
embedding VECTOR(3) -- 3 dimensions pour l'exemple ; 1536 ou plus en pratique (OpenAI, etc.)
);
INSERT INTO documents (contenu, embedding) VALUES
('Le chat dort sur le canape', '[0.1, 0.9, 0.2]'),
('Le chien court dans le jardin', '[0.8, 0.2, 0.1]'),
('Le chaton fait une sieste', '[0.15, 0.85, 0.25]');
-- Les 2 documents les plus proches sémantiquement de "Le chat dort" (recherche par similarité cosinus)
SELECT contenu, embedding <=> '[0.1, 0.9, 0.2]' AS distance
FROM documents
ORDER BY distance
LIMIT 2;
contenu | distance
--------------------------------+----------------------
Le chat dort sur le canape | 0
Le chaton fait une sieste | 0.004003945946260301
Le chien court dans le jardin | 0.6365168729553423

L’opérateur <=> calcule la distance cosinus directement dans la requête SQL. La phrase identique à la requête ressort avec une distance de 0 (logique), la phrase sur le chaton qui fait une sieste — sémantiquement très proche d’un chat qui dort — ressort avec une distance quasi nulle (0,004), et la phrase sur le chien qui court arrive loin derrière (0,637) : exactement le comportement attendu d’une base vectorielle dédiée comme Pinecone ou Qdrant, mais sans quitter PostgreSQL.

  • Séries temporelles : l’extension TimescaleDB transforme une table PostgreSQL en hypertable, partitionnée et compressée automatiquement par le temps — un vrai projet open source packagé comme extension PostgreSQL, activement maintenu (version stable 2.28, 2026), pas une simple astuce marketing.
  • Clé-valeur : PostgreSQL n’égale pas la vitesse en mémoire de Redis pour du cache pur — sur ce terrain précis, consolider n’a pas de sens, Redis reste imbattable.
  • Colonne, graphe : des extensions existent (par exemple AGE pour le graphe), mais avec une maturité et des performances moins éprouvées que des systèmes nés spécifiquement pour ces usages (Neo4j, BigQuery).

Quand consolider sur PostgreSQL a du sens — et quand ça n’en a pas

Section titled “Quand consolider sur PostgreSQL a du sens — et quand ça n’en a pas”

Consolider a du sens quand :

  • L’équipe maîtrise déjà PostgreSQL en profondeur, et le volume de données « document » ou « vecteur » reste raisonnable (pas des dizaines de téraoctets par jour).
  • La cohérence transactionnelle entre les données relationnelles et les données document/vecteur compte (par exemple : mettre à jour une commande et son embedding de recherche dans la même transaction).
  • Réduire le nombre de systèmes à sauvegarder, superviser, sécuriser et faire monter en compétence l’équipe est une vraie priorité opérationnelle.

Consolider n’a pas de sens quand :

  • Le volume ou le débit dépasse largement ce qu’un serveur relationnel, même bien réglé, peut absorber — un vrai MongoDB shardé sur plusieurs continents, ou un vrai Redis Cluster, restent structurellement mieux adaptés à une échelle extrême.
  • L’équipe a déjà une expertise profonde sur l’outil spécialisé, et migrer ferait perdre plus qu’il ne ferait gagner.
  • Un besoin très spécifique à la technologie spécialisée existe (l’agrégation par pipeline de MongoDB, le clustering Redis natif, l’indexation approximative ultra-optimisée d’une base vectorielle dédiée à très grande échelle).

Le message central de ce module, et de ce petit cours dans son ensemble : il n’existe pas de réponse universelle à « quelle base de données choisir ». Le Module 1 a montré qu’il existe 7 problèmes différents ; le Module 2 a comparé les outils qui y répondent ; ce module montre qu’un même outil peut parfois en couvrir plusieurs — mais que « peut » ne veut jamais dire « doit » systématiquement. La bonne décision se prend au cas par cas, en testant (comme ici) plutôt qu’en supposant.