# Fichier: python_cheats/cheatsheets/introduction_no_sql_mongo_db.txt
# Introduction MongoDB (NoSQL) - Pour Grands Débutants
# Comprendre MongoDB de A à Z : Architecture, Fonctionnement, Pratique

"""
[OK] INTRODUCTION : POURQUOI CE GUIDE ?

# Tu débutes avec les bases de données NoSQL et MongoDB ?
# Ce guide est fait pour TOI !

# Objectif :
# Te montrer EXACTEMENT comment MongoDB fonctionne "sous le capot"
# Comprendre ce qui se passe quand tu exécutes une requête
# Découvrir TOUS les fichiers créés et leur rôle précis
# Maîtriser les concepts NoSQL vs SQL
# Aucun concept ne sera laissé dans le flou !

# Plan du guide :
# 1. Qu'est-ce que NoSQL et pourquoi MongoDB ?
# 2. SQL vs NoSQL : Différences fondamentales
# 3. Installation et fichiers créés (détail complet)
# 4. Architecture interne de MongoDB
# 5. Du JSON à l'exécution : voyage complet d'une requête
# 6. Stockage physique des données (BSON, WiredTiger)
# 7. Index, Performance et Optimisations
# 8. Réplication et Sharding (scalabilité horizontale)
# 9. Agrégation Pipeline (le SQL de MongoDB)
# 10. Premiers pas pratiques
# 11. Cas d'usage réels et bonnes pratiques
"""


# [OK] PARTIE 1 : QU'EST-CE QUE NoSQL ET POURQUOI MONGODB ?

"""
┌────────────────────────────────────────────────────────────────────────┐
│                    NoSQL : UNE RÉVOLUTION NÉCESSAIRE                   │
└────────────────────────────────────────────────────────────────────────┘

CONTEXTE HISTORIQUE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AVANT 2000 : ÈRE DES BASES RELATIONNELLES (SQL)
-> Oracle, MySQL, PostgreSQL dominent
-> Données structurées, schéma rigide
-> Transactions ACID garanties
-> Scalabilité verticale (serveurs plus puissants)

PROBLÈMES APPARUS AVEC LE WEB 2.0 (années 2000) :
[X] Explosion du volume de données (Big Data)
   -> Facebook : milliards d'utilisateurs
   -> Google : milliards de pages web
   -> Twitter : millions de tweets par jour

[X] Besoin de scalabilité horizontale
   -> Un seul serveur ne suffit plus
   -> Besoin de distribuer sur des milliers de serveurs

[X] Schéma rigide inadapté
   -> Chaque utilisateur peut avoir des champs différents
   -> Évolution rapide du modèle de données
   -> ALTER TABLE sur des milliards de lignes = cauchemar

[X] Performance des jointures
   -> Jointures complexes = lentes sur gros volumes
   -> Besoin de dénormalisation

NAISSANCE DU NoSQL (Not Only SQL) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Google BigTable (2006)
-> Amazon DynamoDB (2007)
-> MongoDB (2009)
-> Cassandra, CouchDB, Redis, etc.

NoSQL = "Not Only SQL" (pas "No SQL")
-> Complément aux bases SQL
-> Pas un remplacement universel
-> Choisir le bon outil pour le bon usage


ANALOGIE : BIBLIOTHÈQUE VS ENTREPÔT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

BASE DE DONNÉES SQL (Bibliothèque classique) :
┌────────────────────────────────────────────────────────────────────┐
│ [DOCS] BIBLIOTHÈQUE UNIVERSITAIRE                                      │
│                                                                    │
│ Organisation stricte :                                             │
│ -> Chaque livre a une fiche NORMALISÉE                              │
│ -> ISBN, Titre, Auteur, Éditeur, Date                               │
│ -> Classement par catégories FIXES (Dewey)                          │
│ -> Références croisées entre fiches (comme les jointures)           │
│                                                                    │
│ Avantages :                                                        │
│ [OK] Recherche précise et rapide                                     │
│ [OK] Pas de redondance (normalization)                               │
│ [OK] Cohérence garantie (ACID)                                       │
│                                                                    │
│ Inconvénients :                                                    │
│ [X] Rigide : tous les livres doivent avoir le même format de fiche  │
│ [X] Difficile d'ajouter un nouveau type de média (DVD, jeux)        │
│ [X] Un seul bâtiment = limite de capacité                           │
└────────────────────────────────────────────────────────────────────┘

BASE DE DONNÉES NoSQL (Entrepôt moderne) :
┌────────────────────────────────────────────────────────────────────┐
│ [PACKAGE] ENTREPÔT AMAZON                                                 │
│                                                                    │
│ Organisation flexible :                                            │
│ -> Chaque boîte contient TOUT ce dont elle a besoin                 │
│ -> Pas de format imposé (livres, électronique, vêtements)           │
│ -> Boîtes auto-suffisantes (pas besoin de chercher ailleurs)        │
│ -> Peut ouvrir des entrepôts dans le monde entier                   │
│                                                                    │
│ Avantages :                                                        │
│ [OK] Flexible : chaque produit a ses propres attributs               │
│ [OK] Scalabilité infinie (nouveaux entrepôts)                        │
│ [OK] Très rapide en lecture (tout est dans la boîte)                 │
│                                                                    │
│ Inconvénients :                                                    │
│ [X] Redondance possible (même info dans plusieurs boîtes)           │
│ [X] Cohérence éventuelle (eventual consistency)                     │
│ [X] Pas de transactions multi-documents (avant MongoDB 4.0)         │
└────────────────────────────────────────────────────────────────────┘


LES 4 FAMILLES NoSQL :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. DOCUMENT STORES (MongoDB, CouchDB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Stockage : Documents JSON/BSON
Structure : Hiérarchique, flexible

Exemple MongoDB :
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "nom": "Jean Dupont",
  "age": 30,
  "email": "jean@example.com",
  "adresses": [
    {
      "type": "domicile",
      "rue": "123 Avenue des Champs",
      "ville": "Paris",
      "codePostal": "75008"
    },
    {
      "type": "travail",
      "rue": "456 Rue de Rivoli",
      "ville": "Paris",
      "codePostal": "75001"
    }
  ],
  "hobbies": ["lecture", "vélo", "cuisine"],
  "derniereConnexion": ISODate("2024-12-08T10:30:00Z")
}

Cas d'usage :
[OK] Applications web/mobile (profils utilisateurs)
[OK] CMS (Content Management Systems)
[OK] Catalogues produits e-commerce
[OK] Logs et événements


2. KEY-VALUE STORES (Redis, DynamoDB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Stockage : Paires clé-valeur simples
Structure : Dictionnaire géant

Exemple Redis :
user:1000 -> {"nom": "Jean", "age": 30}
session:abc123 -> {"userId": 1000, "expire": 1234567890}
cache:page:/home -> "<html>...</html>"

Cas d'usage :
[OK] Cache (sessions, pages web)
[OK] Files d'attente (queues)
[OK] Compteurs temps réel
[OK] Leaderboards (classements)


3. COLUMN STORES (Cassandra, HBase)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Stockage : Colonnes groupées (column families)
Structure : Table très large, sparse

Exemple Cassandra :
Row Key: user_1000
├─ profile:name -> "Jean"
├─ profile:age -> 30
├─ stats:logins -> 152
├─ stats:lastLogin -> 2024-12-08
└─ preferences:theme -> "dark"

Cas d'usage :
[OK] Séries temporelles (IoT, métriques)
[OK] Big Data analytics
[OK] Logs distribués
[OK] Recommandations


4. GRAPH DATABASES (Neo4j, OrientDB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Stockage : Nœuds et relations
Structure : Graphe

Exemple Neo4j :
(Jean)-[:AMI_DE]->(Marie)
(Jean)-[:TRAVAILLE_POUR]->(Entreprise)
(Marie)-[:HABITE_A]->(Paris)

Cas d'usage :
[OK] Réseaux sociaux (amis, followers)
[OK] Systèmes de recommandation
[OK] Détection de fraude
[OK] Graphes de connaissances


POURQUOI MONGODB ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MongoDB = "Mongo" (de "humongous" = énorme) + "DB"

HISTORIQUE :
-> Créé en 2007 par 10gen (maintenant MongoDB Inc.)
-> Open Source (SSPL depuis 2018, AGPLv3 avant)
-> Basé sur C++
-> Version actuelle : 7.x (décembre 2024)

POINTS FORTS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[OK] Schéma flexible (pas de ALTER TABLE)
[OK] Documents JSON (langage universel du web)
[OK] Requêtes puissantes (like SQL mais pour JSON)
[OK] Scalabilité horizontale native (sharding)
[OK] Haute disponibilité (replica sets)
[OK] Index riches (comme les SGBD relationnels)
[OK] Aggregation Pipeline (équivalent de SQL GROUP BY, JOIN)
[OK] Transactions ACID (depuis version 4.0)
[OK] Change Streams (notifications temps réel)
[OK] Écosystème riche (drivers pour tous les langages)

QUI UTILISE MONGODB ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> CNBC (médias)
-> Adobe (Creative Cloud)
-> Barclays (banque)
-> Bosch (industrie)
-> SEGA (jeux vidéo)
-> eBay (e-commerce)
-> Uber (géolocalisation)
-> Lyft (transport)


TABLEAU COMPARATIF : MongoDB vs PostgreSQL
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────┬───────────────────────┬─────────────────────┐
│  Caractéristique    │      PostgreSQL       │       MongoDB       │
├─────────────────────┼───────────────────────┼─────────────────────┤
│ Modèle de données   │ Relationnel (tables)  │ Documents (JSON)    │
│ Schéma              │ Rigide (DDL)          │ Flexible (dynamique)│
│ Langage requête     │ SQL                   │ MongoDB Query Lang  │
│ Transactions        │ ACID complet          │ ACID (depuis 4.0)   │
│ Jointures           │ Natives (JOIN)        │ $lookup (limité)    │
│ Scalabilité         │ Verticale surtout     │ Horizontale native  │
│ Normalisation       │ Obligatoire/conseillée│ Dénormalisation     │
│ Cohérence           │ Immédiate (CP)        │ Éventuelle (AP)     │
│ Index               │ B-Tree, Hash, etc.    │ B-Tree, Texte, Geo  │
│ Agrégation          │ GROUP BY, HAVING      │ Aggregation Pipeline│
│ Géospatial          │ PostGIS (extension)   │ Natif (2dsphere)    │
│ Full-text search    │ Basique               │ Avancé (Atlas)      │
│ Cas d'usage         │ Apps transactionnelles│ Apps évolutives     │
│ Apprentissage       │ SQL universel         │ Courbe plus douce   │
└─────────────────────┴───────────────────────┴─────────────────────┘

THÉORÈME CAP (Pourquoi NoSQL ?)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Dans un système distribué, tu peux avoir AU MAXIMUM 2 des 3 :

C = Consistency (Cohérence)
-> Tous les nœuds voient les mêmes données au même moment

A = Availability (Disponibilité)
-> Le système répond toujours (même si certains nœuds sont down)

P = Partition Tolerance (Tolérance au partitionnement)
-> Le système fonctionne malgré les pannes réseau

SGBD Relationnels traditionnels : CP
-> Privilégient Cohérence + Partition Tolerance
-> Sacrifient la Disponibilité (le système peut refuser des requêtes)

MongoDB (par défaut) : CP
-> Cohérence immédiate sur le nœud primaire
-> Disponibilité moindre si le primaire tombe (élection en cours)
-> Peut être configuré en AP (eventual consistency)

Cassandra : AP
-> Toujours disponible
-> Cohérence éventuelle (les données convergent avec le temps)
"""


# [OK] PARTIE 2 : SQL vs NoSQL - DIFFÉRENCES FONDAMENTALES

"""
┌────────────────────────────────────────────────────────────────────────┐
│              SQL vs NoSQL : CHANGEMENT DE PARADIGME                    │
└────────────────────────────────────────────────────────────────────────┘

EXEMPLE CONCRET : BLOG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MODÈLE RELATIONNEL (PostgreSQL) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Table: utilisateurs
┌────┬──────────────┬──────────────────────────┬──────────────────────┐
│ id │     nom      │          email           │   date_inscription   │
├────┼──────────────┼──────────────────────────┼──────────────────────┤
│ 1  │ Jean Dupont  │ jean@example.com         │ 2024-01-15           │
│ 2  │ Marie Martin │ marie@example.com        │ 2024-02-20           │
└────┴──────────────┴──────────────────────────┴──────────────────────┘

Table: articles
┌────┬─────────────┬──────────────────────────┬──────────────┬────────────┐
│ id │ auteur_id   │          titre           │   contenu    │    date    │
├────┼─────────────┼──────────────────────────┼──────────────┼────────────┤
│ 1  │ 1           │ Intro à PostgreSQL       │ Lorem ipsum..│ 2024-03-01 │
│ 2  │ 1           │ SQL Avancé               │ Dolor sit... │ 2024-03-15 │
│ 3  │ 2           │ Python pour débutants    │ Amet conse...│ 2024-04-01 │
└────┴─────────────┴──────────────────────────┴──────────────┴────────────┘

Table: commentaires
┌────┬────────────┬─────────────┬──────────────────────────┬────────────┐
│ id │ article_id │ auteur_id   │         contenu          │    date    │
├────┼────────────┼─────────────┼──────────────────────────┼────────────┤
│ 1  │ 1          │ 2           │ Super article !          │ 2024-03-02 │
│ 2  │ 1          │ 2           │ Merci pour le tuto       │ 2024-03-03 │
│ 3  │ 2          │ 2           │ Très utile               │ 2024-03-16 │
└────┴────────────┴─────────────┴──────────────────────────┴────────────┘

Table: tags
┌────┬─────────────┐
│ id │     nom     │
├────┼─────────────┤
│ 1  │ SQL         │
│ 2  │ Tutoriel    │
│ 3  │ Python      │
└────┴─────────────┘

Table: articles_tags (table de liaison many-to-many)
┌────────────┬────────┐
│ article_id │ tag_id │
├────────────┼────────┤
│ 1          │ 1      │
│ 1          │ 2      │
│ 2          │ 1      │
│ 3          │ 2      │
│ 3          │ 3      │
└────────────┴────────┘

REQUÊTE SQL : Récupérer un article avec son auteur, commentaires et tags
SELECT 
    a.id, a.titre, a.contenu, a.date,
    u.nom AS auteur_nom, u.email AS auteur_email,
    c.contenu AS commentaire_contenu, c.date AS commentaire_date,
    cu.nom AS commentaire_auteur,
    t.nom AS tag_nom
FROM articles a
JOIN utilisateurs u ON a.auteur_id = u.id
LEFT JOIN commentaires c ON c.article_id = a.id
LEFT JOIN utilisateurs cu ON c.auteur_id = cu.id
LEFT JOIN articles_tags at ON at.article_id = a.id
LEFT JOIN tags t ON at.tag_id = t.id
WHERE a.id = 1;

PROBLÈMES :
[X] 6 tables pour un simple blog !
[X] 5 jointures pour récupérer UN article complet
[X] Résultat : lignes dupliquées (Cartesian product partiel)
[X] Besoin de post-traitement pour regrouper commentaires et tags
[X] Performance : plusieurs accès disque, index lookups


MODÈLE DOCUMENT (MongoDB) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Collection: articles (UN seul document contient TOUT)
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "titre": "Intro à PostgreSQL",
  "contenu": "Lorem ipsum dolor sit amet...",
  "date": ISODate("2024-03-01T10:00:00Z"),
  "auteur": {
    "id": ObjectId("507f1f77bcf86cd799439012"),
    "nom": "Jean Dupont",
    "email": "jean@example.com"
  },
  "commentaires": [
    {
      "_id": ObjectId("507f1f77bcf86cd799439013"),
      "auteur": {
        "id": ObjectId("507f1f77bcf86cd799439014"),
        "nom": "Marie Martin",
        "email": "marie@example.com"
      },
      "contenu": "Super article !",
      "date": ISODate("2024-03-02T11:30:00Z")
    },
    {
      "_id": ObjectId("507f1f77bcf86cd799439015"),
      "auteur": {
        "id": ObjectId("507f1f77bcf86cd799439014"),
        "nom": "Marie Martin",
        "email": "marie@example.com"
      },
      "contenu": "Merci pour le tuto",
      "date": ISODate("2024-03-03T14:20:00Z")
    }
  ],
  "tags": ["SQL", "Tutoriel"],
  "stats": {
    "vues": 1250,
    "likes": 42
  }
}

REQUÊTE MongoDB : Récupérer le même article
db.articles.findOne({ _id: ObjectId("507f1f77bcf86cd799439011") })

AVANTAGES :
[OK] UNE seule collection
[OK] AUCUNE jointure
[OK] UN seul accès disque (si document en cache)
[OK] Résultat immédiat, format natif (JSON)
[OK] Facile d'ajouter de nouveaux champs (stats, likes, etc.)


DÉNORMALISATION : PHILOSOPHIE NoSQL
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SQL : NORMALISATION (éviter la redondance)
-> Chaque information n'existe qu'UNE FOIS
-> Relations via clés étrangères
-> Jointures pour reconstituer les données

NoSQL : DÉNORMALISATION (dupliquer pour la vitesse)
-> Dupliquer les données fréquemment accédées ensemble
-> Documents auto-suffisants
-> Trade-off : espace disque contre performance

EXEMPLE DE DUPLICATION :
Dans MongoDB, l'email de Marie apparaît dans CHAQUE commentaire
-> Si Marie change son email, il faut mettre à jour tous les articles
-> Mais en lecture, c'est ULTRA RAPIDE (pas de jointure)


QUAND UTILISER SQL vs NoSQL ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

UTILISER SQL (PostgreSQL, MySQL) SI :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[OK] Données très structurées et stables
[OK] Relations complexes entre entités
[OK] Intégrité référentielle critique (clés étrangères)
[OK] Transactions ACID absolument nécessaires
[OK] Requêtes ad-hoc complexes (Business Intelligence)
[OK] Agrégations complexes, rapports

Exemples :
-> Système bancaire (transactions financières)
-> ERP (gestion d'entreprise)
-> Système de réservation (avions, hôtels)
-> E-commerce traditionnel (commandes, paiements)


UTILISER NoSQL (MongoDB) SI :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[OK] Schéma flexible et évolutif
[OK] Besoin de scalabilité horizontale massive
[OK] Données hiérarchiques (imbrications naturelles)
[OK] Prototypage rapide (MVP, startup)
[OK] Big Data, analytics temps réel
[OK] Cache haute performance
[OK] Géolocalisation (requêtes géospatiales)
[OK] Catalogue produits avec attributs variés

Exemples :
-> Réseaux sociaux (profils, posts, likes)
-> IoT (millions de capteurs)
-> Applications mobile (offline-first)
-> CMS (blogs, sites web)
-> Catalogues e-commerce (produits hétérogènes)
-> Logs et métriques
-> Gaming (state management, leaderboards)


ANTI-PATTERNS : QUAND NE PAS UTILISER NoSQL
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[X] Besoin de jointures complexes multi-collections
   -> MongoDB n'est pas fait pour ça !
   -> $lookup est limité et lent

[X] Transactions multi-documents critiques
   -> Possible depuis MongoDB 4.0, mais moins performant que SQL

[X] Requêtes SQL complexes existantes
   -> Ne pas migrer juste "pour le fun"
   -> Le coût de migration est élevé

[X] Équipe qui ne connaît que SQL
   -> Courbe d'apprentissage
   -> Risque d'utiliser MongoDB comme une base SQL (antipattern)
"""


# [OK] PARTIE 3 : INSTALLATION ET FICHIERS CRÉÉS

"""
┌────────────────────────────────────────────────────────────────────────┐
│              INSTALLATION MONGODB (LINUX/MAC/WINDOWS)                  │
└────────────────────────────────────────────────────────────────────────┘

MÉTHODES D'INSTALLATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Package manager (apt, yum, brew)
2. Binaires officiels (.tar.gz)
3. Docker (recommandé pour développement)
4. MongoDB Atlas (cloud, gratuit pour démarrer)


INSTALLATION UBUNTU/DEBIAN :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Importer la clé GPG publique MongoDB
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
   sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor

# Créer le fichier de liste sources
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] \
https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | \
sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

# Mettre à jour les packages
sudo apt update

# Installer MongoDB
sudo apt install -y mongodb-org

# Démarrer MongoDB
sudo systemctl start mongod
sudo systemctl enable mongod

# Vérifier le statut
sudo systemctl status mongod

[ALARM_CLOCK] Durée d'installation : 2-5 minutes


INSTALLATION macOS (Homebrew) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Ajouter le tap MongoDB
brew tap mongodb/brew

# Installer MongoDB Community Edition
brew install mongodb-community@7.0

# Démarrer MongoDB comme service
brew services start mongodb-community@7.0

# Ou démarrer manuellement
mongod --config /usr/local/etc/mongod.conf


INSTALLATION WINDOWS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Télécharger l'installeur MSI depuis mongodb.com
2. Exécuter l'installeur
3. Choisir "Complete" installation
4. Installer MongoDB Compass (GUI) : OUI
5. MongoDB est installé comme service Windows
6. Démarrage automatique au boot

Répertoire par défaut :
C:\Program Files\MongoDB\Server\7.0\


INSTALLATION DOCKER (Recommandé pour développement) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Télécharger et démarrer MongoDB
docker run -d \
  --name mongodb \
  -p 27017:27017 \
  -v mongodb_data:/data/db \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD=password123 \
  mongo:7.0

# Se connecter au shell
docker exec -it mongodb mongosh -u admin -p password123 --authenticationDatabase admin

Avantages Docker :
[OK] Installation propre (pas de pollution système)
[OK] Versions multiples possibles
[OK] Facile à détruire et recréer
[OK] Même environnement sur tous les OS


STRUCTURE DES FICHIERS MONGODB :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ARBORESCENCE COMPLÈTE (Linux) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

/var/lib/mongodb/                    <- Répertoire de données (dbPath)
├── collection-0--2345678901234567.wt <- Fichier collection WiredTiger
├── collection-2--2345678901234567.wt
├── collection-4--2345678901234567.wt
├── index-1--2345678901234567.wt     <- Fichier index
├── index-3--2345678901234567.wt
├── index-5--2345678901234567.wt
├── _mdb_catalog.wt                  <- Catalogue des collections
├── sizeStorer.wt                    <- Tailles des collections
├── storage.bson                     <- Métadonnées du moteur de stockage
├── WiredTiger                       <- Fichier de contrôle WiredTiger
├── WiredTiger.basecfg               <- Configuration de base
├── WiredTiger.lock                  <- Verrou (un seul mongod à la fois)
├── WiredTiger.turtle                <- Métadonnées de démarrage
├── WiredTiger.wt                    <- Table principale WiredTiger
├── WiredTigerHS.wt                  <- History Store (MVCC)
├── mongod.lock                      <- PID du processus mongod
├── diagnostic.data/                 <- Données de diagnostic
│   ├── metrics.2024-12-08T10-00-00Z-00000
│   └── metrics.interim
└── journal/                         <- Journal (Write-Ahead Log)
    ├── WiredTigerLog.0000000001
    ├── WiredTigerLog.0000000002
    └── WiredTigerPreplog.0000000001

/var/log/mongodb/                    <- Logs
└── mongod.log                       <- Log principal

/etc/mongod.conf                     <- Configuration


DÉTAIL DE CHAQUE TYPE DE FICHIER :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. FICHIERS .wt (WiredTiger) - DONNÉES ET INDEX
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
collection-N--.wt : Données d'une collection
index-N--.wt      : Données d'un index

POURQUOI DES NOMS CRYPTIQUES ?
-> MongoDB utilise des identifiants internes
-> Le mapping nom -> fichier est dans _mdb_catalog.wt

STRUCTURE INTERNE (WiredTiger B-Tree) :
-> Arbre B+ équilibré (comme PostgreSQL/Oracle)
-> Pages de 32 KB par défaut
-> Compression SNAPPY par défaut (économie d'espace)

EXEMPLE :
Collection "utilisateurs" -> collection-4--2345678901234567.wt
Index "_id" de "utilisateurs" -> index-5--2345678901234567.wt

TAILLE TYPIQUE :
-> 10 000 documents de 1 KB ≈ 10 MB (sans compression)
-> Avec compression SNAPPY : ≈ 3-5 MB


2. _mdb_catalog.wt - CATALOGUE DES COLLECTIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RÔLE :
-> Mapping entre noms de collections/index et fichiers .wt
-> Métadonnées des collections (options, validation)
-> Équivalent de pg_class dans PostgreSQL

CONTENU TYPE (conceptuel) :
{
  "db": "myapp",
  "collections": [
    {
      "name": "utilisateurs",
      "uuid": "2345678901234567",
      "dataFile": "collection-4--2345678901234567.wt",
      "indexes": [
        {
          "name": "_id_",
          "file": "index-5--2345678901234567.wt",
          "unique": true
        }
      ]
    }
  ]
}

LECTURE AU DÉMARRAGE :
-> mongod lit ce fichier pour connaître toutes les collections
-> Reconstruction de la structure en mémoire


3. journal/ - WRITE-AHEAD LOG (WAL)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
WiredTigerLog.* : Fichiers de journal

RÔLE CRITIQUE (comme pg_wal dans PostgreSQL) :
-> Enregistre TOUTES les modifications AVANT qu'elles soient sur disque
-> Garantit la DURABILITÉ (le D dans ACID)
-> Permet la récupération après crash

FONCTIONNEMENT :
1. Client envoie : db.users.insertOne({nom: "Jean"})
2. MongoDB écrit dans le journal (RAM buffer)
3. Toutes les 50ms, flush du journal sur disque (fsync)
4. Puis MongoDB met à jour les fichiers .wt (asynchrone)

EN CAS DE CRASH :
-> Au redémarrage, MongoDB rejoue le journal
-> Récupération de toutes les opérations validées (committed)

TAILLE ET ROTATION :
-> Fichiers de 100 MB par défaut
-> Rotation automatique quand plein
-> Anciens journaux supprimés après checkpoint


4. WiredTiger.* - FICHIERS DE MÉTADONNÉES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
WiredTiger           : Fichier de contrôle principal
WiredTiger.turtle    : Métadonnées de démarrage (bootstrap)
WiredTiger.basecfg   : Configuration du moteur
WiredTiger.lock      : Verrou (empêche plusieurs mongod sur même dbPath)
WiredTigerHS.wt      : History Store (versions MVCC)

HISTORY STORE (WiredTigerHS.wt) :
-> Stocke les anciennes versions des documents (MVCC)
-> Permet les lectures cohérentes sans bloquer les écritures
-> Équivalent de UNDO tablespace dans Oracle

POURQUOI MVCC ?
-> Une transaction en lecture ne doit pas voir les modifications non validées
-> MongoDB garde plusieurs versions d'un même document
-> Chaque transaction voit une "snapshot" cohérente


5. mongod.lock - VERROU DE PROCESSUS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CONTENU :
-> PID (Process ID) du processus mongod en cours

RÔLE :
-> Empêcher plusieurs instances mongod sur le même dbPath
-> Corruption des données garantie si deux mongod écrivent simultanément

EN CAS DE CRASH :
-> Le fichier reste présent
-> Au redémarrage, mongod détecte un "dirty shutdown"
-> Lance automatiquement la récupération (replay du journal)


6. storage.bson - MÉTADONNÉES DU MOTEUR
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CONTENU (format BSON) :
{
  "storage": {
    "engine": "wiredTiger",
    "options": {}
  }
}

RÔLE :
-> Indique quel moteur de stockage est utilisé
-> Historiquement : MMAPv1 (obsolète depuis 4.0)
-> Actuellement : WiredTiger (par défaut depuis 3.2)


7. diagnostic.data/ - MÉTRIQUES DE DIAGNOSTIC
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CONTENU :
-> Métriques système collectées automatiquement
-> CPU, mémoire, I/O, latences
-> Utilisé par MongoDB Support pour diagnostics

FICHIERS :
metrics.YYYY-MM-DDTHH-MM-SS-00000 : Métriques horodatées
metrics.interim : Métriques en cours de collection

DÉSACTIVATION (si souhaité) :
# Dans mongod.conf
diagnosticDataCollectionEnabled: false


8. FICHIER DE CONFIGURATION : /etc/mongod.conf
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Format : YAML

EXEMPLE COMPLET :

# Où stocker les données
storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true                    # Activer le journal (obligatoire)
  engine: wiredTiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 1                 # Cache WiredTiger (50% RAM par défaut)
      journalCompressor: snappy      # Compression du journal
    collectionConfig:
      blockCompressor: snappy        # Compression des collections
    indexConfig:
      prefixCompression: true        # Compression des index

# Où écouter les connexions
net:
  port: 27017                        # Port par défaut
  bindIp: 127.0.0.1                  # Écouter uniquement en local (sécurité)
  # bindIp: 0.0.0.0                  # Écouter sur toutes les interfaces
  maxIncomingConnections: 65536      # Connexions max

# Sécurité
security:
  authorization: enabled             # Activer l'authentification
  # keyFile: /path/to/keyfile        # Pour replica sets

# Logs
systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log
  logAppend: true                    # Append (ne pas écraser)
  logRotate: reopen                  # Rotation des logs
  verbosity: 0                       # 0-5 (0 = minimal, 5 = debug complet)

# Réplication (pour replica sets)
#replication:
#  replSetName: rs0                  # Nom du replica set

# Sharding (pour clusters sharded)
#sharding:
#  clusterRole: configsvr            # ou shardsvr

# Processus
processManagement:
  fork: true                         # Exécuter en background (daemon)
  pidFilePath: /var/run/mongodb/mongod.pid
  timeZoneInfo: /usr/share/zoneinfo  # Support des timezones

# Opérations
operationProfiling:
  mode: slowOp                       # Profiler les opérations lentes
  slowOpThresholdMs: 100             # Seuil : > 100ms = slow

# Limites de ressources (Linux uniquement)
#setParameter:
#  enableLocalhostAuthBypass: false  # Désactiver auth bypass localhost


9. FICHIER DE LOG : /var/log/mongodb/mongod.log
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CONTENU TYPE :

{"t":{"$date":"2024-12-08T10:30:00.123Z"},"s":"I","c":"CONTROL","id":23285,
 "ctx":"main","msg":"Automatically disabling TLS 1.0, to force-enable..."}

{"t":{"$date":"2024-12-08T10:30:00.456Z"},"s":"I","c":"STORAGE","id":22297,
 "ctx":"initandlisten","msg":"WiredTiger opened","attr":{"durationMillis":234}}

{"t":{"$date":"2024-12-08T10:30:01.789Z"},"s":"I","c":"NETWORK","id":23015,
 "ctx":"listener","msg":"Listening on","attr":{"address":"127.0.0.1:27017"}}

{"t":{"$date":"2024-12-08T10:30:01.890Z"},"s":"I","c":"INDEX","id":20345,
 "ctx":"LogicalSessionCacheRefresh","msg":"Index build: done building",
 "attr":{"namespace":"config.system.sessions","index":"lsidTTLIndex"}}

FORMAT (depuis MongoDB 4.4) :
-> JSON structuré (facile à parser)
-> Niveau de sévérité : F (Fatal), E (Error), W (Warning), I (Info), D (Debug)
-> Composant : CONTROL, STORAGE, NETWORK, INDEX, QUERY, etc.

NIVEAUX DE VERBOSITÉ :
verbosity: 0  -> Minimal (production)
verbosity: 1  -> Debug léger
verbosity: 5  -> Debug complet (très verbeux)

ROTATION DES LOGS :
# Rotation manuelle
kill -SIGUSR1 <mongod_pid>

# Ou via mongosh
db.adminCommand({ logRotate: 1 })


COMPARAISON AVEC PostgreSQL :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌──────────────────────┬─────────────────────┬─────────────────────┐
│     Composant        │     PostgreSQL      │       MongoDB       │
├──────────────────────┼─────────────────────┼─────────────────────┤
│ Données              │ base/OID/           │ *.wt files          │
│ WAL/Journal          │ pg_wal/             │ journal/            │
│ Catalogue            │ pg_class, pg_attr   │ _mdb_catalog.wt     │
│ Configuration        │ postgresql.conf     │ mongod.conf         │
│ Logs                 │ pg_log/             │ mongod.log          │
│ Verrou instance      │ postmaster.pid      │ mongod.lock         │
│ Moteur stockage      │ Heap/TOAST          │ WiredTiger          │
│ MVCC                 │ xmin/xmax           │ WiredTigerHS        │
└──────────────────────┴─────────────────────┴─────────────────────┘
"""


# [OK] PARTIE 4 : ARCHITECTURE INTERNE DE MONGODB

"""
┌────────────────────────────────────────────────────────────────────────┐
│                   ARCHITECTURE MONGODB                                 │
└────────────────────────────────────────────────────────────────────────┘

VUE D'ENSEMBLE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌─────────────────────────────────────────────────────────────────────┐
│                       PROCESSUS MONGOD                              │
│                  (Instance MongoDB unique)                          │
│                                                                     │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │                   COUCHE CLIENT                                │ │
│  │  - Wire Protocol (BSON over TCP)                               │ │
│  │  - Authentification (SCRAM-SHA-256, x.509)                     │ │
│  │  - Connexions persistantes                                     │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                              v                                      │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │                   COUCHE QUERY                                 │ │
│  │  - Query Parser (analyse du JSON)                              │ │
│  │  - Query Optimizer (choix du plan)                             │ │
│  │  - Query Executor (exécution)                                  │ │
│  │  - Aggregation Framework                                       │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                              v                                      │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │              STORAGE ENGINE (WiredTiger)                       │ │
│  │                                                                │ │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐                │ │
│  │  │   Cache    │  │  Journal   │  │   MVCC     │                │ │
│  │  │ (WiredTiger│  │  (Write-   │  │ (Snapshots)│                │ │
│  │  │   Cache)   │  │   Ahead    │  │            │                │ │
│  │  │            │  │    Log)    │  │            │                │ │
│  │  └────────────┘  └────────────┘  └────────────┘                │ │
│  │                                                                │ │
│  │  ┌────────────┐  ┌────────────┐                                │ │
│  │  │ Compression│  │  Checkpoints│                               │ │
│  │  │  (Snappy,  │  │  (Persist   │                               │ │
│  │  │   Zstd)    │  │   to disk)  │                               │ │
│  │  └────────────┘  └────────────┘                                │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                              v                                      │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │                   FICHIERS SUR DISQUE                          │ │
│  │  - Collections (*.wt)                                          │ │
│  │  - Index (*.wt)                                                │ │
│  │  - Journal (WiredTigerLog.*)                                   │ │
│  └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘


DÉTAIL DES COUCHES :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. COUCHE CLIENT (Wire Protocol)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PROTOCOLE :
-> BSON (Binary JSON) sur TCP
-> Port par défaut : 27017
-> Messages structurés : OP_QUERY, OP_INSERT, OP_UPDATE, OP_DELETE, OP_MSG

CONNEXION :
1. Client établit connexion TCP
2. Handshake (négociation version protocole)
3. Authentification (si activée)
4. Connexion persistante (pas de nouveau handshake à chaque requête)

AUTHENTICATION MECHANISMS :
-> SCRAM-SHA-1 (par défaut jusqu'à 3.6)
-> SCRAM-SHA-256 (par défaut depuis 4.0)
-> x.509 (certificats)
-> LDAP (enterprise)
-> Kerberos (enterprise)

POOLING DE CONNEXIONS :
-> Chaque driver maintient un pool de connexions
-> Réutilisation des connexions (évite handshake répétés)
-> Taille par défaut : 100 connexions

EXEMPLE (Driver Node.js) :
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017', {
  maxPoolSize: 50,        // Max 50 connexions simultanées
  minPoolSize: 10,        // Garder 10 connexions ouvertes
  maxIdleTimeMS: 30000    // Fermer après 30s d'inactivité
});


2. COUCHE QUERY (Traitement des requêtes)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

A) QUERY PARSER (Analyse)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ENTRÉE : Requête JSON/BSON
db.users.find({ age: { $gt: 25 }, ville: "Paris" })

TÂCHES :
-> Validation syntaxique du JSON
-> Vérification des opérateurs ($gt, $in, $regex, etc.)
-> Construction d'un arbre de requête (Query Tree)

QUERY TREE (conceptuel) :
{
  "operation": "find",
  "collection": "users",
  "filter": {
    "operator": "AND",
    "conditions": [
      { "field": "age", "op": "$gt", "value": 25 },
      { "field": "ville", "op": "$eq", "value": "Paris" }
    ]
  }
}


B) QUERY OPTIMIZER (Optimisation)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RÔLE :
-> Choisir le MEILLEUR plan d'exécution
-> Décider quels index utiliser
-> Estimer le coût de chaque plan

PLANS POSSIBLES :
Plan A : COLLSCAN (Collection Scan - parcours séquentiel)
-> Lire TOUS les documents de la collection
-> Coût : O(n) où n = nombre de documents

Plan B : IXSCAN (Index Scan)
-> Utiliser l'index sur "age"
-> Puis filtrer sur "ville" en mémoire
-> Coût : O(log n + k) où k = documents retournés

Plan C : IXSCAN + IXSCAN (Intersection de deux index)
-> Utiliser l'index sur "age" ET l'index sur "ville"
-> Faire l'intersection des résultats
-> Coût : O(log n + log n + k)

QUERY PLANNER CACHE :
-> MongoDB CACHE les plans d'exécution (comme PostgreSQL)
-> Évite de re-planifier les mêmes requêtes
-> Invalidation automatique après 1000 writes ou éviction

VOIR LE PLAN D'EXÉCUTION :
db.users.find({ age: { $gt: 25 } }).explain("executionStats")

SORTIE TYPIQUE :
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "mydb.users",
    "winningPlan": {
      "stage": "FETCH",                    // Récupérer les documents
      "inputStage": {
        "stage": "IXSCAN",                 // Via index scan
        "indexName": "age_1",
        "indexBounds": {
          "age": ["(25, inf.0]"]           // age > 25
        }
      }
    },
    "rejectedPlans": [                     // Plans rejetés
      {
        "stage": "COLLSCAN",               // Collection scan moins efficace
        "filter": { "age": { "$gt": 25 } }
      }
    ]
  },
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 150,                      // Documents retournés
    "executionTimeMillis": 2,              // Temps d'exécution
    "totalKeysExamined": 150,              // Clés d'index examinées
    "totalDocsExamined": 150,              // Documents examinés
    "executionStages": {
      "stage": "FETCH",
      "nReturned": 150,
      "executionTimeMillisEstimate": 0,
      "works": 151,
      "advanced": 150,
      "inputStage": {
        "stage": "IXSCAN",
        "nReturned": 150,
        "indexName": "age_1",
        "keysExamined": 150
      }
    }
  }
}

CRITÈRES DE CHOIX :
-> Sélectivité de l'index (combien de docs retournés ?)
-> Taille de la collection
-> Statistiques sur les données
-> Présence de sort, projection


C) QUERY EXECUTOR (Exécution)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RÔLE :
-> Exécuter le plan d'exécution choisi
-> Appeler le Storage Engine (WiredTiger)
-> Retourner les résultats au client

ÉTAPES (exemple IXSCAN) :
1. Accès à l'index B-Tree
2. Recherche des clés correspondantes (age > 25)
3. Pour chaque clé : récupérer le RecordId (pointeur vers document)
4. Fetch du document complet depuis la collection
5. Application des filtres additionnels (en mémoire)
6. Projection (si spécifiée)
7. Tri (si ORDER BY)
8. Limite (si LIMIT)
9. Retour au client

CURSOR :
-> MongoDB utilise des CURSORS (comme SQL)
-> Retourne les résultats par batch (pas tout d'un coup)
-> Batch size par défaut : 101 documents (premier batch)
-> Puis 16 MB ou 1000 documents par batch

EXEMPLE :
const cursor = db.users.find({ age: { $gt: 25 } });
cursor.forEach(doc => console.log(doc));

// Itération batch par batch (transparent pour le dev)


3. STORAGE ENGINE : WIREDTIGER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

HISTORIQUE :
-> Avant MongoDB 3.2 : MMAPv1 (Memory-Mapped Files)
-> Depuis MongoDB 3.2 : WiredTiger par défaut
-> WiredTiger : créé par les fondateurs de Berkeley DB

POURQUOI WIREDTIGER ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[OK] MVCC (Multi-Version Concurrency Control)
   -> Lectures sans bloquer les écritures
   -> Snapshots cohérents

[OK] Compression
   -> Snappy (par défaut) : rapide, compression moyenne (~50%)
   -> Zlib : lent, compression élevée (~70%)
   -> Zstd : bon compromis (depuis 4.2)

[OK] Document-level locking
   -> Verrous au niveau document (pas table entière)
   -> Haute concurrence

[OK] Checkpoints
   -> Persistance asynchrone sur disque
   -> Toutes les 60 secondes par défaut

[OK] Cache interne
   -> Cache LRU des pages récentes
   -> 50% de la RAM par défaut


A) CACHE WIREDTIGER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TAILLE :
-> Par défaut : 50% de (RAM - 1 GB)
-> Exemple : Serveur avec 8 GB RAM
   -> Cache = 50% * (8 - 1) = 3.5 GB

CONFIGURATION :
storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 4

CONTENU DU CACHE :
-> Pages de données (documents)
-> Pages d'index
-> Versions MVCC récentes

ÉVICTION :
-> Algorithme LRU (Least Recently Used)
-> Quand le cache est plein, éviction des pages les moins utilisées
-> Écriture sur disque si page modifiée (dirty page)

IMPORTANCE :
-> Cache Hit = ultra rapide (RAM)
-> Cache Miss = lent (lecture disque)
-> Ratio cache hit > 95% = bon


B) MVCC (Multi-Version Concurrency Control)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PRINCIPE :
-> Ne jamais écraser un document en place
-> Créer une nouvelle version
-> Garder les anciennes versions temporairement

EXEMPLE :
Document initial (version 1) :
{ _id: 1, nom: "Jean", age: 30 }

Transaction A commence (lecture) :
-> Prend un snapshot de version 1

Transaction B écrit (update age = 31) :
-> Crée version 2 : { _id: 1, nom: "Jean", age: 31 }
-> Version 1 reste présente (pour transaction A)

Transaction A lit à nouveau :
-> Voit toujours version 1 (cohérence du snapshot)

Transaction A se termine :
-> Version 1 peut être supprimée (garbage collection)

AVANTAGES :
[OK] Pas de read locks (lectures non bloquantes)
[OK] Cohérence des lectures (snapshot isolation)
[OK] Haute concurrence

HISTORY STORE (WiredTigerHS.wt) :
-> Stocke les anciennes versions
-> Nettoyé automatiquement quand plus nécessaire


C) JOURNAL (Write-Ahead Log)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FONCTIONNEMENT :
1. Client : db.users.insertOne({ nom: "Jean" })
2. WiredTiger écrit dans le journal (en RAM d'abord)
3. Toutes les 50 ms : fsync du journal sur disque
4. Client reçoit acknowledgment (write concern "acknowledged")
5. Plus tard (checkpoint) : données écrites dans les .wt files

POURQUOI 50ms ?
-> Trade-off performance vs durabilité
-> Grouper plusieurs writes en un seul fsync
-> Réduire la charge I/O

WRITE CONCERNS :
{ w: "majority" }        // Attendre réplication majorité des nœuds
{ w: 1, j: true }        // Attendre écriture journal
{ w: 1, j: false }       // Pas d'attente journal (plus rapide, risqué)

EN CAS DE CRASH :
-> Au redémarrage : replay du journal depuis dernier checkpoint
-> Récupération de toutes les opérations journalisées
-> Temps de récupération : quelques secondes généralement


D) CHECKPOINTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DÉFINITION :
-> Point de cohérence où toutes les données en cache sont écrites sur disque

FRÉQUENCE :
-> Par défaut : toutes les 60 secondes
-> Ou quand 2 GB de journal accumulé

PROCESSUS :
1. WiredTiger gèle momentanément les écritures
2. Écrit toutes les pages dirty du cache sur disque
3. Met à jour WiredTiger.turtle (metadata de checkpoint)
4. Marque les anciens journaux comme recyclables
5. Reprend les écritures

DURÉE TYPIQUE :
-> Quelques millisecondes à quelques secondes
-> Dépend du volume de données dirty

IMPACT :
-> Peut causer des micro-pauses
-> Optimisé pour minimiser l'impact


E) COMPRESSION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SNAPPY (par défaut) :
-> Compression : ~50% de réduction
-> Vitesse : très rapide
-> CPU : faible impact
-> Recommandé pour la plupart des cas

ZLIB :
-> Compression : ~70% de réduction
-> Vitesse : lent
-> CPU : impact moyen
-> Pour données rarement accédées

ZSTD (depuis 4.2) :
-> Compression : ~60-65% de réduction
-> Vitesse : rapide
-> CPU : faible impact
-> Meilleur compromis que Snappy

NONE :
-> Pas de compression
-> Maximum de performance CPU
-> Plus d'espace disque

CONFIGURATION :
storage:
  wiredTiger:
    collectionConfig:
      blockCompressor: snappy    # ou zlib, zstd, none
    indexConfig:
      prefixCompression: true    # Compression des index

EXEMPLE CONCRET :
Collection non compressée : 10 GB
Avec Snappy : ~5 GB
Avec Zstd : ~4 GB
Avec Zlib : ~3 GB


F) LOCKING (Verrouillage)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
NIVEAUX DE VERROUS :

1. Global Lock (instance entière)
   -> Très rare (shutdown, backup)

2. Database Lock (base de données)
   -> Operations administratives

3. Collection Lock (collection)
   -> DDL operations (drop, create index)

4. Document Lock (document individuel)
   -> Read/Write sur documents
   -> Granularité fine = haute concurrence

MODES :
-> IS (Intent Shared) : intention de lire
-> IX (Intent Exclusive) : intention d'écrire
-> S (Shared) : lecture
-> X (Exclusive) : écriture

EXEMPLE :
Transaction A : db.users.updateOne({ _id: 1 }, { $set: { age: 31 } })
-> Prend un verrou X sur document _id: 1

Transaction B : db.users.updateOne({ _id: 2 }, { $set: { age: 25 } })
-> Prend un verrou X sur document _id: 2
-> Pas de conflit ! (documents différents)

Transaction C : db.users.updateOne({ _id: 1 }, { $set: { ville: "Paris" } })
-> Attend que transaction A libère le verrou sur _id: 1

DEADLOCK DETECTION :
-> MongoDB détecte automatiquement les deadlocks
-> Abort une transaction et retry


COMPARAISON AVEC POSTGRESQL :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌──────────────────────┬─────────────────────┬─────────────────────┐
│     Composant        │     PostgreSQL      │  MongoDB/WiredTiger │
├──────────────────────┼─────────────────────┼─────────────────────┤
│ MVCC                 │ xmin/xmax dans tuple│ History Store       │
│ Cache                │ Shared Buffers      │ WiredTiger Cache    │
│ WAL                  │ pg_wal/             │ journal/            │
│ Checkpoint           │ Checkpointer process│ WiredTiger threads  │
│ Compression          │ TOAST (limité)      │ Snappy/Zlib/Zstd    │
│ Locking              │ Row-level (MVCC)    │ Document-level      │
│ Durabilité           │ fsync par défaut    │ Journal 50ms        │
└──────────────────────┴─────────────────────┴─────────────────────┘
"""


# [OK] PARTIE 5 : FORMAT BSON vs JSON

"""
┌────────────────────────────────────────────────────────────────────────┐
│                    BSON : Binary JSON                                  │
└────────────────────────────────────────────────────────────────────────┘

POURQUOI BSON ET PAS JSON ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

JSON (JavaScript Object Notation) :
-> Format TEXTE
-> Lisible par les humains
-> Encodage UTF-8
-> Parsing lent (parser du texte)
-> Types limités : string, number, boolean, null, array, object

BSON (Binary JSON) :
-> Format BINAIRE
-> Pas lisible directement
-> Encodage binaire compact
-> Parsing rapide (accès direct aux bytes)
-> Types étendus : Date, ObjectId, Binary, Int32, Int64, Decimal128, etc.


EXEMPLE COMPARATIF :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

JSON (texte) :
{
  "nom": "Jean Dupont",
  "age": 30,
  "actif": true,
  "dateInscription": "2024-03-15T10:30:00Z"
}

Taille : 98 bytes (en UTF-8)


BSON (binaire) :
\x5e\x00\x00\x00                           // Taille totale : 94 bytes
\x02nom\x00\x0c\x00\x00\x00Jean Dupont\x00 // String
\x10age\x00\x1e\x00\x00\x00                // Int32: 30
\x08actif\x00\x01                          // Boolean: true
\x09dateInscription\x00\x80\x3e...         // Date (timestamp 64-bit)
\x00                                       // Fin du document

Taille : 94 bytes

AVANTAGES BSON :
[OK] Légèrement plus compact (surtout pour nombres)
[OK] Parsing 10-100x plus rapide
[OK] Accès direct aux champs (sans parser tout le document)
[OK] Types natifs (Date, ObjectId, etc.)


TYPES BSON :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. OBJECTID (identifiant unique)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ObjectId("507f1f77bcf86cd799439011")

STRUCTURE (12 bytes) :
[4 bytes: timestamp Unix] [5 bytes: random] [3 bytes: counter]
  ^                         ^                ^
  Secondes depuis epoch     Valeur aléatoire Compteur auto-incrémenté

EXEMPLE :
507f1f77 bcf86cd799 439011
   ^         ^         ^
   Timestamp Random    Counter

AVANTAGES :
[OK] Génération distribuée (pas besoin de serveur central)
[OK] Probabilité de collision infinitésimale
[OK] Tri chronologique naturel (timestamp en premier)
[OK] Contient la date de création

EXTRACTION DE LA DATE :
const objectId = ObjectId("507f1f77bcf86cd799439011");
const timestamp = objectId.getTimestamp();
// 2012-10-17T20:46:31.000Z


2. DATE (timestamp)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ISODate("2024-12-08T10:30:00.123Z")

STOCKAGE :
-> 64-bit integer (millisecondes depuis Unix epoch)
-> Précision : milliseconde

EXEMPLE :
new Date()                    // Date actuelle
new Date("2024-12-08")        // Date spécifique
ISODate("2024-12-08T10:30:00Z")

COMPARAISON AVEC JSON :
-> JSON : string "2024-12-08T10:30:00Z" (parsing nécessaire)
-> BSON : integer 1733654400123 (comparaison directe)


3. NOMBRES (Int32, Int64, Double, Decimal128)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TYPES :
-> Double (64-bit floating point) : par défaut pour nombres
-> Int32 (32-bit integer) : NumberInt(42)
-> Int64 (64-bit integer) : NumberLong(9223372036854775807)
-> Decimal128 (128-bit decimal) : NumberDecimal("123.45")

PROBLÈME AVEC JSON :
-> JSON n'a qu'un type "number" (imprécis)
-> 0.1 + 0.2 = 0.30000000000000004 (floating point)

SOLUTION BSON :
-> Decimal128 pour calculs financiers précis
-> Stockage décimal exact

EXEMPLE :
{
  prix: NumberDecimal("19.99"),      // Précis
  quantite: NumberInt(5),            // Entier
  total: NumberDecimal("99.95")      // Précis
}


4. BINARY (données binaires)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BinData(0, "SGVsbG8gV29ybGQh")

SOUS-TYPES :
-> 0x00 : Generic binary
-> 0x03 : UUID
-> 0x05 : MD5 hash
-> 0x80 : User-defined

EXEMPLE :
{
  photo: BinData(0, "iVBORw0KGgoAAAANSUhEUgAA..."),
  uuid: UUID("550e8400-e29b-41d4-a716-446655440000")
}


5. AUTRES TYPES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Null         : null
Boolean      : true / false
String       : "texte"
Array        : [1, 2, 3]
Object       : { clé: "valeur" }
Regex        : /pattern/flags
Timestamp    : Timestamp(1701234567, 1) (usage interne réplication)
MinKey       : -∞ (comparaison)
MaxKey       : +∞ (comparaison)


TAILLE MAXIMALE D'UN DOCUMENT :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
LIMITE : 16 MB par document

POURQUOI 16 MB ?
-> Éviter les documents monstrueux
-> Garantir performance réseau/mémoire
-> Encourager la dénormalisation raisonnable

SI BESOIN DE PLUS :
-> GridFS (système de fichiers distribué)
-> Divise les fichiers en chunks de 255 KB
-> Stocke dans collections fs.files et fs.chunks

EXEMPLE GRIDFS :
const bucket = new GridFSBucket(db);
const uploadStream = bucket.openUploadStream('video.mp4');
fs.createReadStream('video.mp4').pipe(uploadStream);


CONVERSION JSON <-> BSON :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AUTOMATIQUE DANS LES DRIVERS :
// JavaScript
const doc = { nom: "Jean", age: 30 };        // JSON
await db.collection.insertOne(doc);           // Converti en BSON
const result = await db.collection.findOne(); // BSON converti en JSON

// Python
doc = {"nom": "Jean", "age": 30}              # Dict
collection.insert_one(doc)                    # Converti en BSON
result = collection.find_one()                # BSON converti en dict

MANUEL (si nécessaire) :
const BSON = require('bson');
const bson = BSON.serialize({ nom: "Jean" }); // JSON -> BSON
const json = BSON.deserialize(bson);          // BSON -> JSON
"""


# [OK] PARTIE 6 : VOYAGE COMPLET D'UNE REQUÊTE

"""
┌────────────────────────────────────────────────────────────────────────┐
│     SCÉNARIO : INSERT + FIND DEPUIS DAKAR VERS SERVEUR LONDRES         │
└────────────────────────────────────────────────────────────────────────┘

CONTEXTE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Tu es à DAKAR (Sénégal)
Serveur MongoDB à LONDRES (AWS eu-west-2)
Distance : ~4 500 km
Latence réseau : ~150-200 ms (aller-retour)

Serveur MongoDB (Londres) :
-> IP : 51.124.45.67:27017
-> Base : ecommerce
-> Collection : produits
-> Replica Set : rs0 (3 nœuds)


ÉTAPE 0 : CONFIGURATION ET CONNEXION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

INSTALLATION DU DRIVER (Node.js) :
npm install mongodb

CODE DE CONNEXION :
const { MongoClient } = require('mongodb');

const uri = "mongodb://admin:password@51.124.45.67:27017/ecommerce?" +
            "authSource=admin&" +
            "replicaSet=rs0&" +
            "readPreference=primary&" +
            "ssl=true";

const client = new MongoClient(uri, {
  maxPoolSize: 10,
  serverSelectionTimeoutMS: 5000,
  socketTimeoutMS: 45000,
});


ÉTAPE 1 : ÉTABLISSEMENT DE LA CONNEXION (DAKAR -> LONDRES)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

await client.connect();

1. RÉSOLUTION DNS (Dakar) :
   -> Résoudre "51.124.45.67" (déjà une IP, pas de DNS)
   [TEMPS] Temps : 0 ms

2. HANDSHAKE TCP (Dakar -> Londres) :
   a) SYN (Dakar -> Londres) : [TEMPS] +100 ms
   b) SYN-ACK (Londres -> Dakar) : [TEMPS] +100 ms
   c) ACK (Dakar -> Londres) : [TEMPS] +100 ms
   
   Total TCP : ~300 ms

3. HANDSHAKE TLS/SSL (si ssl: true) :
   a) Client Hello (Dakar -> Londres) : [TEMPS] +100 ms
   b) Server Hello + Certificat (Londres -> Dakar) : [TEMPS] +100 ms
   c) Key Exchange (Dakar <-> Londres) : [TEMPS] +200 ms
   
   Total SSL : ~400 ms

4. HANDSHAKE MONGODB (Wire Protocol) :
   
   a) Client envoie OP_COMPRESSED (Dakar -> Londres) :
      Message : "isMaster" command
      {
        "isMaster": 1,
        "client": {
          "application": {"name": "myapp"},
          "driver": {"name": "nodejs", "version": "6.3.0"},
          "os": {"type": "Linux", "architecture": "x64"}
        },
        "compression": ["snappy", "zstd"],
        "saslSupportedMechs": "admin.admin"
      }
      [TEMPS] +100 ms
   
   b) Serveur répond (Londres -> Dakar) :
      {
        "ismaster": true,
        "topologyVersion": {...},
        "maxBsonObjectSize": 16777216,      // 16 MB
        "maxMessageSizeBytes": 48000000,
        "maxWriteBatchSize": 100000,
        "localTime": ISODate("2024-12-08T10:30:00Z"),
        "logicalSessionTimeoutMinutes": 30,
        "minWireVersion": 0,
        "maxWireVersion": 17,
        "readOnly": false,
        "compression": ["snappy", "zstd"],
        "saslSupportedMechs": ["SCRAM-SHA-1", "SCRAM-SHA-256"],
        "ok": 1
      }
      [TEMPS] +100 ms

5. AUTHENTIFICATION SCRAM-SHA-256 (Dakar <-> Londres) :
   
   a) Client -> Serveur : SCRAM start
      {
        "saslStart": 1,
        "mechanism": "SCRAM-SHA-256",
        "payload": BinData(0, "n,,n=admin,r=fyko+d2lbbFgONRv9qkxdawL")
      }
      [TEMPS] +100 ms
   
   b) Serveur -> Client : Challenge
      {
        "conversationId": 1,
        "payload": BinData(0, "r=fyko+...HNI,s=QSXCR+Q6sek8bf92,i=4096"),
        "done": false,
        "ok": 1
      }
      [TEMPS] +100 ms
   
   c) Client calcule réponse (Dakar, local) :
      -> PBKDF2 avec 4096 iterations
      -> Hash du mot de passe + salt + nonce
      [TEMPS] ~10 ms (local)
   
   d) Client -> Serveur : Response
      {
        "saslContinue": 1,
        "conversationId": 1,
        "payload": BinData(0, "c=biws,r=fyko+...=")
      }
      [TEMPS] +100 ms
   
   e) Serveur vérifie (Londres, local) :
      -> Comparaison avec hash stocké
      -> Génération token session
      [TEMPS] ~5 ms (local)
   
   f) Serveur -> Client : Success
      {
        "conversationId": 1,
        "payload": BinData(0, "v=rmF9p...="),
        "done": true,
        "ok": 1
      }
      [TEMPS] +100 ms
   
   Total authentification : ~500 ms

6. DÉCOUVERTE DU REPLICA SET (Londres, local) :
   -> Client contacte chaque membre du replica set
   -> Détermine qui est PRIMARY, SECONDARY
   -> Établit des connexions au pool
   [TEMPS] +300 ms

TOTAL CONNEXION : ~1600 ms (1.6 secondes)


ÉTAPE 2 : INSERT D'UN DOCUMENT (DAKAR -> LONDRES)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CODE (Dakar) :
const db = client.db('ecommerce');
const collection = db.collection('produits');

const produit = {
  nom: "Djembé artisanal",
  description: "Djembé fait main en bois de Lenke",
  prix: 45000,              // FCFA
  categorie: "Instruments",
  stock: 15,
  origine: "Dakar",
  tags: ["musique", "artisanat", "traditionnel"],
  images: ["djembe1.jpg", "djembe2.jpg"],
  dateAjout: new Date()
};

const result = await collection.insertOne(produit, {
  writeConcern: { w: "majority", j: true }
});

PROCESSUS DÉTAILLÉ :

1. PRÉPARATION CLIENT (Dakar, local) :
   
   a) Génération de l'_id :
      -> ObjectId("6752a1b5c4d6e7f8a9b0c1d2")
      -> Timestamp: 2024-12-08 10:30:00
      -> Random: c4d6e7f8a9
      -> Counter: b0c1d2
      [TEMPS] <1 ms
   
   b) Ajout de l'_id au document :
      produit._id = ObjectId("6752a1b5c4d6e7f8a9b0c1d2");
   
   c) Sérialisation en BSON :
      JSON (347 bytes) -> BSON (329 bytes)
      [TEMPS] ~1 ms
   
   d) Compression (Snappy) :
      BSON (329 bytes) -> Compressed (198 bytes)
      [TEMPS] <1 ms

2. ENVOI DU MESSAGE OP_MSG (Dakar -> Londres) :
   
   Message Wire Protocol :
   {
     "insert": "produits",
     "documents": [
       { _id: ObjectId(...), nom: "Djembé...", ... }
     ],
     "ordered": true,
     "writeConcern": { "w": "majority", "j": true }
   }
   
   Taille : ~250 bytes (compressé)
   [TEMPS] +100 ms (latence réseau)

3. RÉCEPTION PAR LE PRIMARY (Londres) :
   
   a) Décompression (Snappy) :
      [TEMPS] <1 ms
   
   b) Désérialisation BSON -> structure interne :
      [TEMPS] ~1 ms
   
   c) Validation :
      -> Taille < 16 MB ? [OK]
      -> _id unique ? (vérification index) [OK]
      -> Schéma valide ? (si validation schema activée)
      [TEMPS] ~2 ms

4. ÉCRITURE DANS LE JOURNAL (Londres, PRIMARY) :
   
   a) Génération d'une opération (oplog entry) :
      {
        "ts": Timestamp(1733654400, 1),
        "t": 5,
        "h": NumberLong("1234567890"),
        "v": 2,
        "op": "i",                          // insert
        "ns": "ecommerce.produits",
        "ui": UUID("..."),
        "o": { _id: ObjectId(...), ... }    // document
      }
      [TEMPS] ~1 ms
   
   b) Écriture dans WiredTiger Journal (RAM buffer) :
      [TEMPS] <1 ms
   
   c) Attente fsync du journal (writeConcern j: true) :
      -> Prochain flush : dans max 50 ms
      -> Ou immédiat si buffer plein
      [TEMPS] ~0-50 ms (supposons 25 ms en moyenne)

5. ÉCRITURE DANS LA COLLECTION (Londres, PRIMARY) :
   
   a) Insertion dans WiredTiger Cache :
      -> Ajout à la B-Tree en mémoire
      -> Document dans collection-.wt (cache)
      -> Mise à jour index _id dans index-.wt (cache)
      [TEMPS] ~3 ms
   
   b) PAS d'écriture disque immédiate (asynchrone) :
      -> Sera fait au prochain checkpoint (dans 60s)
      -> Ou si éviction du cache

6. RÉPLICATION AUX SECONDARIES (Londres, PRIMARY -> SECONDARIES) :
   
   PRIMARY a 2 SECONDARIES dans le replica set
   
   a) PRIMARY envoie l'oplog entry aux SECONDARIES :
      -> Via connexions persistantes
      -> SECONDARY-1 (même datacenter Londres) : [TEMPS] ~2 ms
      -> SECONDARY-2 (même datacenter Londres) : [TEMPS] ~2 ms
   
   b) SECONDARIES écrivent dans leur journal :
      [TEMPS] ~25 ms chacun (fsync)
   
   c) SECONDARIES appliquent l'opération :
      [TEMPS] ~3 ms chacun
   
   d) SECONDARIES envoient ACK au PRIMARY :
      [TEMPS] ~2 ms
   
   Total réplication : ~30 ms (en parallèle)

7. WRITE CONCERN "majority" :
   
   -> PRIMARY attend ACK de la MAJORITÉ (2/3 nœuds minimum)
   -> PRIMARY + SECONDARY-1 ont confirmé : [OK] Majorité atteinte
   [TEMPS] ~30 ms (inclus dans étape 6)

8. RÉPONSE AU CLIENT (Londres -> Dakar) :
   
   Message :
   {
     "ok": 1,
     "n": 1,
     "insertedId": ObjectId("6752a1b5c4d6e7f8a9b0c1d2")
   }
   
   [TEMPS] +100 ms (latence réseau)

RÉCAPITULATIF TEMPS INSERT :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Préparation client :           3 ms
Envoi (Dakar -> Londres) :    100 ms
Traitement PRIMARY :            5 ms
Journal fsync :                25 ms
Insertion cache :               3 ms
Réplication majority :         30 ms
Réponse (Londres -> Dakar) :   100 ms
──────────────────────────────────────
TOTAL :                       ~266 ms

SANS write concern "majority" (w: 1) :
-> Pas d'attente réplication : -30 ms
-> Total : ~236 ms


ÉTAPE 3 : FIND (RECHERCHE) D'UN DOCUMENT (DAKAR -> LONDRES)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CODE (Dakar) :
const produit = await collection.findOne({
  _id: ObjectId("6752a1b5c4d6e7f8a9b0c1d2")
});

PROCESSUS DÉTAILLÉ :

1. PRÉPARATION CLIENT (Dakar, local) :
   
   a) Construction de la requête :
      {
        "find": "produits",
        "filter": { "_id": ObjectId("6752a1b5c4d6e7f8a9b0c1d2") },
        "limit": 1,
        "singleBatch": true
      }
   
   b) Sérialisation BSON :
      [TEMPS] <1 ms
   
   c) Compression :
      [TEMPS] <1 ms

2. ENVOI (Dakar -> Londres) :
   [TEMPS] +100 ms

3. RÉCEPTION PAR LE PRIMARY (ou SECONDARY si readPreference) :
   
   a) Décompression + Désérialisation :
      [TEMPS] ~1 ms

4. QUERY PARSING (Londres) :
   
   a) Analyse du filtre :
      -> Champ : _id
      -> Opérateur : $eq (égalité implicite)
      -> Valeur : ObjectId(...)
      [TEMPS] <1 ms
   
   b) Construction du Query Tree :
      {
        "operation": "find",
        "collection": "produits",
        "filter": {
          "field": "_id",
          "op": "$eq",
          "value": ObjectId("6752a1b5c4d6e7f8a9b0c1d2")
        },
        "limit": 1
      }
      [TEMPS] <1 ms

5. QUERY PLANNING (Londres) :
   
   a) Recherche dans le plan cache :
      -> Cache hit ? OUI (requête par _id très commune)
      -> Plan : IDHACK (optimisation spéciale pour _id)
      [TEMPS] <1 ms
   
   Si cache miss :
   Plans possibles :
   -> IDHACK : accès direct via index _id (O(1))
   -> IXSCAN : scan de l'index _id (O(log n))
   -> COLLSCAN : scan de toute la collection (O(n))
   
   Choix : IDHACK (le plus rapide)
   [TEMPS] ~2 ms (si cache miss)

6. QUERY EXECUTION (Londres) :
   
   a) IDHACK : Accès direct par _id
      -> Index _id_ est un B-Tree
      -> Clé : ObjectId -> RecordId
      
      Vérification dans WiredTiger Cache :
      -> Index page en cache ? [OK] (très probable)
      [TEMPS] <1 ms (cache hit)
      
      Sinon (cache miss) :
      -> Lecture disque index-.wt
      [TEMPS] ~5 ms (SSD)
   
   b) Récupération du RecordId :
      RecordId = 42:1234
      (file 42, offset 1234)
      [TEMPS] <1 ms
   
   c) Fetch du document complet :
      Vérification dans WiredTiger Cache :
      -> Document en cache ? [OK] (vient d'être inséré)
      [TEMPS] <1 ms (cache hit)
      
      Sinon (cache miss) :
      -> Lecture disque collection-.wt
      [TEMPS] ~5 ms (SSD)
   
   d) Vérification MVCC :
      -> Document visible pour cette transaction ? [OK]
      [TEMPS] <1 ms
   
   Total execution (cache hit) : ~3 ms
   Total execution (cache miss) : ~15 ms

7. CONSTRUCTION DE LA RÉPONSE (Londres) :
   
   a) Sérialisation BSON :
      [TEMPS] ~1 ms
   
   b) Compression :
      [TEMPS] <1 ms

8. ENVOI (Londres -> Dakar) :
   [TEMPS] +100 ms

9. RÉCEPTION CLIENT (Dakar) :
   
   a) Décompression + Désérialisation :
      [TEMPS] ~1 ms
   
   b) Conversion en objet JavaScript :
      [TEMPS] <1 ms

RÉCAPITULATIF TEMPS FIND :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Préparation client :           2 ms
Envoi (Dakar -> Londres) :    100 ms
Parsing :                      1 ms
Planning (cache hit) :         1 ms
Execution (cache hit) :        3 ms
Réponse (Londres -> Dakar) :  100 ms
Réception client :             2 ms
──────────────────────────────────────
TOTAL (cache hit) :          ~209 ms

TOTAL (cache miss) :         ~225 ms


ÉTAPE 4 : FIND COMPLEXE AVEC INDEX (DAKAR -> LONDRES)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CODE (Dakar) :
const produits = await collection.find({
  categorie: "Instruments",
  prix: { $gte: 10000, $lte: 50000 },
  stock: { $gt: 0 }
}).sort({ prix: 1 }).limit(10).toArray();

SUPPOSONS :
-> Collection : 100 000 produits
-> Index sur "categorie"
-> Index composé sur "categorie" + "prix"
-> Pas d'index sur "stock"

PROCESSUS :

1. ENVOI (Dakar -> Londres) : [TEMPS] +100 ms

2. QUERY PLANNING (Londres) :
   
   Plans candidats :
   
   PLAN A : COLLSCAN
   -> Parcourir les 100 000 documents
   -> Filtrer en mémoire
   -> Coût estimé : 100 000 lectures
   
   PLAN B : IXSCAN sur index "categorie_1"
   -> Scanner l'index categorie = "Instruments"
   -> Retourne ~5000 documents
   -> Filtrer prix et stock en mémoire
   -> Trier en mémoire (SORT stage)
   -> Coût estimé : 5000 lectures + tri
   
   PLAN C : IXSCAN sur index composé "categorie_1_prix_1" [OK] GAGNANT
   -> Scanner l'index pour categorie = "Instruments" AND prix: [10000, 50000]
   -> Index déjà trié par prix !
   -> Retourne ~500 documents
   -> Filtrer stock en mémoire
   -> Pas besoin de SORT (ordre garanti par index)
   -> Coût estimé : 500 lectures
   
   Choix : PLAN C
   [TEMPS] ~5 ms (planning)

3. EXECUTION (Londres) :
   
   a) IXSCAN sur "categorie_1_prix_1" :
      -> Recherche point d'entrée : ("Instruments", 10000)
      -> Scan séquentiel jusqu'à : ("Instruments", 50000)
      -> 500 clés d'index trouvées
      
      Cache hit probable (index chaud) :
      [TEMPS] ~10 ms
      
      Cache miss :
      [TEMPS] ~50 ms
   
   b) FETCH des 500 documents :
      -> Récupération via RecordId
      
      Cache hit partiel (20% des docs en cache) :
      [TEMPS] ~50 ms
      
      Cache miss complet :
      [TEMPS] ~200 ms
   
   c) Filtrage "stock > 0" (en mémoire) :
      -> 500 docs -> 450 docs (90% en stock)
      [TEMPS] ~5 ms
   
   d) PAS de SORT (ordre déjà respecté par index)
   
   e) LIMIT 10 :
      -> Prendre les 10 premiers
      [TEMPS] <1 ms
   
   Total execution (cache hit partiel) : ~65 ms
   Total execution (cache miss) : ~255 ms

4. RÉPONSE (Londres -> Dakar) :
   
   -> 10 documents × ~350 bytes = 3.5 KB
   -> Compression : ~2 KB
   [TEMPS] +100 ms

5. RÉCEPTION (Dakar) :
   
   -> Désérialisation des 10 documents
   [TEMPS] ~2 ms

RÉCAPITULATIF TEMPS FIND COMPLEXE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Envoi (Dakar -> Londres) :    100 ms
Planning :                      5 ms
Execution (cache hit) :        65 ms
Réponse (Londres -> Dakar) :   100 ms
Réception :                     2 ms
──────────────────────────────────────
TOTAL (cache hit) :           ~272 ms

TOTAL (cache miss) :          ~462 ms


IMPORTANCE DES INDEX :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SANS INDEX (COLLSCAN) :
-> 100 000 documents à lire
-> Cache miss : 100 000 × 2 ms = 200 secondes (!)
-> Même avec cache : plusieurs secondes

AVEC INDEX SIMPLE (categorie_1) :
-> 5000 documents à lire + tri
-> Temps : ~500 ms

AVEC INDEX COMPOSÉ (categorie_1_prix_1) :
-> 500 documents à lire, pas de tri
-> Temps : ~272 ms

GAIN : 735x plus rapide qu'un COLLSCAN !


CHEMIN PHYSIQUE DES DONNÉES (DAKAR <-> LONDRES) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[Ton PC Dakar]
    v
[WiFi/4G Router]
    v
[FAI Sénégal (Orange/Sonatel)]
    v
[Point d'atterrissage Câble sous-marin ACE]
    v
[CÂBLE SOUS-MARIN ATLANTIQUE - 4500 km]
    v (~22 ms propagation lumière)
    v
[Point d'atterrissage Europe (Portugal)]
    v
[Backbone Internet Européen]
    v
[IXP Londres (Internet Exchange Point)]
    v
[Réseau AWS]
    v
[Datacenter AWS eu-west-2 (Londres)]
    v
[Serveur MongoDB PRIMARY]
    v (réplication interne)
    v
[Serveurs MongoDB SECONDARY-1 et SECONDARY-2]
"""


# [OK] PARTIE 7 : INDEX MONGODB - GUIDE COMPLET

"""
┌────────────────────────────────────────────────────────────────────────┐
│                    INDEX : ACCÉLÉRER LES REQUÊTES                      │
└────────────────────────────────────────────────────────────────────────┘

POURQUOI LES INDEX ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SANS INDEX :
db.users.find({ email: "jean@example.com" })

-> MongoDB doit scanner TOUS les documents (COLLSCAN)
-> 1 million de documents ? 1 million de lectures !
-> Temps : plusieurs secondes [LENT]

AVEC INDEX :
db.users.createIndex({ email: 1 })
db.users.find({ email: "jean@example.com" })

-> MongoDB utilise l'index B-Tree
-> Recherche en O(log n) : ~20 comparaisons pour 1 million de docs
-> Temps : quelques millisecondes [RAPIDE]


TYPES D'INDEX MONGODB :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. INDEX SIMPLE (Single Field Index)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CRÉATION :
db.users.createIndex({ email: 1 })
                        //  ^
                        //  1 = ordre croissant (ascending)
                        // -1 = ordre décroissant (descending)

UTILISATION :
db.users.find({ email: "jean@example.com" })         // [OK] Utilise l'index
db.users.find({ email: { $in: ["a@x.com", "b@x.com"] } })  // [OK]
db.users.find({ email: { $regex: /^jean/ } })        // [OK] (prefix)
db.users.find({ email: { $regex: /example/ } })      // [X] (pas de prefix)

ORDRE (1 vs -1) :
-> Important pour les SORT
-> Pas important pour les recherches d'égalité

db.users.createIndex({ age: 1 })
db.users.find().sort({ age: 1 })    // [OK] Ordre direct
db.users.find().sort({ age: -1 })   // [OK] MongoDB peut scanner à l'envers


2. INDEX COMPOSÉ (Compound Index)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Index sur PLUSIEURS champs

CRÉATION :
db.products.createIndex({ category: 1, price: 1 })

STRUCTURE INTERNE (B-Tree) :
("Electronics", 100)   -> RecordId: 1:100
("Electronics", 150)   -> RecordId: 1:101
("Electronics", 200)   -> RecordId: 1:102
("Books", 10)          -> RecordId: 2:50
("Books", 15)          -> RecordId: 2:51
("Books", 20)          -> RecordId: 2:52

UTILISATION :

[OK] EFFICACE :
db.products.find({ category: "Electronics" })
-> Utilise le préfixe de l'index

db.products.find({ category: "Electronics", price: { $gte: 100 } })
-> Utilise l'index complet

db.products.find({ category: "Electronics" }).sort({ price: 1 })
-> Pas de SORT en mémoire (déjà trié)

[X] INEFFICACE :
db.products.find({ price: { $gte: 100 } })
-> N'utilise PAS l'index ! (price n'est pas le préfixe)

[OK] SOLUTION :
-> Créer un index séparé sur "price"
db.products.createIndex({ price: 1 })


RÈGLE ESR (Equality, Sort, Range) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Ordre optimal pour index composé :
1. Equality (=) : champs avec égalité exacte
2. Sort : champs de tri
3. Range : champs avec plage ($gt, $lt, etc.)

EXEMPLE :
db.orders.find({
  status: "completed",           // Equality
  userId: 12345,                 // Equality
  amount: { $gte: 100 }          // Range
}).sort({ date: -1 })            // Sort

INDEX OPTIMAL :
db.orders.createIndex({
  status: 1,      // E - Equality
  userId: 1,      // E - Equality
  date: -1,       // S - Sort
  amount: 1       // R - Range
})


3. INDEX MULTIKEY (pour Arrays)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Indexation automatique de CHAQUE élément d'un array

DOCUMENT :
{
  _id: 1,
  title: "MongoDB Guide",
  tags: ["database", "nosql", "mongodb"]
}

INDEX :
db.articles.createIndex({ tags: 1 })

STRUCTURE INTERNE (MongoDB créé automatiquement un index multikey) :
"database" -> RecordId: 1
"mongodb"  -> RecordId: 1
"nosql"    -> RecordId: 1

UTILISATION :
db.articles.find({ tags: "mongodb" })
-> Trouve TOUS les documents ayant "mongodb" dans leur array tags

db.articles.find({ tags: { $in: ["mongodb", "postgresql"] } })
-> Trouve documents avec au moins un de ces tags

LIMITE IMPORTANTE :
[X] Impossible d'avoir 2 champs array indexés dans le même index composé

db.articles.createIndex({ tags: 1, categories: 1 })
-> ERREUR si tags ET categories sont des arrays !

[OK] Solution : Index séparés
db.articles.createIndex({ tags: 1 })
db.articles.createIndex({ categories: 1 })


4. INDEX TEXT (Full-Text Search)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Recherche textuelle avancée (stemming, stop words)

CRÉATION :
db.articles.createIndex({ 
  title: "text", 
  content: "text" 
}, {
  default_language: "french",
  weights: {
    title: 10,      // Titre 10x plus important
    content: 1      // Contenu poids normal
  }
})

UTILISATION :
db.articles.find({ 
  $text: { 
    $search: "mongodb performance optimisation" 
  } 
})

FONCTIONNALITÉS :
-> Stemming : "optimiser" trouve "optimisation", "optimal"
-> Stop words : ignore "le", "la", "de", "et"
-> Recherche par phrase : "\"exact phrase\""
-> Exclusion : "mongodb -postgresql" (sans postgresql)

SCORING :
db.articles.find(
  { $text: { $search: "mongodb" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })

LANGUES SUPPORTÉES :
-> french, english, spanish, arabic, german, etc.

LIMITE :
-> Un seul index text par collection
-> Moins puissant qu'Elasticsearch
-> MongoDB Atlas Search (cloud) est bien meilleur


5. INDEX GÉOSPATIAL (2d, 2dsphere)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Pour requêtes géographiques (proximité, zones)

DOCUMENT :
{
  _id: 1,
  name: "Restaurant Le Dakarois",
  location: {
    type: "Point",
    coordinates: [-17.4441, 14.6928]  // [longitude, latitude]
  }
}

INDEX 2dsphere (sphère terrestre) :
db.restaurants.createIndex({ location: "2dsphere" })

REQUÊTES :

A) Proximité ($near) :
db.restaurants.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [-17.4500, 14.7000]  // Ma position
      },
      $maxDistance: 5000  // 5 km
    }
  }
})

-> Retourne restaurants dans un rayon de 5 km, triés par distance

B) Dans une zone ($geoWithin) :
// Définir un polygone (quartier de Dakar)
const plateau = {
  type: "Polygon",
  coordinates: [[
    [-17.4500, 14.6900],
    [-17.4400, 14.6900],
    [-17.4400, 14.7000],
    [-17.4500, 14.7000],
    [-17.4500, 14.6900]  // Fermer le polygone
  ]]
}

db.restaurants.find({
  location: {
    $geoWithin: {
      $geometry: plateau
    }
  }
})

C) Intersection ($geoIntersects) :
-> Trouve les zones qui intersectent une géométrie

TYPES DE GÉOMÉTRIE GEOJSON :
-> Point : un lieu
-> LineString : une route
-> Polygon : une zone
-> MultiPoint, MultiLineString, MultiPolygon


INDEX 2d (plan cartésien) :
-> Pour coordonnées plates (jeux vidéo, plans)
-> Moins utilisé que 2dsphere

db.maps.createIndex({ position: "2d" })


6. INDEX UNIQUE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Garantit l'unicité des valeurs

CRÉATION :
db.users.createIndex({ email: 1 }, { unique: true })

EFFET :
db.users.insertOne({ email: "jean@example.com" })  // [OK]
db.users.insertOne({ email: "jean@example.com" })  // [X] Erreur duplicate key

INDEX _id :
-> Créé automatiquement
-> Unique par défaut
-> Ne peut pas être supprimé

UNIQUE + SPARSE :
db.users.createIndex({ phone: 1 }, { unique: true, sparse: true })

-> sparse: true : ignore les documents sans le champ "phone"
-> Permet plusieurs documents sans "phone"
-> Mais garantit l'unicité si "phone" est présent


7. INDEX PARTIEL (Partial Index)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Index uniquement un sous-ensemble de documents

CRÉATION :
db.orders.createIndex(
  { customerId: 1, orderDate: 1 },
  { 
    partialFilterExpression: { 
      status: "active" 
    } 
  }
)

UTILISATION :
db.orders.find({ 
  customerId: 12345, 
  status: "active" 
})  // [OK] Utilise l'index

db.orders.find({ 
  customerId: 12345, 
  status: "completed" 
})  // [X] N'utilise PAS l'index (status != "active")

AVANTAGES :
[OK] Index plus petit (seulement documents "active")
[OK] Plus rapide à maintenir
[OK] Économie d'espace

CAS D'USAGE :
-> Documents "soft deleted" (deleted: false)
-> Documents actifs vs archivés
-> Seulement les documents récents


8. INDEX TTL (Time-To-Live)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Suppression automatique après expiration

CRÉATION :
db.sessions.createIndex(
  { createdAt: 1 }, 
  { expireAfterSeconds: 3600 }  // 1 heure
)

DOCUMENT :
{
  _id: ObjectId("..."),
  userId: 12345,
  token: "abc123",
  createdAt: ISODate("2024-12-08T10:30:00Z")
}

EFFET :
-> MongoDB supprime automatiquement le document 1 heure après createdAt
-> Vérification toutes les 60 secondes (background task)

CAS D'USAGE :
-> Sessions utilisateur
-> Cache temporaire
-> Logs (garder 30 jours)
-> OTP / codes de vérification


9. INDEX HASHED (pour Sharding)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Hash de la valeur pour distribution uniforme

CRÉATION :
db.users.createIndex({ userId: "hashed" })

UTILISATION :
-> Principalement pour shard key
-> Distribution uniforme des données sur les shards
-> Ne supporte PAS les range queries ($gt, $lt)

db.users.find({ userId: 12345 })       // [OK] Égalité OK
db.users.find({ userId: { $gt: 100 } }) // [X] Range query impossible


10. INDEX WILDCARD (MongoDB 4.2+)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Index sur tous les champs (ou sous-documents)

CRÉATION :
db.products.createIndex({ "$**": 1 })

-> Index TOUS les champs du document
-> Utile pour schémas très variables

LIMITÉ À UN SOUS-DOCUMENT :
db.products.createIndex({ "attributes.$**": 1 })

DOCUMENT :
{
  _id: 1,
  name: "T-shirt",
  attributes: {
    color: "red",
    size: "L",
    material: "cotton"
  }
}

-> Index color, size, material automatiquement

UTILISATION :
db.products.find({ "attributes.color": "red" })  // [OK] Utilise l'index
db.products.find({ "attributes.size": "L" })     // [OK] Utilise l'index


GESTION DES INDEX :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

LISTER LES INDEX :
db.collection.getIndexes()

[
  { 
    "v": 2, 
    "key": { "_id": 1 }, 
    "name": "_id_" 
  },
  { 
    "v": 2, 
    "key": { "email": 1 }, 
    "name": "email_1",
    "unique": true 
  }
]

CRÉER UN INDEX :
db.collection.createIndex(
  { field: 1 },
  { 
    name: "custom_name",     // Nom personnalisé
    background: true,        // Création en arrière-plan (deprecated depuis 4.2)
    unique: false,
    sparse: false,
    expireAfterSeconds: null
  }
)

DEPUIS MongoDB 4.2 :
-> Création d'index non bloquante par défaut
-> Option "background" ignorée

SUPPRIMER UN INDEX :
db.collection.dropIndex("email_1")
db.collection.dropIndex({ email: 1 })

SUPPRIMER TOUS LES INDEX (sauf _id) :
db.collection.dropIndexes()

CACHER UN INDEX (MongoDB 4.4+) :
db.collection.hideIndex("email_1")

-> Index existe toujours mais n'est pas utilisé
-> Permet de tester l'impact avant suppression

db.collection.unhideIndex("email_1")


VOIR L'UTILISATION DES INDEX :
db.collection.aggregate([
  { $indexStats: {} }
])

[
  {
    "name": "email_1",
    "key": { "email": 1 },
    "host": "server1:27017",
    "accesses": {
      "ops": 12547,           // Nombre d'utilisations
      "since": ISODate("...")
    }
  }
]


STRATÉGIE D'INDEXATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RÈGLES D'OR :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[OK] CRÉER DES INDEX POUR :
-> Champs dans WHERE (filtres)
-> Champs dans SORT
-> Champs dans JOIN ($lookup)
-> Champs fréquemment recherchés

[X] NE PAS CRÉER D'INDEX POUR :
-> Champs rarement utilisés
-> Champs avec peu de valeurs distinctes (sexe: M/F)
-> Petites collections (< 1000 documents)
-> Collections avec beaucoup d'écritures

COÛT D'UN INDEX :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Espace disque (10-20% de la taille de la collection)
-> RAM (index chargé en cache)
-> Ralentissement des écritures (UPDATE/INSERT/DELETE)

CHAQUE INDEX = +10-20% de temps d'écriture

NOMBRE OPTIMAL D'INDEX :
-> 3-7 index par collection (en moyenne)
-> Analyse avec explain() pour valider


ANALYSE DE PERFORMANCE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

EXPLAIN (équivalent de EXPLAIN dans SQL) :

db.collection.find({ email: "jean@example.com" }).explain("executionStats")

{
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 1,                    // Documents retournés
    "executionTimeMillis": 2,          // Temps total
    "totalKeysExamined": 1,            // Clés d'index examinées
    "totalDocsExamined": 1,            // Documents examinés
    "executionStages": {
      "stage": "FETCH",
      "nReturned": 1,
      "inputStage": {
        "stage": "IXSCAN",             // [OK] Index utilisé !
        "indexName": "email_1",
        "keysExamined": 1,
        "direction": "forward"
      }
    }
  }
}

STAGES IMPORTANTS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COLLSCAN  : [X] Collection scan (mauvais !)
IXSCAN    : [OK] Index scan (bon)
FETCH     : Récupération document complet
SORT      : [X] Tri en mémoire (créer index pour éviter)
SORT_MERGE: Tri avec index
PROJECTION_COVERED : [OK] Couvert uniquement par index (excellent !)

EXAMPLE OPTIMAL (Covered Query) :
db.users.createIndex({ email: 1, name: 1 })
db.users.find(
  { email: "jean@example.com" }, 
  { _id: 0, name: 1 }  // Projection : seulement "name"
)

-> "totalDocsExamined": 0  (aucun document lu !)
-> Données lues uniquement depuis l'index
-> Ultra rapide [RAPIDE]
"""


# [OK] PARTIE 8 : AGGREGATION PIPELINE

"""
┌────────────────────────────────────────────────────────────────────────┐
│            AGGREGATION PIPELINE : Le SQL de MongoDB                    │
└────────────────────────────────────────────────────────────────────────┘

QU'EST-CE QUE L'AGGREGATION ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Équivalent de GROUP BY, JOIN, HAVING en SQL
Traitement de données complexe par étapes (pipeline)

ANALOGIE : Chaîne de montage
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Documents -> [Filtrer] -> [Transformer] -> [Grouper] -> [Trier] -> Résultat

Chaque étape = un opérateur ($match, $group, $sort, etc.)


SYNTAXE DE BASE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.collection.aggregate([
  { $stage1: {...} },
  { $stage2: {...} },
  { $stage3: {...} }
])


PRINCIPAUX OPÉRATEURS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. $match - FILTRER (équivalent WHERE)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.orders.aggregate([
  { 
    $match: { 
      status: "completed",
      amount: { $gte: 100 }
    }
  }
])

ÉQUIVALENT SQL :
SELECT * FROM orders 
WHERE status = 'completed' AND amount >= 100

CONSEIL :
-> Placer $match le PLUS TÔT possible dans le pipeline
-> Réduit le nombre de documents à traiter
-> Peut utiliser les index


2. $project - PROJECTION (équivalent SELECT)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Sélectionner/transformer les champs

db.users.aggregate([
  {
    $project: {
      _id: 0,                    // Exclure _id
      name: 1,                   // Inclure name
      email: 1,                  // Inclure email
      fullName: {                // Nouveau champ calculé
        $concat: ["$firstName", " ", "$lastName"]
      },
      year: { $year: "$birthDate" }  // Extraire année
    }
  }
])

ÉQUIVALENT SQL :
SELECT 
  name, 
  email,
  CONCAT(firstName, ' ', lastName) AS fullName,
  YEAR(birthDate) AS year
FROM users


3. $group - GROUPER (équivalent GROUP BY)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Agréger les données

db.orders.aggregate([
  {
    $group: {
      _id: "$customerId",               // GROUP BY customerId
      totalOrders: { $sum: 1 },         // COUNT(*)
      totalAmount: { $sum: "$amount" }, // SUM(amount)
      avgAmount: { $avg: "$amount" },   // AVG(amount)
      maxAmount: { $max: "$amount" },   // MAX(amount)
      minAmount: { $min: "$amount" }    // MIN(amount)
    }
  }
])

ÉQUIVALENT SQL :
SELECT 
  customerId,
  COUNT(*) AS totalOrders,
  SUM(amount) AS totalAmount,
  AVG(amount) AS avgAmount,
  MAX(amount) AS maxAmount,
  MIN(amount) AS minAmount
FROM orders
GROUP BY customerId

ACCUMULATEURS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
$sum      : Somme
$avg      : Moyenne
$min      : Minimum
$max      : Maximum
$first    : Premier élément
$last     : Dernier élément
$push     : Créer un array avec tous les éléments
$addToSet : Créer un array avec éléments uniques


4. $sort - TRIER (équivalent ORDER BY)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.products.aggregate([
  { $sort: { price: 1, name: -1 } }
  //         ^         ^
  //         croissant décroissant
])

ÉQUIVALENT SQL :
SELECT * FROM products
ORDER BY price ASC, name DESC


5. $limit et $skip - PAGINATION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.products.aggregate([
  { $sort: { _id: 1 } },
  { $skip: 20 },      // Sauter 20 premiers
  { $limit: 10 }      // Prendre 10 suivants
])

ÉQUIVALENT SQL :
SELECT * FROM products
ORDER BY _id
LIMIT 10 OFFSET 20


6. $lookup - JOIN (équivalent LEFT JOIN)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Joindre deux collections

db.orders.aggregate([
  {
    $lookup: {
      from: "customers",              // Collection à joindre
      localField: "customerId",       // Champ dans orders
      foreignField: "_id",            // Champ dans customers
      as: "customerInfo"              // Nom du champ array résultat
    }
  }
])

RÉSULTAT :
{
  _id: 1,
  customerId: ObjectId("..."),
  amount: 150,
  customerInfo: [                     // Array !
    {
      _id: ObjectId("..."),
      name: "Jean Dupont",
      email: "jean@example.com"
    }
  ]
}

ÉQUIVALENT SQL :
SELECT 
  o.*,
  c.name,
  c.email
FROM orders o
LEFT JOIN customers c ON o.customerId = c._id

DÉCOMPRESSER LE RÉSULTAT ($unwind) :
db.orders.aggregate([
  {
    $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customerInfo"
    }
  },
  { $unwind: "$customerInfo" }  // Transformer array en objet
])

RÉSULTAT APRÈS $unwind :
{
  _id: 1,
  customerId: ObjectId("..."),
  amount: 150,
  customerInfo: {                // Objet (plus array)
    _id: ObjectId("..."),
    name: "Jean Dupont",
    email: "jean@example.com"
  }
}


7. $unwind - DÉCOMPOSER UN ARRAY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Créer un document par élément d'un array

DOCUMENT :
{
  _id: 1,
  title: "MongoDB Guide",
  tags: ["database", "nosql", "mongodb"]
}

db.articles.aggregate([
  { $unwind: "$tags" }
])

RÉSULTAT (3 documents) :
{ _id: 1, title: "MongoDB Guide", tags: "database" }
{ _id: 1, title: "MongoDB Guide", tags: "nosql" }
{ _id: 1, title: "MongoDB Guide", tags: "mongodb" }


8. $addFields - AJOUTER DES CHAMPS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Ajouter des champs calculés (garde tous les champs existants)

db.products.aggregate([
  {
    $addFields: {
      discountPrice: { 
        $multiply: ["$price", 0.9]  // -10%
      },
      isExpensive: { 
        $gte: ["$price", 1000] 
      }
    }
  }
])


9. $count - COMPTER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.users.aggregate([
  { $match: { status: "active" } },
  { $count: "totalActive" }
])

RÉSULTAT :
{ totalActive: 4523 }


10. $facet - AGRÉGATIONS MULTIPLES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Exécuter plusieurs pipelines en parallèle

db.products.aggregate([
  {
    $facet: {
      "categoriesStats": [
        { $group: { _id: "$category", count: { $sum: 1 } } }
      ],
      "priceStats": [
        { 
          $group: { 
            _id: null, 
            avg: { $avg: "$price" },
            min: { $min: "$price" },
            max: { $max: "$price" }
          } 
        }
      ],
      "recentProducts": [
        { $sort: { createdAt: -1 } },
        { $limit: 5 }
      ]
    }
  }
])


EXEMPLE COMPLET : RAPPORT DE VENTES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

OBJECTIF :
-> Ventes par catégorie pour l'année 2024
-> Avec détails client
-> Triées par total décroissant

db.orders.aggregate([
  // 1. Filtrer 2024
  {
    $match: {
      orderDate: {
        $gte: ISODate("2024-01-01"),
        $lt: ISODate("2025-01-01")
      },
      status: "completed"
    }
  },
  
  // 2. Joindre avec produits
  {
    $lookup: {
      from: "products",
      localField: "productId",
      foreignField: "_id",
      as: "product"
    }
  },
  { $unwind: "$product" },
  
  // 3. Joindre avec clients
  {
    $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customer"
    }
  },
  { $unwind: "$customer" },
  
    // 4. Grouper par catégorie (suite)
  {
    $group: {
      _id: "$product.category",
      totalSales: { $sum: "$amount" },
      totalOrders: { $sum: 1 },
      avgOrderAmount: { $avg: "$amount" },
      customers: { $addToSet: "$customer.name" },  // Liste unique
      products: { $push: "$product.name" }         // Toutes les ventes
    }
  },
  
  // 5. Ajouter des champs calculés
  {
    $addFields: {
      customerCount: { $size: "$customers" }
    }
  },
  
  // 6. Trier par total décroissant
  {
    $sort: { totalSales: -1 }
  },
  
  // 7. Formater la sortie
  {
    $project: {
      _id: 0,
      category: "$_id",
      totalSales: 1,
      totalOrders: 1,
      avgOrderAmount: { $round: ["$avgOrderAmount", 2] },
      customerCount: 1,
      topCustomers: { $slice: ["$customers", 5] }  // Top 5
    }
  }
])

RÉSULTAT :
[
  {
    category: "Electronics",
    totalSales: 1250000,
    totalOrders: 523,
    avgOrderAmount: 2390.25,
    customerCount: 342,
    topCustomers: ["Jean Dupont", "Marie Martin", ...]
  },
  {
    category: "Books",
    totalSales: 450000,
    ...
  }
]


ÉQUIVALENT SQL (approximatif) :
SELECT 
  p.category,
  SUM(o.amount) AS totalSales,
  COUNT(*) AS totalOrders,
  ROUND(AVG(o.amount), 2) AS avgOrderAmount,
  COUNT(DISTINCT o.customerId) AS customerCount
FROM orders o
JOIN products p ON o.productId = p._id
JOIN customers c ON o.customerId = c._id
WHERE o.orderDate >= '2024-01-01' 
  AND o.orderDate < '2025-01-01'
  AND o.status = 'completed'
GROUP BY p.category
ORDER BY totalSales DESC


OPÉRATEURS D'EXPRESSION UTILES :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ARITHMÉTIQUES :
$add, $subtract, $multiply, $divide, $mod, $abs, $ceil, $floor, $round

COMPARAISON :
$eq, $ne, $gt, $gte, $lt, $lte, $cmp

LOGIQUE :
$and, $or, $not

STRINGS :
$concat, $substr, $toLower, $toUpper, $split, $trim

ARRAYS :
$size, $slice, $arrayElemAt, $filter, $map, $reduce

DATES :
$year, $month, $dayOfMonth, $hour, $minute, $dateToString, $dateDiff

CONDITIONNELS :
$cond, $ifNull, $switch

EXEMPLE CONDITIONNEL :
{
  $addFields: {
    priceCategory: {
      $switch: {
        branches: [
          { case: { $lt: ["$price", 100] }, then: "Budget" },
          { case: { $lt: ["$price", 500] }, then: "Standard" },
          { case: { $lt: ["$price", 1000] }, then: "Premium" }
        ],
        default: "Luxury"
      }
    }
  }
}


PERFORMANCE DES PIPELINES :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RÈGLES D'OPTIMISATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. $match AU DÉBUT :
[OK] BON :
[
  { $match: { status: "active" } },  // Filtre d'abord
  { $lookup: ... },
  { $project: ... }
]

[X] MAUVAIS :
[
  { $lookup: ... },                  // Join TOUS les documents
  { $match: { status: "active" } }   // Filtre après (trop tard)
]

2. $project APRÈS $match :
-> Réduire la taille des documents tôt
-> Moins de données à transférer entre étapes

3. INDEX :
-> $match et $sort peuvent utiliser des index
-> Seulement au DÉBUT du pipeline
-> Après $lookup ou $unwind : plus d'index

4. ALLOWDISKÜSE :
-> Par défaut : pipeline limité à 100 MB RAM
-> Pour gros volumes : { allowDiskUse: true }

db.orders.aggregate([...], { allowDiskUse: true })

-> Utilise des fichiers temporaires sur disque
-> Plus lent mais évite les erreurs


EXPLAIN POUR AGGREGATION :
db.orders.explain("executionStats").aggregate([...])
"""


# [OK] PARTIE 9 : RÉPLICATION (REPLICA SETS)

"""
┌────────────────────────────────────────────────────────────────────────┐
│              REPLICA SETS : HAUTE DISPONIBILITÉ                        │
└────────────────────────────────────────────────────────────────────────┘

QU'EST-CE QU'UN REPLICA SET ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Groupe de serveurs MongoDB qui maintiennent les MÊMES données
-> 1 PRIMARY (lecture + écriture)
-> N SECONDARIES (lecture seule, copies)
-> Élection automatique si PRIMARY tombe

POURQUOI ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[OK] Haute disponibilité (HA)
[OK] Redondance des données
[OK] Failover automatique
[OK] Lecture distribuée (secondaries)
[OK] Backup sans downtime


ARCHITECTURE TYPIQUE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌─────────────────────────────────────────────────────────────────────┐
│                         REPLICA SET "rs0"                           │
│                                                                     │
│   ┌──────────────────┐         ┌──────────────────┐               │
│   │   SERVER 1       │         │   SERVER 2       │               │
│   │   PRIMARY        │ [BLACK_LEFT-POINTING_POINTER]────── │   SECONDARY      │               │
│   │   Londres        │  Oplog  │   Londres        │               │
│   │   Lecture + Write│         │   Lecture        │               │
│   └──────────────────┘         └──────────────────┘               │
│            │                              │                        │
│            │ Oplog                        │                        │
│            [BLACK_DOWN-POINTING_TRIANGLE]                              │                        │
│   ┌──────────────────┐                   │                        │
│   │   SERVER 3       │ [BLACK_LEFT-POINTING_POINTER]─────────────────┘                        │
│   │   SECONDARY      │                                             │
│   │   Paris          │                                             │
│   │   Lecture        │                                             │
│   └──────────────────┘                                             │
│                                                                     │
│   CLIENT  ──[BLACK_RIGHT-POINTING_POINTER]  PRIMARY (écritures)                                │
│   CLIENT  ──[BLACK_RIGHT-POINTING_POINTER]  SECONDARIES (lectures si read preference)          │
└─────────────────────────────────────────────────────────────────────┘


OPLOG (Operations Log) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Collection spéciale : local.oplog.rs
-> Taille fixe (capped collection)
-> Enregistre TOUTES les opérations d'écriture
-> Réplication asynchrone

FLUX DE RÉPLICATION :
1. Client écrit sur PRIMARY
2. PRIMARY écrit dans son oplog
3. SECONDARIES lisent l'oplog du PRIMARY
4. SECONDARIES appliquent les opérations
5. SECONDARIES écrivent dans leur propre oplog

EXEMPLE D'ENTRÉE OPLOG :
{
  "ts": Timestamp(1733654400, 1),  // Timestamp + compteur
  "t": 5,                          // Term (élection)
  "h": NumberLong("123456789"),    // Hash
  "v": 2,                          // Version
  "op": "i",                       // Opération: i=insert, u=update, d=delete
  "ns": "mydb.users",              // Namespace
  "ui": UUID("..."),               // Collection UUID
  "o": {                           // Document inséré
    "_id": ObjectId("..."),
    "name": "Jean"
  }
}


CONFIGURATION D'UN REPLICA SET :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ÉTAPE 1 : Démarrer 3 mongod avec replSet
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Serveur 1 (port 27017)
mongod --replSet rs0 --port 27017 --dbpath /data/rs1 --bind_ip localhost

# Serveur 2 (port 27018)
mongod --replSet rs0 --port 27018 --dbpath /data/rs2 --bind_ip localhost

# Serveur 3 (port 27019)
mongod --replSet rs0 --port 27019 --dbpath /data/rs3 --bind_ip localhost


ÉTAPE 2 : Initialiser le Replica Set
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Se connecter au premier serveur
mongosh --port 27017

# Initialiser
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "localhost:27017" },
    { _id: 1, host: "localhost:27018" },
    { _id: 2, host: "localhost:27019" }
  ]
})

RÉSULTAT :
{ "ok": 1 }

# Vérifier le statut
rs.status()

{
  "set": "rs0",
  "date": ISODate("2024-12-08T10:30:00Z"),
  "myState": 1,  // 1 = PRIMARY, 2 = SECONDARY
  "members": [
    {
      "_id": 0,
      "name": "localhost:27017",
      "health": 1,
      "state": 1,          // PRIMARY
      "stateStr": "PRIMARY",
      "uptime": 123,
      "optime": { "ts": Timestamp(...), "t": 5 },
      "electionTime": Timestamp(...),
      "self": true
    },
    {
      "_id": 1,
      "name": "localhost:27018",
      "health": 1,
      "state": 2,          // SECONDARY
      "stateStr": "SECONDARY",
      "uptime": 120,
      "optime": { "ts": Timestamp(...), "t": 5 },
      "syncSourceHost": "localhost:27017"
    },
    {
      "_id": 2,
      "name": "localhost:27019",
      "health": 1,
      "state": 2,          // SECONDARY
      "stateStr": "SECONDARY",
      "uptime": 120,
      "optime": { "ts": Timestamp(...), "t": 5 }
    }
  ],
  "ok": 1
}


ÉLECTION DU PRIMARY :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ALGORITHME RAFT :
-> Chaque membre a une priorité (0-1000, défaut: 1)
-> Votes à la majorité (n/2 + 1)
-> Membre avec la priorité la plus haute gagne

DÉCLENCHEURS D'ÉLECTION :
-> PRIMARY tombe (crash, network partition)
-> rs.stepDown() (descendre manuellement)
-> Ajout d'un nouveau membre avec priorité plus haute

DURÉE TYPIQUE : 10-20 secondes

PENDANT L'ÉLECTION :
[X] Pas d'écriture possible
[OK] Lectures possibles (si read preference secondary)


PRIORITÉS DES MEMBRES :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

cfg = rs.conf()
cfg.members[0].priority = 2    // Priorité haute (devient PRIMARY)
cfg.members[1].priority = 1    // Priorité normale
cfg.members[2].priority = 0    // Priorité 0 = JAMAIS PRIMARY
rs.reconfig(cfg)

ARBITER (arbitre) :
-> Membre qui ne stocke PAS de données
-> Participe uniquement aux votes
-> Économise des ressources

rs.addArb("localhost:27020")

HIDDEN MEMBER (caché) :
-> Secondary invisible pour les clients
-> Utilisé pour analytics ou backup
-> Ne peut pas devenir PRIMARY

cfg.members[2].hidden = true
cfg.members[2].priority = 0


READ PREFERENCE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Où lire les données ?

1. primary (défaut)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Toutes les lectures sur PRIMARY
-> Cohérence immédiate garantie
-> Charge maximum sur PRIMARY

const client = new MongoClient(uri, {
  readPreference: 'primary'
})

2. primaryPreferred
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> PRIMARY si disponible
-> SECONDARY sinon

3. secondary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Toujours sur SECONDARY
-> Décharge le PRIMARY
-> Possible stale reads (données légèrement anciennes)

4. secondaryPreferred
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> SECONDARY si disponible
-> PRIMARY sinon

5. nearest
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Membre avec la latence la plus faible
-> Bon pour déploiements multi-régions

EXAMPLE DAKAR -> LONDRES :
-> PRIMARY: Londres
-> SECONDARY: Dakar

const client = new MongoClient(uri, {
  readPreference: 'nearest'  // Lira sur SECONDARY Dakar (plus proche)
})


WRITE CONCERN :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Combien de nœuds doivent confirmer l'écriture ?

1. w: 1 (défaut)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> PRIMARY confirme
-> Pas d'attente de réplication
-> Rapide mais risqué (perte possible si PRIMARY crash)

db.users.insertOne(doc, { writeConcern: { w: 1 } })

2. w: "majority"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Majorité des nœuds confirment (n/2 + 1)
-> Garanti que les données survivent à un crash PRIMARY
-> Plus lent mais sûr

db.users.insertOne(doc, { writeConcern: { w: "majority" } })

3. w: <number>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Attendre N nœuds

db.users.insertOne(doc, { writeConcern: { w: 3 } })

4. j: true (journal)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Attendre que le journal soit écrit sur disque
-> Durabilité maximale

db.users.insertOne(doc, { 
  writeConcern: { w: "majority", j: true } 
})

5. wtimeout
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Timeout si réplication trop lente

db.users.insertOne(doc, { 
  writeConcern: { w: "majority", wtimeout: 5000 }  // 5 secondes max
})


SCÉNARIO DE FAILOVER :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

T=0 : Configuration normale
┌──────────┐     ┌──────────┐     ┌──────────┐
│ PRIMARY  │────[BLACK_RIGHT-POINTING_TRIANGLE]│SECONDARY1│────[BLACK_RIGHT-POINTING_TRIANGLE]│SECONDARY2│
│ Londres  │     │ Londres  │     │  Paris   │
└──────────┘     └──────────┘     └──────────┘
     [BLACK_UP-POINTING_TRIANGLE]
     │
  CLIENT


T=10s : PRIMARY CRASH [IMPACT]
┌──────────┐     ┌──────────┐     ┌──────────┐
│   [IMPACT]     │  X  │SECONDARY1│────[BLACK_RIGHT-POINTING_TRIANGLE]│SECONDARY2│
│ Londres  │     │ Londres  │     │  Paris   │
└──────────┘     └──────────┘     └──────────┘
                       │               │
                       └───[BLACK_RIGHT-POINTING_POINTER] [BALLOT_BOX_WITH_BALLOT] Vote [BLACK_LEFT-POINTING_POINTER]──┘


T=15s : ÉLECTION EN COURS
-> SECONDARY1 et SECONDARY2 ne reçoivent plus de heartbeat du PRIMARY
-> Déclenchent une élection
-> SECONDARY1 a la priorité la plus haute
-> SECONDARY2 vote pour SECONDARY1


T=25s : NOUVEAU PRIMARY ÉLU [OK]
┌──────────┐     ┌──────────┐     ┌──────────┐
│   [IMPACT]     │     │ PRIMARY  │────[BLACK_RIGHT-POINTING_TRIANGLE]│SECONDARY │
│ Londres  │     │ Londres  │     │  Paris   │
└──────────┘     └──────────┘     └──────────┘
                       [BLACK_UP-POINTING_TRIANGLE]
                       │
                    CLIENT
                 (reconnecte automatiquement)


T=60s : ANCIEN PRIMARY REVIENT
┌──────────┐     ┌──────────┐     ┌──────────┐
│SECONDARY │[BLACK_LEFT-POINTING_POINTER]────│ PRIMARY  │────[BLACK_RIGHT-POINTING_TRIANGLE]│SECONDARY │
│ Londres  │     │ Londres  │     │  Paris   │
└──────────┘     └──────────┘     └──────────┘

-> Ancien PRIMARY redémarre
-> Se synchronise (rattrape l'oplog)
-> Rejoint comme SECONDARY
-> Pas de basculement automatique (stabilité)


IMPACT POUR LES CLIENTS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AVEC DRIVER MODERNE :
-> Reconnexion automatique au nouveau PRIMARY
-> Retry automatique des écritures (retryWrites: true)
-> Downtime : 10-20 secondes

SANS REPLICA SET (instance unique) :
-> Downtime jusqu'au redémarrage manuel
-> Perte possible de données
"""


# [OK] PARTIE 10 : SHARDING (SCALABILITÉ HORIZONTALE)

"""
┌────────────────────────────────────────────────────────────────────────┐
│              SHARDING : DISTRIBUER LES DONNÉES                         │
└────────────────────────────────────────────────────────────────────────┘

QU'EST-CE QUE LE SHARDING ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Partitionner horizontalement les données sur plusieurs serveurs
-> Chaque shard = sous-ensemble des données
-> Scalabilité quasi-infinie

POURQUOI ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Base trop grande pour un seul serveur (> 2-3 TB)
-> Débit trop élevé (> 50000 ops/sec)
-> Besoin de scalabilité horizontale

EXEMPLE :
Collection users avec 1 milliard de documents
-> 1 serveur : impossible (RAM, disque, CPU)
-> 10 shards : 100 millions de documents par shard [OK]


ARCHITECTURE SHARDED CLUSTER :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌─────────────────────────────────────────────────────────────────────┐
│                       SHARDED CLUSTER                               │
│                                                                     │
│                     ┌──────────────┐                               │
│                     │   MONGOS     │  <- Router (proxy)             │
│                     │   (Router)   │                               │
│                     └──────┬───────┘                               │
│                            │                                        │
│              ┌─────────────┼─────────────┐                         │
│              │             │             │                         │
│     ┌────────[BLACK_DOWN-POINTING_TRIANGLE]──────┐ ┌───[BLACK_DOWN-POINTING_TRIANGLE]──────────┐ ┌[BLACK_DOWN-POINTING_TRIANGLE]────────────┐           │
│     │ CONFIG SERVER │ │ CONFIG SERVER│ │CONFIG SERVER│           │
│     │   (Metadata)  │ │   (Metadata) │ │ (Metadata)  │           │
│     └───────────────┘ └──────────────┘ └─────────────┘           │
│                                                                     │
│     ┌──────────────────┬──────────────────┬──────────────────┐   │
│     │                  │                  │                  │   │
│ ┌───[BLACK_DOWN-POINTING_TRIANGLE]───────┐     ┌───[BLACK_DOWN-POINTING_TRIANGLE]───────┐     ┌───[BLACK_DOWN-POINTING_TRIANGLE]───────┐     ┌────[BLACK_DOWN-POINTING_TRIANGLE]──┐│
│ │  SHARD 0  │     │  SHARD 1  │     │  SHARD 2  │ ... │SHARD N││
│ │  (RS)     │     │  (RS)     │     │  (RS)     │     │ (RS)  ││
│ │  Docs:    │     │  Docs:    │     │  Docs:    │     │       ││
│ │  0-333M   │     │  333M-666M│     │  666M-1B  │     │       ││
│ └───────────┘     └───────────┘     └───────────┘     └───────┘│
└─────────────────────────────────────────────────────────────────────┘

COMPOSANTS :

1. MONGOS (Query Router)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Point d'entrée pour les clients
-> Route les requêtes vers les bons shards
-> Agrège les résultats
-> Pas de stockage de données
-> Stateless (peut en avoir plusieurs)

2. CONFIG SERVERS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Stockent les métadonnées du cluster
-> Mapping chunk -> shard
-> Replica set de 3 membres (obligatoire)
-> Critique : sans eux, le cluster ne fonctionne pas

3. SHARDS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> Stockent les données
-> Chaque shard = replica set (recommandé)
-> Minimum 2 shards


SHARD KEY :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Champ(s) qui détermine(nt) la distribution des documents

CHOIX CRITIQUE ! Ne peut pas être changé facilement

EXEMPLE :
sh.shardCollection("mydb.users", { userId: 1 })

-> Documents avec userId proche seront sur le même shard

TYPES DE SHARD KEY :

1. RANGED SHARDING
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Distribution par plages de valeurs

sh.shardCollection("mydb.users", { userId: 1 })

DISTRIBUTION :
SHARD 0 : userId [MinKey, 1000000)
SHARD 1 : userId [1000000, 2000000)
SHARD 2 : userId [2000000, MaxKey)

AVANTAGES :
[OK] Range queries efficaces
   db.users.find({ userId: { $gte: 1000000, $lt: 1500000 } })
   -> Va uniquement sur SHARD 1

[OK] Bon pour données avec ordre naturel (dates, IDs séquentiels)

INCONVÉNIENTS :
[X] Risque de hotspot (tous les nouveaux docs sur un shard)
[X] Distribution inégale si données pas uniformes


2. HASHED SHARDING
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Hash de la clé pour distribution uniforme

sh.shardCollection("mydb.users", { userId: "hashed" })

DISTRIBUTION :
-> Hash(userId) détermine le shard
-> Distribution uniforme garantie

AVANTAGES :
[OK] Distribution uniforme
[OK] Pas de hotspot
[OK] Bon pour IDs aléatoires

INCONVÉNIENTS :
[X] Range queries inefficaces (broadcast à tous les shards)
[X] Pas de locality (données proches éparpillées)


3. COMPOUND SHARD KEY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Clé composée de plusieurs champs

sh.shardCollection("mydb.orders", { country: 1, customerId: 1 })

DISTRIBUTION :
-> D'abord par country
-> Puis par customerId dans chaque country

AVANTAGES :
[OK] Meilleure distribution
[OK] Query targeting amélioré

EXEMPLE :
db.orders.find({ country: "SN", customerId: 12345 })
-> Va directement sur le bon shard

db.orders.find({ country: "SN" })
-> Va sur tous les shards du Sénégal


BONNES PRATIQUES SHARD KEY :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CARDINALITÉ :
[OK] Haute cardinalité (beaucoup de valeurs distinctes)
[X] Basse cardinalité (country: seulement 195 pays)

FRÉQUENCE D'ÉCRITURE :
[OK] Distribution uniforme des écritures
[X] Monotone croissant (_id, timestamp) -> hotspot

QUERY PATTERNS :
[OK] Inclure la shard key dans les requêtes fréquentes
[X] Requêtes sans shard key -> broadcast à tous les shards


EXEMPLE DE MAUVAISE SHARD KEY :
[X] { timestamp: 1 }
-> Tous les nouveaux documents vont sur le dernier shard
-> Hotspot garanti !

EXEMPLE DE BONNE SHARD KEY :
[OK] { userId: "hashed" }
-> Distribution uniforme
-> Queries par userId efficaces


CHUNKS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Unité de distribution des données

TAILLE PAR DÉFAUT : 128 MB

STRUCTURE :
{
  "_id": ObjectId("..."),
  "ns": "mydb.users",
  "min": { "userId": 0 },
  "max": { "userId": 1000000 },
  "shard": "shard0"
}

SPLITTING :
-> Quand un chunk dépasse 128 MB
-> MongoDB le divise automatiquement en 2 chunks

BALANCING :
-> Si un shard a trop de chunks
-> Balancer migre des chunks vers d'autres shards
-> S'exécute en arrière-plan

VOIR LES CHUNKS :
use config
db.chunks.find({ ns: "mydb.users" }).pretty()


CONFIGURATION D'UN SHARDED CLUSTER :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ÉTAPE 1 : Démarrer les Config Servers
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Config Server 1
mongod --configsvr --replSet configRS --port 27019 --dbpath /data/config1

# Config Server 2
mongod --configsvr --replSet configRS --port 27020 --dbpath /data/config2

# Config Server 3
mongod --configsvr --replSet configRS --port 27021 --dbpath /data/config3

# Initialiser le replica set
mongosh --port 27019
rs.initiate({
  _id: "configRS",
  configsvr: true,
  members: [
    { _id: 0, host: "localhost:27019" },
    { _id: 1, host: "localhost:27020" },
    { _id: 2, host: "localhost:27021" }
  ]
})


ÉTAPE 2 : Démarrer les Shards (replica sets)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Shard 0
mongod --shardsvr --replSet shard0RS --port 27022 --dbpath /data/shard0
mongod --shardsvr --replSet shard0RS --port 27023 --dbpath /data/shard0b

# Shard 1
mongod --shardsvr --replSet shard1RS --port 27024 --dbpath /data/shard1
mongod --shardsvr --replSet shard1RS --port 27025 --dbpath /data/shard1b

# Initialiser chaque replica set
mongosh --port 27022
rs.initiate({
  _id: "shard0RS",
  members: [
    { _id: 0, host: "localhost:27022" },
    { _id: 1, host: "localhost:27023" }
  ]
})

# Pareil pour shard1RS...


ÉTAPE 3 : Démarrer mongos
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

mongos --configdb configRS/localhost:27019,localhost:27020,localhost:27021 --port 27017


ÉTAPE 4 : Ajouter les shards
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

mongosh --port 27017

sh.addShard("shard0RS/localhost:27022,localhost:27023")
sh.addShard("shard1RS/localhost:27024,localhost:27025")

sh.status()


ÉTAPE 5 : Activer le sharding sur une database
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

sh.enableSharding("mydb")


ÉTAPE 6 : Sharder une collection
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Créer l'index sur la shard key (obligatoire)
db.users.createIndex({ userId: "hashed" })

// Sharder la collection
sh.shardCollection("mydb.users", { userId: "hashed" })


REQUÊTES DANS UN CLUSTER SHARDED :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

TARGETED QUERY (ciblée) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.users.find({ userId: 12345 })

-> Shard key présente
-> mongos sait exactement quel shard contacter
-> 1 seul shard interrogé [OK]

BROADCAST QUERY (diffusée) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.users.find({ email: "jean@example.com" })

-> Shard key absente
-> mongos envoie la requête à TOUS les shards
-> Agrège les résultats
-> Lent [X]


ZONE SHARDING (géolocalisation) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Assigner des plages de données à des shards spécifiques

EXEMPLE : Données par région

// Créer des zones
sh.addShardToZone("shard0", "Europe")
sh.addShardToZone("shard1", "Africa")
sh.addShardToZone("shard2", "Americas")

// Assigner des plages
sh.updateZoneKeyRange(
  "mydb.users",
  { country: "FR" },
  { country: "GB" },
  "Europe"
)

sh.updateZoneKeyRange(
  "mydb.users",
  { country: "SN" },
  { country: "ZA" },
  "Africa"
)

AVANTAGES :
[OK] Latence réduite (données près des utilisateurs)
[OK] Conformité GDPR (données UE restent en UE)
"""


# [OK] PARTIE 11 : TRANSACTIONS ACID

"""
┌────────────────────────────────────────────────────────────────────────┐
│              TRANSACTIONS ACID (depuis MongoDB 4.0)                    │
└────────────────────────────────────────────────────────────────────────┘

HISTORIQUE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Avant MongoDB 4.0 :
-> Atomicité seulement au niveau document
-> Pas de transactions multi-documents

MongoDB 4.0 (2018) :
-> Transactions multi-documents (replica sets)

MongoDB 4.2 (2019) :
-> Transactions distribuées (sharded clusters)


QU'EST-CE QU'ACID ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

A - Atomicity (Atomicité)
-> Tout ou rien : si une opération échoue, tout est annulé

C - Consistency (Cohérence)
-> Les données respectent toujours les règles

I - Isolation (Isolation)
-> Les transactions ne s'interfèrent pas

D - Durability (Durabilité)
-> Une fois validée, une transaction est permanente

Voici une **analogie simple et très claire** avec une situation de la vie quotidienne pour expliquer **ACID** v

---

## [ITEM] Analogie : Commander une pizza en ligne

Imagine que tu commandes une pizza via une application.

---

### 🅰 Atomicity (Atomicité) -> **Tout ou rien**

Tu commandes :

* 1 pizza
* 1 boisson
* Paiement en ligne

-> Si le paiement échoue,
-> la commande entière est annulée
-> tu ne reçois **ni pizza, ni boisson**

[IDEE] Comme en base de données :

> soit toute la requête réussit, soit rien n’est enregistré.

---

### 🅲 Consistency (Cohérence) -> **Les règles sont respectées**

L’application vérifie :

* Tu as bien assez d’argent
* L’adresse est valide
* La pizza existe au menu

-> Impossible de commander une pizza qui n’existe pas.

[IDEE] Comme en base de données :

> après la transaction, les données respectent toujours les règles.

---

### 🅸 Isolation (Isolation) -> **Les commandes ne se mélangent pas**

Toi et ton ami commandez en même temps :

* Ta commande reste ta commande
* Sa commande reste sa commande

-> Le système ne mélange pas vos pizzas.

[IDEE] Comme en base de données :

> plusieurs requêtes en même temps ne se perturbent pas.

---

### 🅳 Durability (Durabilité) -> **Une fois validé, c’est définitif**

Tu reçois le message :

> "Commande confirmée"

Même si :

* L’application plante
* Le téléphone s’éteint
* Le serveur redémarre

-> Ta commande est toujours enregistrée.

[IDEE] Comme en base de données :

> une transaction validée ne disparaît jamais.

---

## [OK] Résumé ultra simple

| Principe   | En base de données                  | Dans l’analogie                  |
| ---------- | ----------------------------------- | -------------------------------- |
| Atomicité  | Tout passe ou tout échoue           | Soit tu reçois tout, soit rien   |
| Cohérence  | Les règles sont respectées          | Pas de pizza inexistante         |
| Isolation  | Les requêtes ne se mélangent pas    | Ta commande ≠ celle des autres   |
| Durabilité | Données sauvegardées définitivement | Commande gardée même après panne |



EXEMPLE CLASSIQUE : TRANSFERT BANCAIRE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SANS TRANSACTION (dangereux) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Retirer 100€ du compte A
db.accounts.updateOne(
  { accountId: "A" },
  { $inc: { balance: -100 } }
)

// [IMPACT] CRASH ICI -> Les 100€ sont perdus !

// Ajouter 100€ au compte B
db.accounts.updateOne(
  { accountId: "B" },
  { $inc: { balance: 100 } }
)


AVEC TRANSACTION (sécurisé) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

const session = client.startSession();

try {
  session.startTransaction();
  
  // Retirer 100€ du compte A
  await db.collection('accounts').updateOne(
    { accountId: "A" },
    { $inc: { balance: -100 } },
    { session }
  );
  
  // Ajouter 100€ au compte B
  await db.collection('accounts').updateOne(
    { accountId: "B" },
    { $inc: { balance: 100 } },
    { session }
  );
  
  // Valider la transaction
  await session.commitTransaction();
  console.log("Transfert réussi [OK]");
  
} catch (error) {
  // Annuler TOUT
  await session.abortTransaction();
  console.log("Transfert annulé [X]");
} finally {
  session.endSession();
}


SYNTAXE CALLBACK (plus sûre) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

await session.withTransaction(async () => {
  
  await db.collection('accounts').updateOne(
    { accountId: "A" },
    { $inc: { balance: -100 } },
    { session }
  );
  
  await db.collection('accounts').updateOne(
    { accountId: "B" },
    { $inc: { balance: 100 } },
    { session }
  );
  
  // Commit automatique si pas d'erreur
  // Abort automatique en cas d'erreur
});


NIVEAUX D'ISOLATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MongoDB utilise : SNAPSHOT ISOLATION
-> Chaque transaction voit un snapshot cohérent
-> Pas de dirty reads
-> Pas de non-repeatable reads
-> Possible phantom reads (mais rare)

session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" }
})


LIMITATIONS DES TRANSACTIONS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[X] Ne pas utiliser pour des opérations longues
   -> Timeout : 60 secondes par défaut
   
[X] Coût en performance
   -> Plus lent que les opérations simples
   
[X] Limite de 16 MB par transaction
   
[X] Pas de DDL (CREATE/DROP collection) dans une transaction

QUAND UTILISER ?
-> Uniquement si VRAIMENT nécessaire
-> MongoDB préfère l'atomicité au niveau document
-> Privilégier l'embedded documents quand possible


EXEMPLE : E-COMMERCE ORDER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SANS TRANSACTION (embed tout) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

db.orders.insertOne({
  orderId: 12345,
  customerId: 67890,
  items: [
    { productId: 1, quantity: 2, price: 50 },
    { productId: 2, quantity: 1, price: 30 }
  ],
  total: 130,
  status: "pending"
})

-> Atomique automatiquement (un seul document) [OK]


AVEC TRANSACTION (collections séparées) :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

await session.withTransaction(async () => {
  
  // Créer la commande
  await db.orders.insertOne({
    orderId: 12345,
    customerId: 67890,
    total: 130
  }, { session });
  
  // Décrémenter le stock
  await db.products.updateOne(
    { _id: 1 },
    { $inc: { stock: -2 } },
    { session }
  );
  
  await db.products.updateOne(
    { _id: 2 },
    { $inc: { stock: -1 } },
    { session }
  );
  
  // Débiter le compte
  await db.accounts.updateOne(
    { customerId: 67890 },
    { $inc: { balance: -130 } },
    { session }
  );
});
"""


# [OK] PARTIE 12 : BONNES PRATIQUES ET CAS D'USAGE

"""
┌────────────────────────────────────────────────────────────────────────┐
│              BONNES PRATIQUES MONGODB                                  │
└────────────────────────────────────────────────────────────────────────┘

1. MODÉLISATION DES DONNÉES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RÈGLE D'OR : Modéliser selon les PATTERNS D'ACCÈS
-> Pas selon la normalisation SQL

EMBED vs REFERENCE ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

EMBED (imbrication) SI :
[OK] Relation 1-à-1 ou 1-à-peu
[OK] Données toujours accédées ensemble
[OK] Pas de mise à jour fréquente des données imbriquées
[OK] Taille raisonnable (< 16 MB au total)

EXEMPLE :
{
  _id: 1,
  name: "Jean Dupont",
  address: {                    // [OK] Embed
    street: "123 Rue",
    city: "Dakar",
    country: "SN"
  }
}


REFERENCE (séparation) SI :
[OK] Relation 1-à-beaucoup ou N-à-N
[OK] Données mises à jour indépendamment
[OK] Besoin d'accéder aux données séparément
[OK] Risque de dépasser 16 MB

EXEMPLE :
// Collection users
{
  _id: 1,
  name: "Jean Dupont"
}

// Collection orders (référence)
{
  _id: 100,
  userId: 1,              // [OK] Reference
  items: [...]
}


PATTERN : EXTENDED REFERENCE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Dupliquer les champs fréquemment accédés

{
  _id: 100,
  userId: 1,
  userName: "Jean Dupont",    // [OK] Dupliqué pour éviter JOIN
  userEmail: "jean@example.com",
  items: [...]
}

AVANTAGES :
[OK] Pas de $lookup nécessaire
[OK] Ultra rapide

INCONVÉNIENTS :
[X] Duplication (espace disque)
[X] Mise à jour complexe (si nom change)


PATTERN : SUBSET
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Garder un sous-ensemble fréquemment accédé

EXEMPLE : Articles de blog

{
  _id: 1,
  title: "MongoDB Guide",
  content: "...",
  recentComments: [           // [OK] 10 derniers commentaires
    { author: "Jean", text: "...", date: ... },
    { author: "Marie", text: "...", date: ... }
  ],
  commentCount: 1523          // Total dans collection séparée
}

// Collection comments (tous les commentaires)
{ articleId: 1, author: "Jean", text: "...", date: ... }
{ articleId: 1, author: "Marie", text: "...", date: ... }
...


PATTERN : COMPUTED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Précalculer les valeurs agrégées

{
  _id: 1,
  productId: 100,
  avgRating: 4.5,             // [OK] Précalculé
  ratingCount: 1250,
  ratings: [...]
}

-> Évite des aggregations coûteuses à chaque requête


2. PERFORMANCE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

INDEXATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[OK] Index sur champs de filtrage fréquents
[OK] Index composés dans l'ordre ESR (Equality, Sort, Range)
[OK] Covered queries (projection incluse dans l'index)
[OK] Surveiller avec explain() et $indexStats

[X] Trop d'index (ralentit les écritures)
[X] Index sur champs à faible cardinalité
[X] Index non utilisés


PROJECTIONS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Toujours spécifier les champs nécessaires

[OK] BON :
db.users.find({ status: "active" }, { name: 1, email: 1 })

[X] MAUVAIS :
db.users.find({ status: "active" })  // Retourne TOUT


PAGINATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[X] MAUVAIS (skip coûteux) :
db.products.find().skip(10000).limit(10)
-> MongoDB lit et ignore 10000 documents !

[OK] BON (cursor-based) :
db.products.find({ _id: { $gt: lastSeenId } }).limit(10)


BULK OPERATIONS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Pour insérer/mettre à jour beaucoup de documents

[OK] BON :
const bulkOps = [
  { insertOne: { document: { name: "Jean" } } },
  { updateOne: { filter: { _id: 1 }, update: { $set: { age: 30 } } } },
  { deleteOne: { filter: { _id: 2 } } }
];

db.users.bulkWrite(bulkOps, { ordered: false });

-> Plus rapide (moins de round-trips réseau)


3. SÉCURITÉ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AUTHENTIFICATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[OK] TOUJOURS activer l'authentification en production

# mongod.conf
security:
  authorization: enabled

# Créer un admin
use admin
db.createUser({
  user: "admin",
  pwd: "strongPassword123!",
  roles: [ "root" ]
})

# Créer un utilisateur applicatif
use mydb
db.createUser({
  user: "appuser",
  pwd: "appPassword456!",
  roles: [
    { role: "readWrite", db: "mydb" }
  ]
})


AUTORISATION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Principe du moindre privilège

RÔLES BUILT-IN :
-> read : lecture seule
-> readWrite : lecture + écriture
-> dbAdmin : administration DB
-> userAdmin : gestion utilisateurs
-> clusterAdmin : administration cluster
-> root : tous les droits


CHIFFREMENT :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[OK] TLS/SSL pour connexions client <-> serveur
[OK] Encryption at rest (Enterprise)
[OK] Field-level encryption (données sensibles)


AUDIT :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Tracer toutes les opérations (Enterprise)

# mongod.conf
auditLog:
  destination: file
  format: JSON
  path: /var/log/mongodb/audit.json
  filter: '{ atype: { $in: ["authenticate", "createUser", "dropDatabase"] } }'


RÉSEAU :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[X] NE JAMAIS exposer MongoDB directement sur Internet
[OK] Firewall (autoriser seulement IPs de confiance)
[OK] VPN pour accès distant
[OK] bind_ip: localhost en développement

# mongod.conf
net:
  bindIp: 127.0.0.1,10.0.0.5  # Localhost + IP privée
  port: 27017


4. MONITORING ET MAINTENANCE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

COMMANDES DE MONITORING :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Statut du serveur
db.serverStatus()

{
  "host": "server1:27017",
  "version": "7.0.5",
  "uptime": 123456,
  "connections": {
    "current": 52,
    "available": 838808
  },
  "opcounters": {
    "insert": 1000000,
    "query": 5000000,
    "update": 500000,
    "delete": 100000
  },
  "network": {
    "bytesIn": NumberLong("123456789"),
    "bytesOut": NumberLong("987654321")
  },
  "mem": {
    "resident": 2048,      // MB en RAM
    "virtual": 4096
  },
  "wiredTiger": {
    "cache": {
      "bytes currently in the cache": 1073741824,
      "maximum bytes configured": 2147483648
    }
  }
}


# Stats d'une base
db.stats()

{
  "db": "mydb",
  "collections": 10,
  "views": 0,
  "objects": 1000000,        // Nombre de documents
  "avgObjSize": 1024,        // Taille moyenne
  "dataSize": 1024000000,    // 1 GB
  "storageSize": 512000000,  // 512 MB (avec compression)
  "indexes": 15,
  "indexSize": 50000000,     // 50 MB
  "totalSize": 562000000     // Total
}


# Stats d'une collection
db.users.stats()


# Opérations en cours
db.currentOp()

-> Voir les requêtes lentes
-> Identifier les blocages

# Tuer une opération
db.killOp(12345)


# Profiler (analyser les requêtes lentes)
db.setProfilingLevel(1, { slowms: 100 })  // Log si > 100ms

db.setProfilingLevel(2)  // Log TOUTES les requêtes (debug)

# Voir les requêtes loggées
db.system.profile.find().sort({ ts: -1 }).limit(10)


LOGS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Voir les logs en temps réel
tail -f /var/log/mongodb/mongod.log

# Changer le niveau de verbosité
db.setLogLevel(1)  // 0-5 (0=info, 5=debug complet)


BACKUP :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MÉTHODE 1 : mongodump (logical backup)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Backup complet
mongodump --uri="mongodb://localhost:27017" --out=/backup/$(date +%Y%m%d)

# Backup d'une base
mongodump --db=mydb --out=/backup/mydb

# Backup d'une collection
mongodump --db=mydb --collection=users --out=/backup/users

# Restauration
mongorestore --uri="mongodb://localhost:27017" /backup/20241208


MÉTHODE 2 : Snapshot filesystem (physical backup)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

-> Snapshot LVM ou EBS (AWS)
-> Plus rapide pour gros volumes
-> Nécessite d'arrêter les écritures ou replica set


MÉTHODE 3 : MongoDB Atlas Backup (cloud)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

-> Backup continu automatique
-> Point-in-time recovery
-> Rétention configurable


STRATÉGIE DE BACKUP :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[OK] Backup quotidien (mongodump)
[OK] Snapshot hebdomadaire (filesystem)
[OK] Tester la restauration régulièrement !
[OK] Stockage hors site (S3, GCS)
[OK] Chiffrement des backups


MAINTENANCE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

COMPACT (défragmenter) :
db.runCommand({ compact: "users" })

-> Récupère l'espace disque
-> Bloque les opérations sur la collection
-> Utiliser pendant maintenance window


REINDEX (reconstruire les index) :
db.users.reIndex()

-> Optimise les index fragmentés
-> Coûteux en ressources


ANALYSER LA TAILLE :
db.users.storageSize()     // Taille sur disque
db.users.totalIndexSize()  // Taille des index
db.users.totalSize()       // Total


5. OUTILS MONGODB
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MONGODB COMPASS (GUI)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Interface graphique officielle

[OK] Exploration des données
[OK] Construction visuelle de requêtes
[OK] Création d'index
[OK] Validation de schéma
[OK] Performance insights
[OK] Aggregation Pipeline builder

Téléchargement : mongodb.com/products/compass


MONGODB ATLAS (DBaaS)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MongoDB hébergé dans le cloud

[OK] Déploiement en 5 minutes
[OK] Replica sets automatiques
[OK] Backup automatique
[OK] Monitoring intégré
[OK] Atlas Search (Elasticsearch-like)
[OK] Data Lake (analytics)
[OK] Serverless (pay-per-use)
[OK] Multi-cloud (AWS, GCP, Azure)

FREE TIER : 512 MB gratuit (parfait pour apprendre)


MONGODB SHELL (mongosh)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Shell JavaScript moderne

mongosh --host localhost --port 27017

FONCTIONNALITÉS :
-> Autocomplétion intelligente
-> Syntax highlighting
-> Snippets (scripts réutilisables)
-> Support async/await


DRIVERS OFFICIELS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Node.js, Python, Java, C#, Go, Ruby, PHP, C++, etc.

# Node.js
npm install mongodb

# Python
pip install pymongo

# Java
<dependency>
  <groupId>org.mongodb</groupId>
  <artifactId>mongodb-driver-sync</artifactId>
</dependency>
"""


# [OK] PARTIE 13 : CAS D'USAGE RÉELS

"""
┌────────────────────────────────────────────────────────────────────────┐
│                  CAS D'USAGE MONGODB DANS LE MONDE RÉEL                │
└────────────────────────────────────────────────────────────────────────┘

1. E-COMMERCE : CATALOGUE PRODUITS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PROBLÈME :
-> Produits avec attributs très variables
-> T-shirt : taille, couleur, matière
-> Ordinateur : processeur, RAM, disque, GPU
-> Livre : auteur, éditeur, ISBN, pages

SQL : ALTER TABLE cauchemar !
MongoDB : Schéma flexible [OK]

MODÈLE :

// Collection products
{
  _id: ObjectId("..."),
  name: "MacBook Pro M3",
  category: "Electronics",
  brand: "Apple",
  price: 2499.99,
  currency: "USD",
  stock: 15,
  
  // Attributs spécifiques (flexibles)
  specs: {
    processor: "Apple M3 Max",
    ram: "32GB",
    storage: "1TB SSD",
    display: "14.2-inch Retina"
  },
  
  // Images
  images: [
    "https://cdn.example.com/macbook-1.jpg",
    "https://cdn.example.com/macbook-2.jpg"
  ],
  
  // Reviews (embedded)
  reviews: [
    {
      userId: ObjectId("..."),
      userName: "Jean Dupont",
      rating: 5,
      comment: "Excellent laptop!",
      date: ISODate("2024-12-01T10:00:00Z")
    }
  ],
  
  // Stats précalculées
  avgRating: 4.8,
  reviewCount: 142,
  
  // SEO
  tags: ["laptop", "apple", "m3", "professional"],
  
  createdAt: ISODate("2024-01-15T08:00:00Z"),
  updatedAt: ISODate("2024-12-08T14:30:00Z")
}

INDEX :
db.products.createIndex({ category: 1, price: 1 })
db.products.createIndex({ brand: 1 })
db.products.createIndex({ tags: 1 })
db.products.createIndex({ name: "text", "specs.$**": "text" })


2. RÉSEAUX SOCIAUX : POSTS ET COMMENTAIRES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Collection posts
{
  _id: ObjectId("..."),
  userId: ObjectId("..."),
  
  // Extended reference (évite JOIN)
  user: {
    _id: ObjectId("..."),
    name: "Jean Dupont",
    avatar: "https://cdn.example.com/avatar.jpg"
  },
  
  content: "Découvrez MongoDB ! #database #nosql",
  
  media: [
    {
      type: "image",
      url: "https://cdn.example.com/post-image.jpg",
      thumbnail: "https://cdn.example.com/thumb.jpg"
    }
  ],
  
  // Stats en temps réel
  likes: {
    count: 342,
    users: [ObjectId("..."), ObjectId("...")]  // Subset des 50 premiers
  },
  
  // Commentaires récents (subset pattern)
  comments: [
    {
      userId: ObjectId("..."),
      userName: "Marie Martin",
      text: "Super post !",
      date: ISODate("2024-12-08T10:30:00Z")
    }
  ],
  commentCount: 87,  // Total
  
  hashtags: ["database", "nosql"],
  mentions: [ObjectId("..."), ObjectId("...")],
  
  visibility: "public",  // public, friends, private
  
  createdAt: ISODate("2024-12-08T09:00:00Z")
}

// Collection comments (tous les commentaires)
{
  _id: ObjectId("..."),
  postId: ObjectId("..."),
  userId: ObjectId("..."),
  userName: "Pierre Durand",
  text: "Très intéressant !",
  likes: 12,
  createdAt: ISODate("2024-12-08T11:00:00Z")
}

INDEX :
db.posts.createIndex({ userId: 1, createdAt: -1 })
db.posts.createIndex({ hashtags: 1 })
db.posts.createIndex({ "user._id": 1 })
db.comments.createIndex({ postId: 1, createdAt: -1 })


3. IoT : DONNÉES DE CAPTEURS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PROBLÈME :
-> Millions de mesures par jour
-> Besoin d'agrégation par période
-> Rétention limitée (ex: 90 jours)

// Collection sensor_data (time series)
{
  _id: ObjectId("..."),
  sensorId: "TEMP_001",
  location: "Dakar",
  
  // Bucketing : 1 document = 1 heure de mesures
  timestamp: ISODate("2024-12-08T10:00:00Z"),
  
  measurements: [
    { time: ISODate("2024-12-08T10:00:00Z"), temp: 28.5, humidity: 65 },
    { time: ISODate("2024-12-08T10:01:00Z"), temp: 28.6, humidity: 65 },
    { time: ISODate("2024-12-08T10:02:00Z"), temp: 28.7, humidity: 64 },
    // ... 60 mesures (1 par minute)
  ],
  
  // Stats précalculées
  summary: {
    avgTemp: 28.5,
    minTemp: 27.8,
    maxTemp: 29.2,
    count: 60
  }
}

INDEX :
db.sensor_data.createIndex({ sensorId: 1, timestamp: -1 })

TTL (auto-suppression après 90 jours) :
db.sensor_data.createIndex(
  { timestamp: 1 },
  { expireAfterSeconds: 7776000 }  // 90 jours
)


4. LOGS D'APPLICATION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Collection logs (capped collection pour rotation auto)
db.createCollection("logs", { 
  capped: true, 
  size: 10737418240,  // 10 GB max
  max: 100000000      // 100M documents max
})

{
  _id: ObjectId("..."),
  timestamp: ISODate("2024-12-08T10:30:45.123Z"),
  level: "ERROR",  // DEBUG, INFO, WARN, ERROR, FATAL
  service: "api-gateway",
  host: "server-1",
  
  message: "Database connection failed",
  
  context: {
    userId: 12345,
    requestId: "req_abc123",
    endpoint: "/api/users",
    method: "GET",
    statusCode: 500
  },
  
  stackTrace: "Error: Connection refused\n  at ...",
  
  metadata: {
    version: "1.2.3",
    environment: "production"
  }
}

INDEX :
db.logs.createIndex({ timestamp: -1 })
db.logs.createIndex({ level: 1, timestamp: -1 })
db.logs.createIndex({ service: 1, timestamp: -1 })


5. GÉOLOCALISATION : RESTAURANTS À PROXIMITÉ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Collection restaurants
{
  _id: ObjectId("..."),
  name: "Le Dakarois",
  cuisine: "Sénégalaise",
  
  location: {
    type: "Point",
    coordinates: [-17.4441, 14.6928]  // [lng, lat]
  },
  
  address: {
    street: "Avenue Malick Sy",
    city: "Dakar",
    country: "Sénégal"
  },
  
  rating: 4.5,
  priceRange: "$$",
  
  menu: [
    { name: "Thiéboudienne", price: 3000, currency: "XOF" },
    { name: "Yassa Poulet", price: 2500, currency: "XOF" }
  ],
  
  hours: {
    monday: { open: "11:00", close: "23:00" },
    tuesday: { open: "11:00", close: "23:00" }
    // ...
  }
}

INDEX GÉOSPATIAL :
db.restaurants.createIndex({ location: "2dsphere" })

REQUÊTE (trouver restaurants dans 5 km) :
db.restaurants.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [-17.4500, 14.7000]  // Ma position
      },
      $maxDistance: 5000  // 5 km
    }
  },
  cuisine: "Sénégalaise"
}).limit(10)


6. CMS / BLOG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Collection articles
{
  _id: ObjectId("..."),
  slug: "introduction-mongodb",
  title: "Introduction à MongoDB",
  
  author: {
    _id: ObjectId("..."),
    name: "Jean Dupont",
    email: "jean@example.com"
  },
  
  content: {
    raw: "Markdown content...",
    html: "<p>HTML content...</p>",
    excerpt: "MongoDB est une base..."
  },
  
  seo: {
    metaTitle: "Introduction MongoDB - Guide Complet",
    metaDescription: "Découvrez MongoDB...",
    keywords: ["mongodb", "nosql", "database"]
  },
  
  media: {
    featuredImage: "https://cdn.example.com/featured.jpg",
    gallery: []
  },
  
  status: "published",  // draft, published, archived
  visibility: "public",
  
  publishedAt: ISODate("2024-12-08T10:00:00Z"),
  updatedAt: ISODate("2024-12-08T15:00:00Z"),
  
  // Versioning
  version: 3,
  history: [
    { version: 1, updatedAt: ISODate("..."), content: "..." },
    { version: 2, updatedAt: ISODate("..."), content: "..." }
  ],
  
  stats: {
    views: 5420,
    likes: 123,
    shares: 45
  }
}

INDEX :
db.articles.createIndex({ slug: 1 }, { unique: true })
db.articles.createIndex({ status: 1, publishedAt: -1 })
db.articles.createIndex({ "author._id": 1 })
db.articles.createIndex({ title: "text", "content.raw": "text" })
"""


# [OK] RÉCAPITULATIF FINAL

"""
┌────────────────────────────────────────────────────────────────────────┐
│                  RÉCAPITULATIF COMPLET MONGODB                         │
└────────────────────────────────────────────────────────────────────────┘

CONCEPTS CLÉS :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. NoSQL Document Store
   -> Documents JSON/BSON flexibles
   -> Collections sans schéma rigide
   -> Dénormalisation encouragée

2. Architecture
   -> WiredTiger (storage engine)
   -> MVCC (concurrence)
   -> Journal (durabilité)
   -> Cache (performance)

3. Index
   -> B-Tree par défaut
   -> Types : simple, composé, multikey, text, géospatial
   -> ESR rule : Equality, Sort, Range

4. Aggregation Pipeline
   -> Traitement de données par étapes
   -> Équivalent SQL avancé
   -> $match, $group, $lookup, $project

5. Réplication (Replica Sets)
   -> Haute disponibilité
   -> Failover automatique
   -> Read preference & Write concern

6. Sharding
   -> Scalabilité horizontale
   -> Distribution par shard key
   -> Ranged ou Hashed

7. Transactions ACID
   -> Multi-documents (depuis 4.0)
   -> Distribuées (depuis 4.2)
   -> Snapshot isolation


QUAND UTILISER MONGODB ?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[OK] OUI SI :
-> Schéma évolutif/variable
-> Données hiérarchiques (JSON naturel)
-> Besoin de scalabilité horizontale
-> Prototypage rapide
-> Géolocalisation native
-> Catalogue produits hétérogènes
-> Logs, métriques, IoT
-> Content Management
-> Applications temps réel

[X] NON SI :
-> Transactions complexes multi-tables critiques
-> Jointures complexes fréquentes
-> Rapports BI complexes
-> Données très structurées et stables
-> Équipe maîtrise uniquement SQL


MONGODB vs SQL :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌──────────────────┬────────────────────┬────────────────────┐
│   Aspect         │     PostgreSQL     │      MongoDB       │
├──────────────────┼────────────────────┼────────────────────┤
│ Modèle           │ Tables/Lignes      │ Collections/Docs   │
│ Schéma           │ Rigide (DDL)       │ Flexible           │
│ Langage          │ SQL                │ JavaScript/JSON    │
│ Transactions     │ ACID natif         │ ACID (depuis 4.0)  │
│ Jointures        │ Natives            │ $lookup (limité)   │
│ Scalabilité      │ Verticale          │ Horizontale        │
│ Performance read │ Très bon           │ Excellent          │
│ Performance write│ Bon                │ Excellent          │
│ Géospatial       │ PostGIS extension  │ Natif              │
│ Full-text        │ Basique            │ Avancé (Atlas)     │
│ Courbe apprenti. │ SQL universel      │ Plus douce         │
│ Cas d'usage      │ ERP, finance       │ Web, mobile, IoT   │
└──────────────────┴────────────────────┴────────────────────┘


COMMANDES ESSENTIELLES :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// CRUD
db.collection.insertOne({...})
db.collection.insertMany([{...}, {...}])
db.collection.find({filter}, {projection})
db.collection.findOne({filter})
db.collection.updateOne({filter}, {$set: {...}})
db.collection.updateMany({filter}, {$set: {...}})
db.collection.replaceOne({filter}, {...})
db.collection.deleteOne({filter})
db.collection.deleteMany({filter})

// Index
db.collection.createIndex({field: 1})
db.collection.getIndexes()
db.collection.dropIndex("index_name")

// Aggregation
db.collection.aggregate([
  { $match: {...} },
  { $group: {...} },
  { $sort: {...} }
])

// Explain
db.collection.find({...}).explain("executionStats")

// Admin
db.stats()
db.serverStatus()
rs.status()
sh.status()


RESSOURCES POUR ALLER PLUS LOIN :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[DOCS] DOCUMENTATION OFFICIELLE :
-> https://docs.mongodb.com/

[COURS] MONGODB UNIVERSITY (GRATUIT) :
-> https://university.mongodb.com/
-> Cours certifiants gratuits
-> M001: MongoDB Basics
-> M121: The MongoDB Aggregation Framework
-> M201: MongoDB Performance

[OUTILS] OUTILS :
-> MongoDB Compass (GUI)
-> MongoDB Atlas (Cloud gratuit)
-> Studio 3T (GUI tiers)
-> NoSQLBooster (GUI tiers)

[GUIDE] LIVRES :
-> "MongoDB: The Definitive Guide" - O'Reilly
-> "Practical MongoDB" - Apress

[MOVIE_CAMERA] CHAÎNES YOUTUBE :
-> MongoDB Official Channel
-> Tutoriels communautaires


CHECKLIST DÉPLOIEMENT PRODUCTION :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SÉCURITÉ :
[ ] Authentification activée
[ ] TLS/SSL configuré
[ ] Firewall configuré (pas d'exposition Internet)
[ ] Utilisateurs avec moindre privilège
[ ] Audit logging (si Enterprise)

HAUTE DISPONIBILITÉ :
[ ] Replica Set avec 3+ membres
[ ] Membres dans différentes zones (availability zones)
[ ] Arbiter si nombre pair de membres
[ ] Read preference configuré
[ ] Write concern "majority" pour données critiques

PERFORMANCE :
[ ] Index sur tous les champs fréquemment filtrés
[ ] Index composés optimisés (règle ESR)
[ ] Projection utilisée dans les requêtes
[ ] Aggregation pipeline optimisé
[ ] Connection pooling configuré
[ ] WiredTiger cache dimensionné (50% RAM)

MONITORING :
[ ] Monitoring actif (Prometheus, Grafana, ou MongoDB Cloud Manager)
[ ] Alertes configurées (latence, réplication lag, cache ratio)
[ ] Profiler activé pour requêtes lentes
[ ] Logs centralisés

BACKUP :
[ ] Backup quotidien automatisé
[ ] Backup testé régulièrement
[ ] Rétention définie (7 jours, 30 jours, 1 an)
[ ] Stockage hors site
[ ] Plan de disaster recovery documenté

SCALABILITÉ :
[ ] Sharding si > 2 TB ou > 50K ops/sec
[ ] Shard key choisie judicieusement
[ ] Zone sharding si multi-régions
[ ] Monitoring du balancer


# [OK] PARTIE 14 : EXEMPLES PRATIQUES COMPLETS

"""
┌────────────────────────────────────────────────────────────────────────┐
│              EXEMPLES PRATIQUES AVEC TOUS LES LANGAGES                 │
└────────────────────────────────────────────────────────────────────────┘

1. APPLICATION E-COMMERCE COMPLÈTE (Node.js)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// app.js
const { MongoClient, ObjectId } = require('mongodb');

const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function main() {
  try {
    await client.connect();
    console.log("Connecté à MongoDB [OK]");
    
    const db = client.db('ecommerce');
    
    // Créer les index
    await createIndexes(db);
    
    // Insérer des produits
    await insertProducts(db);
    
    // Créer une commande (avec transaction)
    await createOrder(db);
    
    // Recherche produits
    await searchProducts(db);
    
    // Rapport de ventes
    await salesReport(db);
    
  } finally {
    await client.close();
  }
}

// Création des index
async function createIndexes(db) {
  await db.collection('products').createIndex({ category: 1, price: 1 });
  await db.collection('products').createIndex({ name: "text" });
  await db.collection('orders').createIndex({ customerId: 1, createdAt: -1 });
  console.log("Index créés [OK]");
}

// Insérer des produits
async function insertProducts(db) {
  const products = [
    {
      name: "Djembé Artisanal",
      category: "Instruments",
      price: 45000,
      currency: "XOF",
      stock: 15,
      description: "Djembé fait main en bois de Lenke",
      origin: "Dakar, Sénégal",
      tags: ["musique", "artisanat", "traditionnel"],
      images: [
        "https://example.com/djembe1.jpg",
        "https://example.com/djembe2.jpg"
      ],
      reviews: [],
      avgRating: 0,
      reviewCount: 0
    },
    {
      name: "Boubou Bazin",
      category: "Vêtements",
      price: 35000,
      currency: "XOF",
      stock: 25,
      description: "Boubou traditionnel en tissu bazin",
      origin: "Dakar, Sénégal",
      sizes: ["S", "M", "L", "XL"],
      colors: ["Bleu", "Blanc", "Noir"],
      tags: ["vêtement", "traditionnel", "bazin"],
      images: ["https://example.com/boubou1.jpg"],
      reviews: [],
      avgRating: 0,
      reviewCount: 0
    }
  ];
  
  const result = await db.collection('products').insertMany(products);
  console.log(`${result.insertedCount} produits insérés [OK]`);
}

// Créer une commande avec transaction
async function createOrder(db) {
  const session = client.startSession();
  
  try {
    await session.withTransaction(async () => {
      const customerId = new ObjectId();
      const productId = await db.collection('products')
        .findOne({ name: "Djembé Artisanal" }, { session })
        .then(p => p._id);
      
      // Vérifier le stock
      const product = await db.collection('products').findOne(
        { _id: productId, stock: { $gte: 2 } },
        { session }
      );
      
      if (!product) {
        throw new Error("Stock insuffisant");
      }
      
      // Créer la commande
      const order = {
        customerId,
        items: [
          {
            productId,
            productName: product.name,
            quantity: 2,
            price: product.price
          }
        ],
        total: product.price * 2,
        currency: product.currency,
        status: "pending",
        createdAt: new Date()
      };
      
      await db.collection('orders').insertOne(order, { session });
      
      // Décrémenter le stock
      await db.collection('products').updateOne(
        { _id: productId },
        { $inc: { stock: -2 } },
        { session }
      );
      
      console.log("Commande créée avec succès [OK]");
    });
  } catch (error) {
    console.error("Erreur lors de la commande:", error.message);
  } finally {
    await session.endSession();
  }
}

// Recherche de produits
async function searchProducts(db) {
  const results = await db.collection('products').find({
    category: "Instruments",
    price: { $lte: 50000 }
  })
  .sort({ price: 1 })
  .limit(10)
  .toArray();
  
  console.log(`\n${results.length} produits trouvés:`);
  results.forEach(p => {
    console.log(`  - ${p.name}: ${p.price} ${p.currency}`);
  });
}

// Rapport de ventes par catégorie
async function salesReport(db) {
  const report = await db.collection('orders').aggregate([
    // Décomposer les items
    { $unwind: "$items" },
    
    // Joindre avec produits
    {
      $lookup: {
        from: "products",
        localField: "items.productId",
        foreignField: "_id",
        as: "product"
      }
    },
    { $unwind: "$product" },
    
    // Grouper par catégorie
    {
      $group: {
        _id: "$product.category",
        totalRevenue: { 
          $sum: { $multiply: ["$items.quantity", "$items.price"] } 
        },
        totalOrders: { $sum: 1 },
        avgOrderValue: { 
          $avg: { $multiply: ["$items.quantity", "$items.price"] } 
        }
      }
    },
    
    // Trier par revenue
    { $sort: { totalRevenue: -1 } },
    
    // Formater
    {
      $project: {
        _id: 0,
        category: "$_id",
        totalRevenue: 1,
        totalOrders: 1,
        avgOrderValue: { $round: ["$avgOrderValue", 2] }
      }
    }
  ]).toArray();
  
  console.log("\n[GRAPHIQUE] Rapport de ventes:");
  console.table(report);
}

main().catch(console.error);


2. API REST AVEC EXPRESS.JS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// server.js
const express = require('express');
const { MongoClient, ObjectId } = require('mongodb');

const app = express();
app.use(express.json());

const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);
let db;

// Connexion MongoDB
async function connectDB() {
  await client.connect();
  db = client.db('api_ecommerce');
  console.log("Connecté à MongoDB [OK]");
}

// GET /products - Liste des produits (avec pagination)
app.get('/products', async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 20;
    const skip = (page - 1) * limit;
    
    const filter = {};
    if (req.query.category) filter.category = req.query.category;
    if (req.query.minPrice) filter.price = { $gte: parseFloat(req.query.minPrice) };
    
    const products = await db.collection('products')
      .find(filter)
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .toArray();
    
    const total = await db.collection('products').countDocuments(filter);
    
    res.json({
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
      data: products
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// GET /products/:id - Détail d'un produit
app.get('/products/:id', async (req, res) => {
  try {
    const product = await db.collection('products').findOne({
      _id: new ObjectId(req.params.id)
    });
    
    if (!product) {
      return res.status(404).json({ error: "Produit non trouvé" });
    }
    
    res.json(product);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// POST /products - Créer un produit
app.post('/products', async (req, res) => {
  try {
    const product = {
      ...req.body,
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    const result = await db.collection('products').insertOne(product);
    
    res.status(201).json({
      id: result.insertedId,
      ...product
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// PUT /products/:id - Mettre à jour un produit
app.put('/products/:id', async (req, res) => {
  try {
    const result = await db.collection('products').findOneAndUpdate(
      { _id: new ObjectId(req.params.id) },
      { 
        $set: {
          ...req.body,
          updatedAt: new Date()
        }
      },
      { returnDocument: 'after' }
    );
    
    if (!result.value) {
      return res.status(404).json({ error: "Produit non trouvé" });
    }
    
    res.json(result.value);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// DELETE /products/:id - Supprimer un produit
app.delete('/products/:id', async (req, res) => {
  try {
    const result = await db.collection('products').deleteOne({
      _id: new ObjectId(req.params.id)
    });
    
    if (result.deletedCount === 0) {
      return res.status(404).json({ error: "Produit non trouvé" });
    }
    
    res.json({ message: "Produit supprimé avec succès" });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// POST /products/:id/reviews - Ajouter un avis
app.post('/products/:id/reviews', async (req, res) => {
  try {
    const review = {
      _id: new ObjectId(),
      userId: new ObjectId(req.body.userId),
      userName: req.body.userName,
      rating: req.body.rating,
      comment: req.body.comment,
      date: new Date()
    };
    
    // Mettre à jour le produit
    const result = await db.collection('products').findOneAndUpdate(
      { _id: new ObjectId(req.params.id) },
      {
        $push: { reviews: review },
        $inc: { reviewCount: 1 }
      },
      { returnDocument: 'after' }
    );
    
    if (!result.value) {
      return res.status(404).json({ error: "Produit non trouvé" });
    }
    
    // Recalculer la moyenne
    const avgRating = result.value.reviews.reduce((sum, r) => sum + r.rating, 0) 
                      / result.value.reviews.length;
    
    await db.collection('products').updateOne(
      { _id: new ObjectId(req.params.id) },
      { $set: { avgRating: Math.round(avgRating * 10) / 10 } }
    );
    
    res.status(201).json(review);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Démarrer le serveur
connectDB().then(() => {
  app.listen(3000, () => {
    console.log("Serveur démarré sur http://localhost:3000");
  });
});


3. APPLICATION PYTHON (Flask + PyMongo)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# app.py
from flask import Flask, request, jsonify
from pymongo import MongoClient
from bson import ObjectId
from datetime import datetime

app = Flask(__name__)

# Connexion MongoDB
client = MongoClient('mongodb://localhost:27017/')
db = client['blog']

# Convertir ObjectId en string pour JSON
def serialize_doc(doc):
    if doc:
        doc['_id'] = str(doc['_id'])
    return doc

# GET /articles - Liste des articles
@app.route('/articles', methods=['GET'])
def get_articles():
    page = int(request.args.get('page', 1))
    limit = int(request.args.get('limit', 10))
    skip = (page - 1) * limit
    
    articles = list(db.articles.find(
        {'status': 'published'},
        {'content.raw': 0}  # Exclure le contenu complet
    )
    .sort('publishedAt', -1)
    .skip(skip)
    .limit(limit))
    
    total = db.articles.count_documents({'status': 'published'})
    
    return jsonify({
        'page': page,
        'limit': limit,
        'total': total,
        'data': [serialize_doc(a) for a in articles]
    })

# GET /articles/:slug - Article complet
@app.route('/articles/<slug>', methods=['GET'])
def get_article(slug):
    article = db.articles.find_one({'slug': slug, 'status': 'published'})
    
    if not article:
        return jsonify({'error': 'Article non trouvé'}), 404
    
    # Incrémenter les vues
    db.articles.update_one(
        {'_id': article['_id']},
        {'$inc': {'stats.views': 1}}
    )
    
    return jsonify(serialize_doc(article))

# POST /articles - Créer un article
@app.route('/articles', methods=['POST'])
def create_article():
    data = request.json
    
    article = {
        'slug': data['slug'],
        'title': data['title'],
        'author': {
            '_id': ObjectId(data['authorId']),
            'name': data['authorName']
        },
        'content': {
            'raw': data['content'],
            'html': data.get('html', ''),
            'excerpt': data['content'][:200]
        },
        'tags': data.get('tags', []),
        'status': 'draft',
        'createdAt': datetime.utcnow(),
        'updatedAt': datetime.utcnow(),
        'stats': {
            'views': 0,
            'likes': 0,
            'comments': 0
        }
    }
    
    result = db.articles.insert_one(article)
    article['_id'] = str(result.inserted_id)
    
    return jsonify(article), 201

# POST /articles/:id/comments - Ajouter un commentaire
@app.route('/articles/<id>/comments', methods=['POST'])
def add_comment(id):
    data = request.json
    
    comment = {
        '_id': ObjectId(),
        'userId': ObjectId(data['userId']),
        'userName': data['userName'],
        'text': data['text'],
        'createdAt': datetime.utcnow()
    }
    
    result = db.articles.update_one(
        {'_id': ObjectId(id)},
        {
            '$push': {'comments': comment},
            '$inc': {'stats.comments': 1}
        }
    )
    
    if result.matched_count == 0:
        return jsonify({'error': 'Article non trouvé'}), 404
    
    comment['_id'] = str(comment['_id'])
    return jsonify(comment), 201

# GET /search - Recherche full-text
@app.route('/search', methods=['GET'])
def search():
    query = request.args.get('q', '')
    
    if not query:
        return jsonify({'error': 'Paramètre q requis'}), 400
    
    articles = list(db.articles.find(
        {
            '$text': {'$search': query},
            'status': 'published'
        },
        {
            'score': {'$meta': 'textScore'},
            'content.raw': 0
        }
    )
    .sort([('score', {'$meta': 'textScore'})])
    .limit(20))
    
    return jsonify({
        'query': query,
        'count': len(articles),
        'results': [serialize_doc(a) for a in articles]
    })

if __name__ == '__main__':
    # Créer les index
    db.articles.create_index([('title', 'text'), ('content.raw', 'text')])
    db.articles.create_index([('slug', 1)], unique=True)
    db.articles.create_index([('status', 1), ('publishedAt', -1)])
    
    app.run(debug=True)


4. SCRIPT D'ANALYSE ET REPORTING (Python)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# analytics.py
from pymongo import MongoClient
from datetime import datetime, timedelta
import pandas as pd

client = MongoClient('mongodb://localhost:27017/')
db = client['ecommerce']

# Rapport de ventes des 30 derniers jours
def sales_report():
    thirty_days_ago = datetime.utcnow() - timedelta(days=30)
    
    pipeline = [
        {
            '$match': {
                'createdAt': {'$gte': thirty_days_ago},
                'status': 'completed'
            }
        },
        {
            '$group': {
                '_id': {
                    'year': {'$year': '$createdAt'},
                    'month': {'$month': '$createdAt'},
                    'day': {'$dayOfMonth': '$createdAt'}
                },
                'revenue': {'$sum': '$total'},
                'orders': {'$sum': 1},
                'avgOrderValue': {'$avg': '$total'}
            }
        },
        {
            '$sort': {'_id': 1}
        },
        {
            '$project': {
                '_id': 0,
                'date': {
                    '$dateToString': {
                        'format': '%Y-%m-%d',
                        'date': {
                            '$dateFromParts': {
                                'year': '$_id.year',
                                'month': '$_id.month',
                                'day': '$_id.day'
                            }
                        }
                    }
                },
                'revenue': {'$round': ['$revenue', 2]},
                'orders': 1,
                'avgOrderValue': {'$round': ['$avgOrderValue', 2]}
            }
        }
    ]
    
    results = list(db.orders.aggregate(pipeline))
    df = pd.DataFrame(results)
    
    print("\n[GRAPHIQUE] Rapport de ventes (30 derniers jours)")
    print(df.to_string(index=False))
    print(f"\nRevenue total: {df['revenue'].sum():,.2f} XOF")
    print(f"Commandes totales: {df['orders'].sum()}")
    print(f"Panier moyen: {df['avgOrderValue'].mean():,.2f} XOF")

# Top 10 produits
def top_products():
    pipeline = [
        {'$unwind': '$items'},
        {
            '$group': {
                '_id': '$items.productId',
                'productName': {'$first': '$items.productName'},
                'totalSold': {'$sum': '$items.quantity'},
                'revenue': {
                    '$sum': {'$multiply': ['$items.quantity', '$items.price']}
                }
            }
        },
        {'$sort': {'revenue': -1}},
        {'$limit': 10},
        {
            '$project': {
                '_id': 0,
                'product': '$productName',
                'quantitySold': '$totalSold',
                'revenue': {'$round': ['$revenue', 2]}
            }
        }
    ]
    
    results = list(db.orders.aggregate(pipeline))
    df = pd.DataFrame(results)
    
    print("\n[TROPHEE] Top 10 Produits")
    print(df.to_string(index=False))

# Analyse des clients
def customer_analysis():
    pipeline = [
        {
            '$group': {
                '_id': '$customerId',
                'totalOrders': {'$sum': 1},
                'totalSpent': {'$sum': '$total'},
                'avgOrderValue': {'$avg': '$total'},
                'lastOrder': {'$max': '$createdAt'}
            }
        },
        {
            '$addFields': {
                'customerSegment': {
                    '$switch': {
                        'branches': [
                            {
                                'case': {'$gte': ['$totalSpent', 100000]},
                                'then': 'VIP'
                            },
                            {
                                'case': {'$gte': ['$totalSpent', 50000]},
                                'then': 'Premium'
                            },
                            {
                                'case': {'$gte': ['$totalSpent', 10000]},
                                'then': 'Standard'
                            }
                        ],
                        'default': 'Nouveau'
                    }
                }
            }
        },
        {
            '$group': {
                '_id': '$customerSegment',
                'count': {'$sum': 1},
                'avgSpent': {'$avg': '$totalSpent'},
                'avgOrders': {'$avg': '$totalOrders'}
            }
        },
        {'$sort': {'avgSpent': -1}}
    ]
    
    results = list(db.orders.aggregate(pipeline))
    df = pd.DataFrame(results)
    df.columns = ['Segment', 'Clients', 'Dépense moyenne', 'Commandes moyennes']
    
    print("\n[UTILISATEURS] Analyse des clients par segment")
    print(df.to_string(index=False))

if __name__ == '__main__':
    sales_report()
    top_products()
    customer_analysis()


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CONCLUSION FINALE :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Tu connais maintenant MongoDB de A à Z !

[OK] Architecture complète (WiredTiger, MVCC, Journal)
[OK] Modélisation de données (Embed vs Reference)
[OK] Indexation avancée (tous les types d'index)
[OK] Aggregation Pipeline (requêtes complexes)
[OK] Réplication (Replica Sets, haute disponibilité)
[OK] Sharding (scalabilité horizontale)
[OK] Transactions ACID
[OK] Performance et optimisation
[OK] Sécurité et monitoring
[OK] Cas d'usage réels
[OK] Exemples pratiques complets

PROCHAINES ÉTAPES :
-> Pratiquer avec MongoDB Atlas (gratuit)
-> Suivre les cours MongoDB University
-> Créer ton propre projet
-> Explorer les fonctionnalités avancées (Change Streams, Time Series, etc.)

Bon développement avec MongoDB ! [RAPIDE][POSTGRES]
