Module 1 — Pourquoi un moteur de recherche et une base de graphes ? Installer le kit Veille
Chez Veille, jeune entreprise montréalaise de veille médiatique, Inès a posé la règle du premier jour : « avant de coder, on doit comprendre pourquoi le PostgreSQL de la version 1 nous a lâchés à cinquante mille articles ». Sami vient d'arriver ; ce module lui montre pourquoi la suite du produit s'écrit avec Elasticsearch et Neo4j, puis installe le kit Docker qui accompagnera les seize modules du cours.
Pourquoi une base relationnelle ne suffit plus
La première version du moteur de Veille cherchait dans un article avec SELECT * FROM articles WHERE headline LIKE '%climate change%'. Cela marche jusqu'à quelques milliers de lignes. Passé cinquante mille, trois problèmes apparaissent en même temps.
- Pas de pertinence. SQL vous rend toutes les lignes qui matchent, dans l'ordre du disque. « Climate Change Is Real » et « How I Learned to Change My Diet in a Changing Climate » sortent au même rang. L'utilisateur ne sait pas quoi lire en premier.
- Aucune tolérance.
LIKE '%climat change%'renvoie zéro résultat. Une faute de frappe et l'article disparaît. Les singuliers, pluriels, majuscules, accents et variantes (« climate », « climatic », « climates ») exigent chacun leur clause. - Lent, très lent. Un
LIKE '%…%'ne peut pas utiliser un index B-tree : PostgreSQL relit toute la table à chaque recherche. Sur 200 000 articles, chaque requête coûte plusieurs secondes.
Les extensions plein texte de PostgreSQL (tsvector, pg_trgm) résolvent une partie du problème et suffisent parfois. Le cours n'est pas contre : il montre juste que quand la recherche devient le produit — pertinence, autocomplétion, agrégations, tableaux de bord, tolérance aux fautes, multilingue — un moteur dédié coûte moins cher à entretenir qu'un empilement d'extensions.
L'idée derrière un moteur de recherche : l'index inversé
Elasticsearch ne cherche jamais dans les articles. Il cherche dans une table qui, pour chaque terme du corpus, liste les documents qui le contiennent. On appelle cela un index inversé.
Sur nos titres, le pipeline d'analyse coupe « Climate Change Is Here » en jetons [climate, change, is, here], jette les mots vides (is, here), passe en minuscules, applique un stemmer anglais (changing → change) et enregistre :
climate → docs [42, 137, 501, 1729, ...]
change → docs [42, 501, 833, 1729, ...]
Chercher « climate change » revient alors à intersecter deux listes triées : opération immédiate, indépendante de la taille du corpus. Le moteur ajoute par-dessus un score de pertinence (BM25) qui remonte les documents où les termes sont plus rares et plus fréquents dans le document — d'où « Climate Change Is Real » avant « How I Learned to Change My Diet ».
C'est aussi l'index inversé qui rend possibles la tolérance aux fautes, l'autocomplétion, l'analyse par catégories et les tableaux de bord Kibana. Le module 4 démonte le pipeline d'analyse ; les modules 5 et 6 exploitent l'index côté requêtes.
Quand un graphe bat une jointure
La base relationnelle est parfaite pour compter les articles par catégorie. Elle devient poussive dès qu'on veut suivre des relations sur plusieurs sauts : « quels auteurs ont écrit sur les mêmes sujets qu'un auteur donné ? », « quelle est la chaîne la plus courte entre deux thématiques via les articles partagés ? », « quel article recommander à un lecteur qui a apprécié celui-ci ? ». Chaque question demande une jointure supplémentaire, chacune multiplie les lignes intermédiaires.
Une base de graphes stocke directement les relations comme des objets de première classe. (:Article)-[:ECRIT_PAR]->(:Auteur)-[:ECRIT_PAR]-(:Article)-[:PUBLIE_DANS]->(:Categorie) se parcourt en suivant les pointeurs, sans matérialiser de table intermédiaire. Un chemin de longueur trois reste immédiat même sur un graphe de deux cent mille articles, vingt-trois mille auteurs et quarante et une catégories — ce que le module 12 démontre en direct sur le corpus News.
Le kit Veille en un coup d'œil
Tout le cours tient dans un dossier unique, un fichier docker-compose.yml et deux scripts, lab.sh pour macOS, Linux, WSL2 et Git Bash, lab.ps1 pour Windows PowerShell. La règle est simple : on ne tape jamais de pip install, jamais de apt, jamais de brew. Tout ce dont vous avez besoin — Elasticsearch, Kibana, Neo4j avec APOC, l'importateur du corpus, un conteneur Python avec les clients officiels, et OpenSearch pour le comparatif du module 9 — tourne dans Docker.
Télécharger et ouvrir le dossier
Téléchargez le kit (42-elasticsearch-neo4j.zip, 30 Ko), décompressez-le, ouvrez un terminal dedans. Sous macOS, Linux et WSL2, rendez les scripts exécutables une seule fois avec chmod +x lab.sh doctor.sh. Le dossier ressemble à cela :
42-elasticsearch-neo4j/
docker-compose.yml
env.example
lab.sh lab.ps1
doctor.sh doctor.ps1
elasticsearch/mappings/news.json
importer/import_news.py
neo4j/cypher/ neo4j/import/
python/ data/
lab.ps1 reproduit lab.sh à l'identique dans PowerShell. Toutes les commandes du cours seront données sous la forme ./lab.sh <sous-commande> ; l'équivalent Windows est .\lab.ps1 <sous-commande>. Une seule fois par module la ligne PowerShell sera rappelée.
Vérifier la machine avec doctor
La première commande à lancer est le diagnostic. Il vérifie Docker, Docker Compose v2, la mémoire allouée, les ports 9200, 5601, 7474, 7687, vm.max_map_count, l'espace disque et l'accès au registre d'images docker.elastic.co.
./lab.sh doctor # macOS, Linux, WSL2, Git Bash
.\lab.ps1 doctor # Windows PowerShell
Une exécution qui passe ressemble à cela :
Kit Veille — diagnostic
[OK] Docker 29.0.2 — démon joignable
[OK] Docker Compose v2.36.1
[OK] mémoire allouée à Docker : 8 Go
[OK] port 9200 libre
[OK] port 5601 libre
[OK] port 7474 libre
[OK] port 7687 libre
[OK] vm.max_map_count = 262144
[OK] espace disque libre : 42 Go
[OK] docker.elastic.co joignable (le premier ./lab.sh up téléchargera ≈ 3 Go)
Prêt. Lancez : ./lab.sh up
Un exemple d'échec typique et sa correction :
[KO] port 9200 déjà occupé par un autre programme
→ Linux/macOS : sudo lsof -iTCP:9200 -sTCP:LISTEN
Windows : netstat -ano | findstr :9200
— arrêtez ce programme (souvent un ancien conteneur : docker ps)
[KO] mémoire allouée à Docker : 3 Go — insuffisant (4 Go minimum, 6 Go recommandés)
→ Docker Desktop → Settings → Resources → Memory
WSL2 : %UserProfile%\.wslconfig → [wsl2] memory=8GB, puis wsl --shutdown
Chaque [KO] est suivi de la commande exacte qui le corrige. Vous ne devriez jamais rester bloqué plus de trente secondes sur un prérequis : c'est le premier engagement du cours.
Docker Desktop règle vm.max_map_count automatiquement dans sa distribution WSL2 interne. Si vous avez installé Docker Engine dans une distribution WSL2 séparée, doctor vous demande de l'exporter à la main.
Démarrer la plateforme avec up
./lab.sh up
La commande démarre elasticsearch, attend qu'il soit healthy, lance le service éphémère setup qui définit le mot de passe de kibana_system, puis démarre kibana et neo4j. Le premier démarrage prend quatre minutes environ : téléchargement des images (Elasticsearch 9.5.3 pèse un peu plus d'un giga-octet, Neo4j 5.26 environ six cents mégas). Les démarrages suivants tournent en deux minutes quarante sur une machine standard.
À la fin, la sortie affiche :
NAME STATUS PORTS
veille-es Up 2 minutes (healthy) 0.0.0.0:9200->9200/tcp
veille-kibana Up 1 minute (healthy) 0.0.0.0:5601->5601/tcp
veille-neo4j Up 2 minutes (healthy) 0.0.0.0:7474->7474/tcp, 0.0.0.0:7687->7687/tcp
Accès
Elasticsearch http://localhost:9200 (elastic / veille2026)
Kibana http://localhost:5601 (elastic / veille2026)
Neo4j Browser http://localhost:7474 (neo4j / veille2026) bolt://localhost:7687
Se connecter à Kibana et à Neo4j Browser
Ouvrez http://localhost:5601 dans un navigateur. L'utilisateur est elastic, le mot de passe veille2026. Kibana s'ouvre en français, prend une dizaine de secondes à charger sa première page. Naviguez vers Management → Dev Tools : c'est la console qu'on utilisera pour toutes les requêtes du cours.
Tapez cette première requête dans Dev Tools et cliquez sur la flèche verte (raccourci Ctrl-Entrée) :
GET /
Vous devez voir répondre le nœud Elasticsearch avec son nom, sa version, le nom du cluster et un message de bienvenue. C'est votre premier appel réussi à l'API.
Ouvrez maintenant http://localhost:7474. Neo4j Browser vous demande une URI : laissez bolt://localhost:7687, utilisateur neo4j, mot de passe veille2026. Une fois connecté, tapez dans la barre du haut :
CALL dbms.components() YIELD name, versions, edition
Le résultat indique Neo4j Kernel, version 5.26.x, édition community. Le graphe est vide pour l'instant ; le module 10 le peuplera.
Interroger Elasticsearch sans Kibana
Pour les vérifications rapides en ligne de commande, ./lab.sh es <chemin> fait un GET authentifié :
./lab.sh es _cat/indices?v
./lab.sh es _cluster/health?pretty
./lab.sh es _cat/nodes?v
Cette commande est réservée aux GET sans corps. Toutes les autres opérations (POST, PUT, DELETE, _search avec un corps JSON) passent par la console Dev Tools de Kibana. Vous éviterez ainsi le piège des guillemets d'échappement dans le terminal.
Arrêter et recommencer
./lab.sh down # arrête tout, conserve les données
./lab.sh reset # arrête tout ET supprime les données (retour à zéro)
./lab.sh status # état, URL et identifiants
./lab.sh logs kibana # suivre les journaux d'un service
down est le geste normal en fin de journée : les volumes Docker conservent les données, le prochain up repart en trois minutes. reset est le geste de secours : conteneurs supprimés, volumes effacés, neo4j/import/news.csv supprimé, et le prochain up repart d'un état totalement neuf. Le fichier téléchargé data/News_Category_Dataset_v2.json est conservé pour éviter un téléchargement de quatre-vingts mégas.
Un conteneur, un rôle
Le kit démarre six conteneurs principaux. Comprendre qui fait quoi rend les journaux lisibles.
| Conteneur | Rôle |
|---|---|
veille-es | le nœud Elasticsearch 9.5.3 : cluster à un seul nœud, sécurité activée, HTTP sans TLS, heap fixé à 1 Go. |
veille-setup | service éphémère qui, après veille-es, définit le mot de passe interne de kibana_system et se termine. |
veille-kibana | Kibana 9.5.3 en français, monté sur veille-es, avec Dev Tools, Discover, Lens et les tableaux de bord. |
veille-neo4j | Neo4j 5.26 community avec le plugin APOC préinstallé ; monte neo4j/import et neo4j/cypher. |
veille-importer | conteneur du profil tools, à la demande : télécharge le corpus, crée l'index news, indexe en _bulk. |
veille-python | conteneur du profil tools : Python 3 avec les clients officiels elasticsearch et neo4j préinstallés. |
Un septième et un huitième conteneur, veille-opensearch et veille-os-dashboards, dorment sous le profil opensearch. Ils démarrent uniquement quand vous lancez ./lab.sh opensearch-up au module 9, sur les ports 9201 et 5602 pour ne pas entrer en conflit avec Elasticsearch et Kibana.
Ce que fait le kit à votre place
Le kit règle silencieusement tout ce qui a fait perdre des heures aux cohortes précédentes :
- Le mot de passe de
kibana_systemest défini par le servicesetupvia l'API/_security/user/kibana_system/_password. Vous n'avez jamais à copier un token à la main entre Elasticsearch et Kibana. - Le heap Java (
-Xms1g -Xmx1g) est écrit dansdocker-compose.yml, pas dans.env. Le piège des guillemets qui font croire à un heap non pris en compte disparaît. - Le seuil de disque est désactivé (
cluster.routing.allocation.disk.threshold_enabled=false) : un disque à quatre-vingt-quinze pour cent ne passera pas votre index en lecture seule pendant l'atelier. - Les healthchecks attendent que chaque service réponde vraiment avant que
./lab.sh uprende la main. Vous n'ouvrez jamais Kibana pendant qu'Elasticsearch est encore en train de démarrer. - APOC est ajouté à Neo4j via
NEO4J_PLUGINS=["apoc"]et gardé dans un volumeneo4j-plugins: téléchargement une fois, disponible ensuite même hors ligne. - Une clé de chiffrement Kibana de plus de trente-deux caractères est fournie dans
env.examplepour éviter la ligne rouge « Kibana requires a value forxpack.encryptedSavedObjects.encryptionKey».
env.exampleLe fichier env.example est copié en .env au premier lancement. Vous pouvez y changer les mots de passe (uniquement des lettres et des chiffres pour éviter les problèmes d'échappement dans le shell) ou fixer NEWS_LIMIT=20000 pour un import éclair sur une petite machine. Après avoir modifié .env, il faut ./lab.sh reset puis ./lab.sh up : les mots de passe sont écrits dans le volume au premier démarrage.
Besoins machine
Quatre giga-octets de RAM alloués à Docker suffisent pour Elasticsearch, Kibana et Neo4j. Six giga-octets sont nécessaires pour ajouter OpenSearch en parallèle au module 9. Il faut environ dix giga-octets d'espace disque (trois pour les images, deux pour les données), et un accès sortant vers docker.elastic.co et registry-1.docker.io au premier démarrage. Aucune de ces contraintes n'est vérifiée manuellement : ./lab.sh doctor le fait à votre place.
À vous
Exercice 1 — Diagnostiquer et démarrer
Lancez ./lab.sh doctor, corrigez chaque [KO] en suivant la flèche, puis lancez ./lab.sh up. Notez, dans un fichier journal.md, le temps qu'a pris votre premier up.
Solution
Sur une machine standard avec Docker Desktop, doctor rend la main en trois secondes. Le premier up prend entre trois et cinq minutes (téléchargement des images comprises) ; les suivants tournent en deux minutes quarante. Si votre up dépasse huit minutes, ouvrez un autre terminal et lancez ./lab.sh logs elasticsearch pour voir ce qui se passe.
Exercice 2 — Vérifier trois fois
Dans Kibana Dev Tools, exécutez :
GET /
GET /_cluster/health
GET /_cat/nodes?v
Notez le nom du cluster, sa couleur (green ou yellow) et le nom du nœud. Ensuite, dans un terminal, lancez la même dernière requête avec ./lab.sh es _cat/nodes?v. Confirmez que la sortie est identique.
Solution
Le nom du cluster est veille, la couleur green (nous verrons pourquoi au module 2), le nom du nœud veille-es. La commande ./lab.sh es _cat/nodes?v renvoie exactement la même sortie tabulaire : c'est la même API HTTP appelée avec les mêmes identifiants, une fois via Kibana, une fois via curl dans le conteneur veille-es.
Exercice 3 — Éteindre proprement, redémarrer
Lancez ./lab.sh down, attendez que les trois conteneurs disparaissent (docker ps), puis relancez ./lab.sh up. Chronométrez le second démarrage. Enfin, essayez ./lab.sh reset — les volumes disparaissent — et refaites ./lab.sh up : vous verrez la différence.
Solution
Après down puis up, le démarrage descend autour de deux minutes quarante : les images sont en cache, le heap est déjà provisionné, Elasticsearch monte plus vite. Après reset puis up, le temps remonte vers trois à quatre minutes parce que le mot de passe de kibana_system doit être redéfini et que Kibana recrée ses index système. C'est normal.
Points à retenir
- Un
LIKE '%…%'en SQL n'est pas un moteur de recherche : ni pertinence, ni tolérance, et lent au-delà de quelques milliers de lignes. - Elasticsearch cherche via un index inversé : « quels documents contiennent tel terme » plutôt que « quels termes contient tel document ».
- Une base de graphes rend immédiats les parcours à profondeur variable qu'une base relationnelle paie en jointures multiples.
- Le kit Veille tient dans un dossier unique et se pilote avec trois commandes :
doctor,up,import-news. ./lab.sh doctordiagnostique la machine et donne la commande exacte de correction pour chaque problème.- Kibana Dev Tools (http://localhost:5601,
elastic/veille2026) est la console de référence du cours ;./lab.sh es <chemin>couvre lesGETen ligne de commande. downconserve les données,resetrepart de zéro : ce sont vos deux gestes de fin de journée.
Si ça ne marche pas
./lab.sh upreste bloqué sur « attente de Kibana » → Kibana met parfois plus de trois minutes au premier démarrage sur un disque lent. Vérifiez avec./lab.sh logs kibana: si vous voyezKibana is now available, patientez ; sinon, relevez le message rouge.[KO] port 9200 déjà occupé par un autre programme→ un ancien conteneur Elasticsearch tourne encore.docker pspour le repérer,docker stop <nom>pour l'arrêter, puis./lab.sh doctor.- Kibana affiche « Kibana server is not ready yet » → le service
setupn'a pas fini. Attendez trente secondes ou consultez./lab.sh logs setup; en dernier recours,./lab.sh resetpuis./lab.sh up. vm.max_map_counttrop bas sous Linux natif →sudo sysctl -w vm.max_map_count=262144corrige la session en cours ; pour rendre permanent,./lab.sh doctorvous donne la commande à ajouter dans/etc/sysctl.d/99-elasticsearch.conf.
Pour aller plus loin
- Documentation Elasticsearch 9 — Install Elasticsearch with Docker
- Documentation Kibana 9 — Configure Kibana
- Documentation Neo4j 5 — Neo4j Docker image
- Documentation Docker — Compose profiles