Bases de données NoSQL

Redis, Mongo,
Cassandra, Neo4j

Quand et pourquoi sortir du relationnel — les quatre grandes familles NoSQL avec leurs commandes essentielles.

SQL vs NoSQL

NoSQL ne remplace pas SQL — il répond à des besoins différents. La bonne question n'est pas "lequel est meilleur ?" mais "lequel correspond à mon modèle de données et mes contraintes de charge ?"

🗃️
SQL — Relationnel
Schéma rigide et défini à l'avance
Transactions ACID garanties
Jointures entre tables
Données structurées et normalisées
Scalabilité verticale (+ gros serveur)
SQL universel et standardisé
Idéal : données financières, ERP, CRM
🌐
NoSQL — Non-relationnel
Schéma flexible ou sans schéma
Cohérence éventuelle (souvent)
Données dénormalisées / agrégées
Très hautes performances en lecture/écriture
Scalabilité horizontale (+ serveurs)
Chaque moteur a sa propre syntaxe
Idéal : big data, temps réel, IoT, réseaux sociaux
ℹ️

En production, les architectures modernes combinent souvent les deux : PostgreSQL pour les données transactionnelles, Redis pour le cache, MongoDB pour les logs ou les profils utilisateurs. Ce n'est pas un choix exclusif.

Les quatre familles NoSQL

Clé-valeur
Redis · DynamoDB · Memcached
Structure la plus simple. Une clé unique → une valeur. Ultra-rapide (en mémoire). Idéal pour le cache et les sessions.
📄
Documents
MongoDB · CouchDB · Firestore
Stocke des documents JSON/BSON semi-structurés. Chaque document peut avoir une structure différente. Très flexible.
📊
Colonnes larges
Cassandra · HBase · ScyllaDB
Tables avec colonnes dynamiques par ligne. Conçu pour écrire et lire massivement à grande échelle distribuée.
🔗
Graphes
Neo4j · ArangoDB · Amazon Neptune
Nœuds et relations comme objets de première classe. Parfait pour les réseaux sociaux, recommandations, détection de fraude.

Comment choisir ?

BesoinRecommandationPourquoi
Données financières, commandes, stocks SQL (MySQL/PostgreSQL) Transactions ACID critiques, intégrité référentielle
Cache, sessions, rate limiting, files de messages Redis Microseconde de latence, structures en mémoire
Catalogues produits, CMS, profils utilisateurs MongoDB Schéma variable, imbrication naturelle, requêtes riches
IoT, logs, analytics, time-series à fort volume Cassandra Écritures massivement distribuées, haute disponibilité
Réseau social, recommandations, fraude Neo4j Les relations sont des données, traversal efficace
Recherche full-text, facettes Elasticsearch / OpenSearch Indexation inversée, scoring, agrégations

Redis — Clé-valeur

⚡ In-memory · Sub-milliseconde · Persistance optionnelle

Redis (Remote Dictionary Server) stocke toutes les données en RAM. C'est la base de données la plus rapide qui soit — au prix d'une capacité limitée à la mémoire disponible.

Redis CLI — commandes de base
── Chaînes (String) ─────────────────────────
SET    user:42:nom   "Alice"
GET    user:42:nom          → "Alice"
SET    compteur     0
INCR   compteur             → 1
INCRBY compteur     5      → 6

── Expiration ───────────────────────────────
SET    session:abc  "token123" EX 3600
TTL    session:abc          → 3598
EXPIRE session:abc  7200   ← redéfinir
PERSIST session:abc         ← rendre permanent

── Suppression & existence ──────────────────
DEL    user:42:nom
EXISTS user:42:nom          → 0 ou 1
KEYS   user:42:*            ← pattern glob
💡

Convention de nommage des clés : utiliser des : comme séparateurs hiérarchiques. Ex : user:42:session, product:99:views. Cela permet de grouper et de scanner par préfixe.

Opérations atomiques utiles
── GETSET : lire et remplacer atomiquement
GETSET lock:resource "owned"

── SETNX : SET seulement si inexistant
── (implémentation de verrou distribué)
SET lock:job "worker-1" NX EX 30

── MGET / MSET : lot de clés
MSET a 1 b 2 c 3
MGET a b c  → ["1","2","3"]

Types de données Redis

Hash — objet structuré
HSET   user:42  nom "Alice"  age 30
HGET   user:42  nom       → "Alice"
HGETALL user:42           → tous les champs
HINCRBY user:42  age 1   → 31
List — file / pile
RPUSH queue:emails "msg1" "msg2"
LPOP  queue:emails         → "msg1"
LLEN  queue:emails         → 1
LRANGE queue:emails 0 -1  → tout
Set — ensemble unique
SADD     tags:article:5  "python" "web"
SMEMBERS tags:article:5
SISMEMBER tags:article:5 "python" → 1
SINTER   tags:art:5 tags:art:6  ← intersection
Sorted Set — classement avec score
ZADD  leaderboard 9850 "Alice"
ZADD  leaderboard 7200 "Bob"
ZADD  leaderboard 11000 "Carol"

── Top 3 (scores décroissants)
ZREVRANGE leaderboard 0 2 WITHSCORES
→ Carol 11000, Alice 9850, Bob 7200

ZRANK leaderboard "Alice"  → 1 (0-indexé)
ZSCORE leaderboard "Alice" → 9850
TypeUsage typique
StringCache, compteurs, tokens, flags
HashObjet utilisateur, configuration
ListFile de tâches, historique, flux
SetTags, membres uniques, intersections
Sorted SetClassements, priorités, time-series

Cas d'usage Redis

Cache applicatif

Stocker le résultat de requêtes SQL coûteuses avec TTL.
SET query:users:active
    "[{...}]" EX 300

Gestion de sessions

Session expirante automatiquement à déconnexion.
HSET session:<token>
     user_id 42 role "admin"
EXPIRE session:<token> 1800

Rate limiting

Limiter le nombre de requêtes par IP par minute.
INCR   rate:192.168.1.1
EXPIRE rate:192.168.1.1 60
→ si > 100 : bloquer

Pub/Sub messagerie

Communication temps réel entre services.
-- Publisher
PUBLISH notifications "msg"
-- Subscriber
SUBSCRIBE notifications

MongoDB — Documents

📄 BSON · Collections · Schéma flexible

MongoDB stocke des documents BSON (JSON binaire) dans des collections. Pas de schéma imposé — chaque document peut avoir sa propre structure.

Vocabulaire SQL → MongoDB

SQLMongoDB
Base de donnéesBase de données
TableCollection
Ligne / enregistrementDocument
ColonneChamp (field)
PRIMARY KEY_id (ObjectId auto)
JOIN$lookup (aggregation)
INDEXIndex (même concept)

Structure d'un document

Document JSON — collection "clients"
{
  "_id":      ObjectId("64a1f2..."),
  "nom":      "Dupont",
  "prenom":   "Alice",
  "email":    "alice@example.com",
  "actif":    true,
  "age":      30,
  "adresses": [
    {
      "type": "domicile",
      "ville": "Bruxelles",
      "cp": "1000"
    }
  ],
  "preferences": {
    "langue": "fr",
    "newsletter": false
  }
}
💡

L'imbrication (embedding) est préférée aux jointures en MongoDB. Si vous affichez toujours adresses avec client, les stocker dans le même document évite un aller-retour réseau.

CRUD MongoDB

MongoDB Shell (mongosh)
── CREATE ────────────────────────────────────────────
db.clients.insertOne({
  nom: "Martin", email: "bob@example.com", age: 25
})

db.clients.insertMany([
  { nom: "A", age: 20 },
  { nom: "B", age: 30 }
])

── READ ──────────────────────────────────────────────
-- Tous les documents
db.clients.find()

-- Avec filtre
db.clients.find({ actif: true, age: { $gte: 18 } })

-- Projection (champs voulus seulement)
db.clients.find({}, { nom: 1, email: 1, _id: 0 })

-- Tri, limite, skip
db.clients.find().sort({age: -1}).limit(10).skip(20)

── UPDATE ────────────────────────────────────────────
-- Modifier un champ ($set) sans écraser le document
db.clients.updateOne(
  { email: "bob@example.com" },
  { $set: { actif: false }, $inc: { connexions: 1 } }
)

── DELETE ────────────────────────────────────────────
db.clients.deleteOne({ _id: ObjectId("64a1f2...") })
db.clients.deleteMany({ actif: false })

Opérateurs de requête

OpérateurSignification
$eq / $neÉgal / différent
$gt / $gteSupérieur (ou égal)
$lt / $lteInférieur (ou égal)
$in / $ninDans / hors d'une liste
$and / $orCombinaison logique
$existsChamp existe ou non
$regexExpression régulière

Opérateurs de mise à jour

OpérateurAction
$setModifier ou créer un champ
$unsetSupprimer un champ
$incIncrémenter un nombre
$pushAjouter à un tableau
$pullRetirer d'un tableau
$renameRenommer un champ

Aggregation pipeline

Le pipeline d'agrégation enchaîne des étapes de transformation — l'équivalent MongoDB d'un SELECT avec GROUP BY et jointures.

Exemple : ventes par catégorie
db.commandes.aggregate([

  -- Étape 1 : filtrer
  { $match: { statut: "livree" } },

  -- Étape 2 : déplier le tableau de lignes
  { $unwind: "$lignes" },

  -- Étape 3 : grouper par catégorie
  { $group: {
    _id:        "$lignes.categorie",
    total_ventes: { $sum: "$lignes.montant" },
    nb_articles:  { $sum: "$lignes.qte" }
  } },

  -- Étape 4 : trier par total décroissant
  { $sort: { total_ventes: -1 } },

  -- Étape 5 : top 5
  { $limit: 5 }
])

Étapes pipeline essentielles

ÉtapeRôle
$matchFiltrer (équiv. WHERE)
$projectSélectionner / calculer des champs
$groupAgréger (équiv. GROUP BY)
$sortTrier (équiv. ORDER BY)
$limit / $skipPaginer
$unwindDéplier un tableau en lignes
$lookupJointure entre collections
$addFieldsAjouter des champs calculés

Cassandra — Colonnes larges

📊 Distribué · Pas de SPOF · Écriture massivement scalable

Cassandra est conçu pour écrire des millions de lignes par seconde, distribué sur des dizaines de nœuds. Il ne fait pas de jointures — chaque table est conçue pour répondre à une requête spécifique.

ℹ️

Philosophie Cassandra : modéliser autour des requêtes, pas autour des entités. Dénormaliser est normal et souhaitable. Une même donnée peut exister dans plusieurs tables.

CQL — CREATE KEYSPACE & TABLE
-- Keyspace = base de données
CREATE KEYSPACE boutique
  WITH replication = {
    'class': 'SimpleStrategy',
    'replication_factor': 3
  };

-- Table optimisée pour requête :
-- "commandes d'un client, triées par date"
CREATE TABLE boutique.commandes_par_client (
  id_client   UUID,
  date_cmd    TIMESTAMP,
  id_commande UUID,
  statut      TEXT,
  total_ht    DECIMAL,

  -- Partition key : répartit sur les nœuds
  PRIMARY KEY ((id_client), date_cmd, id_commande)
) WITH CLUSTERING ORDER BY
  (date_cmd DESC);

Clés Cassandra — concept fondamental

ConceptRôle
Partition KeyDétermine sur quel nœud est stockée la donnée. Toutes les requêtes DOIVENT filtrer par partition key.
Clustering KeyTrie les données à l'intérieur d'une partition. Permet le filtre sur des plages (BETWEEN, >=).
⚠️

Sans partition key dans le WHERE, Cassandra doit scanner tous les nœuds (full cluster scan) — requête extrêmement lente. Toujours concevoir les tables query-first.

CQL — Cassandra Query Language

CQL — syntaxe proche de SQL
── INSERT (pas d'UPDATE si inexistant : toujours INSERT) ──
INSERT INTO boutique.commandes_par_client
  (id_client, date_cmd, id_commande, statut, total_ht)
VALUES
  (uuid(), toTimestamp(now()), uuid(), 'en_attente', 149.90)
USING TTL 2592000;  -- expiration en 30 jours

── SELECT (partition key obligatoire) ──────────────────
SELECT * FROM boutique.commandes_par_client
WHERE  id_client = 550e8400-e29b-41d4-a716-446655440000
  AND  date_cmd >= '2024-01-01'
LIMIT  50;

── UPDATE ──────────────────────────────────────────────
UPDATE boutique.commandes_par_client
SET    statut = 'livree'
WHERE  id_client   = 550e8400-e29b-41d4-a716-446655440000
  AND  date_cmd    = '2024-03-15 10:30:00'
  AND  id_commande = 123e4567-e89b-12d3-a456-426614174000;

── DELETE ──────────────────────────────────────────────
DELETE FROM boutique.commandes_par_client
WHERE id_client = 550e8400-e29b-41d4-a716-446655440000
  AND date_cmd   = '2024-03-15 10:30:00'
  AND id_commande = 123e4567-e89b-12d3-a456-426614174000;

Neo4j — Base de graphes

🔗 Nœuds · Relations · Propriétés · Traversal natif

Neo4j stocke des nœuds (entités) et des relations (liens nommés et directionnels) comme objets de première classe. Les relations sont aussi importantes que les données elles-mêmes.

Modèle de données

Nœud (Node)
Labels : (:Personne), (:Film)
Propriétés : {nom: "Alice", age: 30}
Relation
Type : -[:CONNAIT]->
Direction : toujours directionnelle
Propriétés : {depuis: 2019}
Exemple
(alice)-[:CONNAIT {depuis:2019}]->(bob)

Quand utiliser Neo4j ?

Réseau social — "Amis d'amis", suggestions de connexion
Recommandations — "Les utilisateurs qui ont aimé X ont aussi aimé..."
Détection de fraude — Identifier des anneaux de transactions suspects
Graphe de dépendances — Packages, microservices, infrastructure
💡

En SQL, traverser 6 niveaux de relations (amis d'amis d'amis...) nécessite 6 jointures récursives. Neo4j traverse nativement n'importe quelle profondeur en quelques millisecondes.

Cypher — Requêtes Neo4j

Cypher est le langage déclaratif de Neo4j. Sa syntaxe utilise des patterns visuels pour décrire le graphe à traverser.

Cypher — syntaxe essentielle
── CREATE — créer des nœuds et relations ────────────────
CREATE (alice:Personne {nom: "Alice", age: 30})
CREATE (bob:Personne   {nom: "Bob",   age: 25})
CREATE (alice)-[:CONNAIT {depuis: 2019}]->(bob)

── MATCH — lire des patterns ────────────────────────────
-- Tous les amis d'Alice
MATCH  (p:Personne {nom: "Alice"})-[:CONNAIT]->(ami)
RETURN ami.nom, ami.age

-- Amis d'amis (profondeur 2)
MATCH  (p {nom: "Alice"})-[:CONNAIT*2]->(ami2)
RETURN ami2.nom

-- Chemin le plus court entre Alice et Dave
MATCH  p = shortestPath(
  (a {nom:"Alice"})-[:CONNAIT*]-(d {nom:"Dave"})
)
RETURN p

── Recommandation : films vus par amis mais pas par moi ─
MATCH  (moi:Personne {nom:"Alice"})
        -[:CONNAIT]->(ami)-[:A_VU]->(film:Film)
WHERE NOT (moi)-[:A_VU]->(film)
RETURN film.titre, count(ami) AS popularite
ORDER BY popularite DESC

── MERGE — créer si n'existe pas (upsert) ───────────────
MERGE  (p:Personne {email: "alice@ex.com"})
ON CREATE SET  p.nom = "Alice", p.cree_le = datetime()
ON MATCH  SET  p.vu_le  = datetime()

── SET / DELETE ─────────────────────────────────────────
MATCH (p {nom:"Bob"}) SET p.age = 26
MATCH (p {nom:"Bob"}) DETACH DELETE p  ← supprime nœud + ses relations

Cheat sheet NoSQL

Redis

SET / GETChaîne simple
HSET / HGETHash (objet)
RPUSH / LPOPListe (file FIFO)
SADD / SMEMBERSEnsemble unique
ZADD / ZREVRANGESorted set (score)
EXPIRE / TTLExpiration de clé

MongoDB

insertOne / ManyInsérer
find({filtre})Lire
updateOne / $setModifier un champ
deleteOne / ManySupprimer
aggregate([...])Pipeline d'agrégation
$match / $groupFiltrer / grouper

Cassandra

CREATE KEYSPACEBase de données
PRIMARY KEY ((pk), ck)Partition + clustering
INSERT INTOInsérer (upsert)
SELECT (avec PK)Requête obligatoire
USING TTLExpiration automatique
Query-first designTable par requête

Neo4j — Cypher

CREATE (n:Label)Créer un nœud
-[:RELATION]->Créer une relation
MATCH … RETURNLire un pattern
MERGEUpsert nœud/relation
[:REL*n]Traverser n niveaux
shortestPath()Chemin le plus court