Aller au contenu principal

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.
Le contre-exemple utile

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 (changingchange) 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/
Windows sans WSL2

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.

Windows et WSL2

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.

ConteneurRôle
veille-esle nœud Elasticsearch 9.5.3 : cluster à un seul nœud, sécurité activée, HTTP sans TLS, heap fixé à 1 Go.
veille-setupservice éphémère qui, après veille-es, définit le mot de passe interne de kibana_system et se termine.
veille-kibanaKibana 9.5.3 en français, monté sur veille-es, avec Dev Tools, Discover, Lens et les tableaux de bord.
veille-neo4jNeo4j 5.26 community avec le plugin APOC préinstallé ; monte neo4j/import et neo4j/cypher.
veille-importerconteneur du profil tools, à la demande : télécharge le corpus, crée l'index news, indexe en _bulk.
veille-pythonconteneur 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_system est défini par le service setup via 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 dans docker-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 up rende 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 volume neo4j-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.example pour éviter la ligne rouge « Kibana requires a value for xpack.encryptedSavedObjects.encryptionKey ».
Modifier env.example

Le 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 doctor diagnostique 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 les GET en ligne de commande.
  • down conserve les données, reset repart de zéro : ce sont vos deux gestes de fin de journée.

Si ça ne marche pas

  • ./lab.sh up reste 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 voyez Kibana 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 ps pour le repérer, docker stop <nom> pour l'arrêter, puis ./lab.sh doctor.
  • Kibana affiche « Kibana server is not ready yet » → le service setup n'a pas fini. Attendez trente secondes ou consultez ./lab.sh logs setup ; en dernier recours, ./lab.sh reset puis ./lab.sh up.
  • vm.max_map_count trop bas sous Linux natifsudo sysctl -w vm.max_map_count=262144 corrige la session en cours ; pour rendre permanent, ./lab.sh doctor vous donne la commande à ajouter dans /etc/sysctl.d/99-elasticsearch.conf.

Pour aller plus loin