Skip to main content
backend30 novembre 202514 min de lecture

Choisir la bonne base de données : relationnel vs NoSQL pour de vrais projets

Un cadre de décision pratique pour choisir entre PostgreSQL, MongoDB, Firebase, Redis et d'autres bases de données en fonction de vos besoins réels.

databasepostgresqlmongodb
Choisir la bonne base de données : relationnel vs NoSQL pour de vrais projets

"Ça dépend" est la réponse honnête à toute question sur les bases de données, mais c'est aussi inutile. Quand vous démarrez un projet et devez choisir une base de données, vous avez besoin de quelque chose de plus concret qu'une liste de compromis. Vous avez besoin d'un cadre de décision qui prend en compte vos besoins réels — la forme des données, les patterns de requêtes, l'expertise de l'équipe, les attentes de mise à l'échelle et la complexité opérationnelle.

J'ai utilisé PostgreSQL, MongoDB, Firebase/Firestore, Redis et SQLite dans différents projets de production. Chaque choix était adapté à son contexte, et quelques-uns étaient inadaptés et ont dû être migrés. Les patterns derrière ces décisions sont plus utiles que n'importe quelle comparaison de benchmarks.

Le cadre de décision

Avant de regarder les bases de données individuelles, répondez à ces cinq questions sur votre projet :

1. Quelle est la forme de vos données ?

Il ne s'agit pas de "relationnel vs document". Il s'agit de savoir comment vos entités de données sont liées entre elles.

Fortement relationnelles : Les utilisateurs ont des commandes, les commandes ont des articles, les articles appartiennent à des catégories, les catégories ont des hiérarchies. Si vous dessinez votre modèle de données et qu'il ressemble à un graphe avec de nombreuses connexions, vous avez besoin de solides capacités relationnelles.

Orientées document : Chaque entité est relativement autonome. Un article de blog contient son titre, corps, tags et informations d'auteur. Vous avez rarement besoin de joindre entre les types d'entités.

Clé-valeur : Vous devez stocker et récupérer par clé. Données de session, configuration, feature flags.

Séries temporelles : Événements, métriques, logs. Écriture intensive, ajout uniquement, requêtes par plage temporelle.

2. Quels sont vos patterns de requêtes ?

Requêtes connues : Vous savez exactement quelles requêtes l'application exécutera. E-commerce : "obtenir les commandes de l'utilisateur", "trouver les produits par catégorie", "calculer le chiffre d'affaires total ce mois". Les requêtes connues favorisent les bases de données relationnelles où vous pouvez optimiser avec des index et des plans de requêtes.

Requêtes ad-hoc : Les utilisateurs peuvent rechercher, filtrer et agréger de manière imprévisible. Tableaux de bord analytiques, fonctionnalités de recherche, outils de reporting. Ceux-ci favorisent les moteurs de requêtes flexibles.

Recherches simples : La plupart des lectures sont "obtenir le document par ID". Les bases de données documentaires et les stores clé-valeur excellent ici.

3. Quelle est votre exigence de cohérence ?

Cohérence forte : Banque, inventaire, tout ce où lire des données obsolètes cause de vrais problèmes. Les bases de données relationnelles avec des transactions ACID sont le choix sûr.

Cohérence éventuelle : Flux sociaux, analytics, cache. Vous pouvez tolérer de lire des données légèrement obsolètes en échange de performance et de disponibilité.

4. Quelle est votre trajectoire de mise à l'échelle ?

Le scaling vertical suffit : La plupart des applications. Si votre base de données tient sur une seule machine avec de la marge de croissance, n'importe quelle base de données fonctionne. PostgreSQL sur un serveur moderne gère des millions de lignes sans broncher.

Le scaling horizontal est nécessaire : Vous devez distribuer les données sur plusieurs machines. C'est plus rare que la plupart des développeurs ne le pensent, mais quand vous en avez besoin, votre choix de base de données est contraint.

5. Que connaît votre équipe ?

Ce facteur est sous-estimé. Une équipe d'experts PostgreSQL sera plus productive avec PostgreSQL, même si MongoDB est théoriquement un meilleur choix pour le modèle de données. Le coût d'apprentissage d'une nouvelle base de données — déboguer des erreurs inconnues, apprendre les bonnes pratiques opérationnelles, comprendre les caractéristiques de performance — est réel et significatif.

PostgreSQL : le choix par défaut

Si vous n'êtes pas sûr, choisissez PostgreSQL. Ce n'est pas une opinion controversée parmi les ingénieurs backend — c'est le consensus. PostgreSQL gère les données relationnelles, les documents JSON, la recherche full-text, les requêtes géospatiales et les données de séries temporelles. Il n'est pas le meilleur dans aucun de ces domaines, mais il est assez bon dans tous pour servir de base de données unique pour la plupart des applications.

Points forts

Transactions ACID. Quand vous devez mettre à jour plusieurs tables de manière atomique — déduire l'inventaire et créer une commande, transférer de l'argent entre des comptes — PostgreSQL garantit la correction.

Capacités de requêtes riches. Fonctions de fenêtrage, CTEs (Common Table Expressions), requêtes récursives, lateral joins. SQL n'est pas juste SELECT * FROM — c'est un langage de requêtes puissant qui peut exprimer des analyses complexes sans déplacer les données vers une couche applicative.

-- Find each user's most recent order with running total
WITH ranked_orders AS (
  SELECT
    user_id,
    order_id,
    total,
    created_at,
    ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) as rn,
    SUM(total) OVER (PARTITION BY user_id ORDER BY created_at) as running_total
  FROM orders
)
SELECT * FROM ranked_orders WHERE rn = 1;

Colonnes JSONB. Quand une partie de vos données est véritablement sans schéma — préférences utilisateur, réponses de formulaires dynamiques, payloads de webhooks tiers — vous pouvez les stocker en JSONB et les requêter avec un support complet d'indexation.

-- Store and query semi-structured data
CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  attributes JSONB DEFAULT '{}'
);

-- Index a specific JSON path
CREATE INDEX idx_products_color ON products USING GIN ((attributes->'color'));

-- Query JSON data
SELECT name FROM products
WHERE attributes->>'color' = 'blue'
AND (attributes->>'weight')::numeric < 10;

Écosystème d'extensions. PostGIS pour le géospatial, pg_trgm pour la recherche de texte floue, TimescaleDB pour les séries temporelles, pgvector pour les embeddings IA. Le modèle d'extensions signifie que PostgreSQL peut s'adapter à des charges de travail spécialisées sans remplacer votre base de données principale.

Quand PostgreSQL n'est pas le bon choix

  • Débit d'écriture massif avec scaling horizontal. PostgreSQL scale bien verticalement (machine plus grosse) mais le sharding horizontal est complexe. Si vous devez écrire des millions d'événements par seconde sur des dizaines de machines, envisagez des solutions dédiées.
  • Prototypage rapide où le schéma change quotidiennement. Aux premières étapes d'une startup quand le modèle de données change fondamentalement chaque semaine, l'overhead des migrations peut vous ralentir par rapport aux options sans schéma.
  • Quand votre équipe n'a aucune expérience SQL et que le calendrier du projet ne permet pas l'apprentissage. C'est rare, mais ça arrive.

MongoDB : quand les documents ont du sens

MongoDB reçoit plus de critiques qu'il n'en mérite. Le marketing initial ("sans schéma ! web scale !") a créé des attentes irréalistes, et de nombreux développeurs l'ont utilisé pour des cas d'usage où PostgreSQL aurait été meilleur. Mais il y a de vrais scénarios où MongoDB est le bon choix.

Quand MongoDB l'emporte

Systèmes de gestion de contenu. Chaque contenu a une structure différente — les articles ont des champs différents des vidéos, qui ont des champs différents des podcasts. Dans MongoDB, ce sont tous des documents dans la même collection avec des formes différentes. Dans PostgreSQL, vous créez soit une table large avec de nombreuses colonnes nullables, utilisez des colonnes JSON (auquel cas vous utilisez le paradigme de MongoDB dans PostgreSQL), soit créez une table par type avec des jointures complexes.

Event sourcing et journalisation. Haut débit d'écriture avec des documents qui sont principalement écrits et lus, rarement mis à jour, et jamais joints.

Les documents embarqués réduisent les jointures. Si une commande a toujours besoin de ses articles, et que les articles ne sont jamais consultés sans leur commande, embarquer les articles dans le document de commande signifie que votre requête la plus courante est une simple recherche de document au lieu d'une jointure entre deux tables.

// MongoDB document with embedded sub-documents
{
  _id: ObjectId("..."),
  customer: {
    name: "John Doe",
    email: "john@example.com"
  },
  items: [
    { product: "Widget", quantity: 2, price: 9.99 },
    { product: "Gadget", quantity: 1, price: 24.99 }
  ],
  total: 44.97,
  status: "shipped",
  createdAt: ISODate("2026-03-01T10:00:00Z")
}

Le scaling horizontal est une fonctionnalité native. Le sharding de MongoDB est intégré et bien documenté. Si vous avez réellement besoin de distribuer les données sur de nombreux nœuds (des centaines de millions de documents avec un haut débit), MongoDB gère cela plus gracieusement que PostgreSQL.

Quand MongoDB est le mauvais choix

  • Données fortement relationnelles. Si vous écrivez des agrégations $lookup (la version MongoDB des jointures) dans chaque requête, vous avez choisi la mauvaise base de données.
  • Quand vous avez besoin de transactions inter-collections fréquemment. MongoDB supporte les transactions multi-documents, mais elles ajoutent de la latence et de la complexité. Si vos opérations cœur nécessitent une atomicité inter-collections, le modèle transactionnel de PostgreSQL est plus naturel.
  • Quand vous avez réellement besoin d'un schéma. "Sans schéma" semble libérateur jusqu'à ce que vous réalisiez que chaque morceau de code qui lit la base de données est implicitement un schéma. Sans schéma imposé par la base de données, vous déplacez la validation vers la couche applicative, où elle est plus facile à mal faire et plus difficile à appliquer de manière cohérente.

Firebase et Firestore : développement rapide

Firebase Realtime Database et Firestore servent un créneau spécifique : les applications où la vitesse de développement importe plus que la pureté du modèle de données, et où le backend est principalement une couche de persistance et de synchronisation de données plutôt qu'un moteur de logique métier complexe.

Quand Firebase brille

Applications mobile-first avec synchronisation en temps réel. Les SDK Firebase gèrent le cache hors ligne, les listeners en temps réel et la résolution de conflits nativement. Construire cela à partir de zéro avec PostgreSQL nécessite une couche WebSocket, une stratégie de cache et un code personnalisé significatif.

Petites équipes sans ingénieurs backend. Firebase élimine le besoin de gérer un serveur de base de données, construire une couche API, implémenter l'authentification et gérer le stockage de fichiers. Pour une équipe de deux personnes construisant un MVP, cette réduction de l'overhead opérationnel peut faire la différence entre livrer et ne pas livrer.

Prototypage et validation. Quand vous devez tester une idée avec de vrais utilisateurs en deux semaines, Firebase vous permet de vous concentrer entièrement sur l'application cliente. Si l'idée se valide, vous pouvez migrer vers un backend plus traditionnel par la suite.

Les limites de Firebase

Contraintes de requêtes dans Firestore. Vous ne pouvez pas requêter sur des champs qui ne sont pas indexés. Vous ne pouvez pas faire des filtres d'inégalité sur plusieurs champs. Vous ne pouvez pas faire de recherche full-text. Ces limitations vous obligent à dénormaliser agressivement et parfois à dupliquer les données entre les collections.

// Firestore: You CAN'T do this
db.collection('products')
  .where('price', '>', 10)
  .where('rating', '>', 4)
  .orderBy('name')  // Error: needs composite index on price + rating + name

// Firestore: You CAN do this (with a composite index)
db.collection('products')
  .where('price', '>', 10)
  .where('rating', '>', 4)
  .orderBy('price')  // Must order by a field used in inequality

Dépendance au fournisseur. Votre modèle de données, vos règles de sécurité et vos patterns de requêtes sont tous spécifiques à Firebase. Migrer depuis Firebase est une réécriture, pas une migration.

Imprévisibilité des coûts. Firebase facture par lecture/écriture de document. Une requête mal optimisée ou un listener en temps réel sur une grande collection peut générer des factures surprenantes. J'ai vu des projets où un seul listener mal configuré a coûté plus qu'un serveur PostgreSQL dédié pour un an.

Logique côté serveur limitée. Cloud Functions peut gérer certains traitements côté serveur, mais la logique métier complexe — transactions multi-étapes, agrégation de données, traitement en arrière-plan — nécessite soit des solutions de contournement créatives, soit un backend séparé de toute façon.

Décision : Firebase vs backend traditionnel

Facteur Firebase PostgreSQL + API
Temps jusqu'au MVP Jours Semaines
Overhead opérationnel Quasi nul Modéré
Flexibilité des requêtes Limitée SQL complet
Coût à grande échelle Imprévisible Prévisible
Dépendance fournisseur Élevée Faible
Support hors ligne Intégré À construire
Logique métier complexe Difficile Naturelle
Équipe requise Frontend uniquement Frontend + Backend

Redis : cache, files d'attente, et plus

Redis n'est pas une base de données principale pour la plupart des applications (bien que Redis avec des modules de persistance puisse remplir ce rôle). C'est un store de structures de données haute performance qui excelle dans des problèmes spécifiques.

Cache

Le cas d'utilisation Redis le plus courant. Mettre en cache les requêtes de base de données coûteuses, les réponses API ou les résultats calculés.

import redis
import json

r = redis.Redis(host='localhost', port=6379, db=0)

def get_user_profile(user_id: str):
    # Check cache first
    cached = r.get(f"user:{user_id}")
    if cached:
        return json.loads(cached)

    # Cache miss — query database
    profile = db.query("SELECT * FROM users WHERE id = %s", user_id)

    # Cache for 5 minutes
    r.setex(f"user:{user_id}", 300, json.dumps(profile))
    return profile

Stockage de sessions

Le modèle clé-valeur de Redis avec le support TTL (time-to-live) est un choix naturel pour les données de session. C'est plus rapide que les sessions basées sur une base de données et plus simple que les solutions basées sur JWT pour les applications avec état.

Rate limiting

def is_rate_limited(user_id: str, limit: int = 100, window: int = 60) -> bool:
    key = f"rate:{user_id}"
    current = r.incr(key)
    if current == 1:
        r.expire(key, window)
    return current > limit

Files d'attente de jobs

Les listes et streams Redis font d'excellentes files d'attente de jobs. Des bibliothèques comme Bull (Node.js), Celery (Python), et Sidekiq (Ruby) utilisent Redis comme message broker.

Quand ne pas utiliser Redis

  • Comme votre seule base de données. Redis est en mémoire par défaut. Même avec la persistance (snapshots RDB ou logs AOF), il n'est pas conçu pour des données que vous ne pouvez pas vous permettre de perdre.
  • Pour des requêtes complexes. Les structures de données Redis (strings, listes, sets, sorted sets, hashes) sont puissantes mais ne sont pas interrogeables comme une base de données. Vous accédez aux données par clé, pas par conditions arbitraires.
  • Quand vous n'avez pas de problème de cache. Ajouter Redis à une stack qui n'a pas besoin de cache ajoute de la complexité opérationnelle sans bénéfice. Une requête PostgreSQL qui prend 5ms n'a pas besoin d'être cachée.

Utiliser plusieurs bases de données

La plupart des applications de production de complexité modérée utilisent plus d'une base de données. La clé est d'utiliser chacune pour ce qu'elle fait le mieux, pas d'essayer de forcer une seule base de données à tout gérer.

Un pattern courant pour une application web :

  • PostgreSQL pour les données métier cœur (utilisateurs, commandes, produits).
  • Redis pour le cache, les sessions et le rate limiting.
  • S3 (ou équivalent) pour le stockage de fichiers (images, documents, sauvegardes).

Pour une application en temps réel :

  • PostgreSQL pour les données persistantes et les requêtes complexes.
  • Redis pour le pub/sub et le cache.
  • Firebase/Supabase pour la synchronisation en temps réel vers les clients mobiles.

Le pattern d'intégration

Quand vous utilisez plusieurs bases de données, établissez une propriété claire. Chaque donnée a une source faisant autorité, et les autres systèmes sont des caches ou des vues de ces données.

PostgreSQL (source of truth)
    ├── Redis (read cache, expires after TTL)
    ├── Elasticsearch (search index, synced via change data capture)
    └── Firebase (mobile sync, updated via webhooks)

N'ayez jamais deux bases de données qui se considèrent toutes les deux propriétaires des mêmes données. Cette voie mène à des cauchemars de cohérence quasiment impossibles à déboguer.

Considérations de migration

Si vous réalisez que vous avez choisi la mauvaise base de données, la migration est possible mais coûteuse. Voici les considérations pratiques :

MongoDB vers PostgreSQL est la migration la plus courante que j'ai vue. La raison habituelle est que l'application a grandi au-delà des requêtes documentaires et avait besoin de jointures complexes, de transactions ou d'agrégations. La migration implique de concevoir un schéma relationnel, d'écrire des scripts de transformation et de mettre à jour chaque requête de base de données dans l'application. Prévoyez deux à quatre semaines pour une application de complexité modérée.

PostgreSQL vers MongoDB est plus rare mais arrive quand le modèle de données d'une application devient principalement orienté document. La migration est mécaniquement plus simple (aplatir les tables en documents) mais nécessite de repenser chaque requête et de perdre les garanties transactionnelles.

Firebase vers PostgreSQL est la migration la plus difficile car ce n'est pas juste un changement de base de données — c'est un changement d'architecture. Vous devez construire une couche API, implémenter l'authentification, remplacer les listeners en temps réel par des WebSockets ou du polling, et gérer la synchronisation hors ligne. C'est plus proche d'une réécriture que d'une migration.

La meilleure migration est celle que vous évitez. Passez un jour de plus à réfléchir à votre modèle de données en amont. Parlez à quelqu'un qui a construit une application similaire. Le coût de choisir la bonne base de données initialement est toujours inférieur au coût de migrer plus tard.

Analyse des coûts

Les coûts de base de données à grande échelle peuvent être surprenants, surtout avec les services managés.

Service Offre gratuite Petite production Production moyenne
Supabase (PostgreSQL) 500 Mo, 2 projets ~25 $/mois (8 Go, 2 cœurs) ~100 $/mois (32 Go, 4 cœurs)
Neon (PostgreSQL) 0,5 Go stockage ~19 $/mois ~69 $/mois
MongoDB Atlas 512 Mo partagé ~57 $/mois (M10 dédié) ~200 $/mois (M30)
Firebase Firestore 1 Go stockage, 50k lectures/jour ~25-100 $/mois (varie beaucoup) 100-1000 $/mois (dépend des requêtes)
Redis Cloud 30 Mo ~7 $/mois (250 Mo) ~60 $/mois (1 Go)
PlanetScale (MySQL) 5 Go, 1Md lectures/mois ~39 $/mois ~99 $/mois

L'imprévisibilité des coûts de Firebase mérite d'être soulignée. J'ai vu des projets où les coûts sont restés sous 30 $/mois pendant des mois, puis ont grimpé à 300 $ après un lancement de fonctionnalité qui a augmenté les lectures de documents. Avec PostgreSQL ou MongoDB, les coûts sont corrélés à la taille de la machine, ce qui est prévisible.

Mon vrai processus de décision

Quand je démarre un nouveau projet, la décision suit généralement ce chemin :

  1. PostgreSQL par défaut sauf s'il y a une raison spécifique de ne pas le faire.
  2. Ajouter Redis si l'application a des besoins de cache, de rate limiting ou de files d'attente de jobs.
  3. Envisager Firebase/Supabase si l'application est mobile-first avec des besoins temps réel et que l'équipe est petite.
  4. Envisager MongoDB si le modèle de données est véritablement orienté document avec des besoins relationnels minimaux.
  5. Ajouter des bases de données spécialisées (Elasticsearch, TimescaleDB, etc.) uniquement quand une charge de travail spécifique l'exige.

Ce cadre m'a bien servi sur une variété de projets, des plateformes de gestion de restaurants aux applications de santé mobiles. L'insight clé est que la base de données est de l'infrastructure — elle doit servir les besoins de votre application, pas piloter votre architecture. Choisissez le choix ennuyeux qui fonctionne, et consacrez votre effort d'ingénierie aux parties de l'application qui vous différencient.

DU

Danil Ulmashev

Full Stack Developer

Intéressé par une collaboration ?