Εργαλειοθήκη — Έναρξη, επαλήθευση, ζημιά και επιδιόρθωση του εργαστηρίου

Καθοδηγούμενη πρακτική64 λεπτά
Διάρκεια
45 έως 60 λεπτά
Ενότητα
1/7
Θα φτιάξεις
ένα φορτωμένο εργαστήριο, επαληθευμένο με τρεις δρόμους (σενάριο, Dev Tools, Neo4j Browser), μετά σκόπιμα χαλασμένο και επιδιορθωμένο
Παραδοτέο
η πλήρης έξοδος του etat με (healthy) παντού, 504 / 609 / 12000 έγγραφα και 872 κόμβοι, συν δύο γραμμές που εξηγούν τη βλάβη που προκάλεσες

Πώς να διαβάσεις αυτή τη σελίδα. Κάθε ενότητα είναι διπλωμένη κάτω από τον τίτλο της: κάνε κλικ στο «Afficher …» για να την ανοίξεις, και ξανάκλεισέ την όταν τελειώσεις για να κρατήσεις τη σελίδα ευανάγνωστη. Σειρά ανάγνωσης: Objectif (Στόχος), μετά En bref (Εν συντομία, οι εντολές που θα πληκτρολογήσεις), μετά Le jeu de données (Το σύνολο δεδομένων, να διαβαστεί πριν από κάθε ερώτημα), μετά τα ερωτήματα Elasticsearch και Neo4j, ταξινομημένα από το απλούστερο (GET _cat/indices, MATCH (n) RETURN n) στο πιο εντυπωσιακό, με μια εξήγηση μετά το καθένα. Το λεπτομερές βήμα προς βήμα, με την αναμενόμενη έξοδο κάθε εντολής και τις βλάβες προς πρόκληση, βρίσκεται σε παράρτημα: παράρτημα Α για Windows (PowerShell), παράρτημα Β για Linux, macOS, WSL 2 και Git Bash. Άνοιξε μόνο ένα παράρτημα, αυτό του συστήματός σου. Το παράρτημα Γ, κοινό, συγκεντρώνει τις περιπτώσεις που κολλάνε.

Objectif (Στόχος)

Εντάσσεσαι στην ομάδα που κατασκευάζει τη μηχανή αναζήτησης της πλατφόρμας διαδικτυακών μαθημάτων. Η προϊσταμένη σου σου δίνει το kit του εργαστηρίου: «Αύριο το πρωί, θέλω ένα εργαστήριο που να τρέχει στον σταθμό σου, τα δεδομένα φορτωμένα, και τη βεβαιότητα ότι ξέρεις να το επιδιορθώνεις χωρίς να με καλέσεις.» Θα ξεκινήσεις λοιπόν το stack, θα αποδείξεις ότι οι τρεις κινητήρες περιέχουν αυτό που πρέπει, μετά θα σταματήσεις μια υπηρεσία επίτηδες για να δεις πώς διαβάζεται η βλάβη στο etat, στο πρόγραμμα περιήγησης και στο αρχείο καταγραφής, και θα την επαναφέρεις σε λειτουργία. Το να αναγνωρίζεις «αυτή η υπηρεσία είναι σταματημένη» σε δέκα δευτερόλεπτα είναι αυτό που αποτρέπει ώρες αναζήτησης στο λάθος μέρος.

Τα εννέα βήματα αυτού του διαγράμματος περιγράφονται λεπτομερώς, με την αναμενόμενη έξοδο κάθε εντολής, στο παράρτημα Α (Windows) ή στο παράρτημα Β (Linux, macOS) στο τέλος της σελίδας.

En bref (Εν συντομία): οι εντολές του εργαστηρίου

Εμφάνιση εντολών

Kit του εργαστηρίου: https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr

Κλωνοποιείς το kit σε έναν φάκελο lab1, επαληθεύεις ότι το Docker είναι έτοιμο, ξεκινάς τις τρεις υπηρεσίες (Elasticsearch, Kibana, Neo4j), ανοίγεις τις τρεις ιστοσελίδες τους, μετά φορτώνεις τα δεδομένα. Εκτελείς το importer και το charger-graphe δύο φορές: η δεύτερη δεν πρέπει να αλλάξει τίποτα στους μετρητές, αυτή είναι η απόδειξη ότι η φόρτωση επαναλαμβάνεται χωρίς διπλότυπο. Στο τέλος, το etat πρέπει να εμφανίζει (healthy) παντού, acces 12000 avis 609 cours 504 και nœuds : 872. Ξεκίνα εκτελώντας αυτό το μπλοκ.

Windows (PowerShell)

powershell
git clone https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr.git lab1
cd lab1
ls                       # explorer le contenu : docker-compose.yml, labo.ps1, labo.sh, elasticsearch/, neo4j/, outils/
.\labo.ps1 prerequis
.\labo.ps1 demarrer
.\labo.ps1 etat

Επαλήθευσε τα τρία URL στο πρόγραμμα περιήγησης:

text
Kibana           http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
Elasticsearch    http://localhost:9200
Neo4j Browser    http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)
powershell
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer          # 2e passage : mêmes compteurs, rien ne double
.\labo.ps1 charger-graphe
.\labo.ps1 etat              # attendu : acces 12000  avis 609  cours 504  nœuds : 872

Αν το PowerShell αρνείται το .\labo.ps1 («η εκτέλεση σεναρίων είναι απενεργοποιημένη»): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, απάντησε O, ξανατρέξε.

Linux, macOS, WSL 2, Git Bash

bash
git clone https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr.git lab1
cd lab1
ls                       # explorer le contenu : docker-compose.yml, labo.sh, labo.ps1, elasticsearch/, neo4j/, outils/
./labo.sh prerequis
./labo.sh demarrer
./labo.sh etat

Επαλήθευσε τα τρία URL στο πρόγραμμα περιήγησης:

text
Kibana           http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
Elasticsearch    http://localhost:9200
Neo4j Browser    http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)
bash
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer           # 2e passage : mêmes compteurs, rien ne double
./labo.sh charger-graphe
./labo.sh etat               # attendu : acces 12000  avis 609  cours 504  nœuds : 872

Το σύνολο δεδομένων: τι θα χειριστείς

Afficher le jeu de données (Εμφάνιση συνόλου δεδομένων)

Πριν πληκτρολογήσεις έστω ένα ερώτημα, κοίταξε τα δεδομένα. Όλο το εργαστήριο περιστρέφεται γύρω από μια φανταστική πλατφόρμα διαδικτυακών μαθημάτων: έναν κατάλογο μαθημάτων, τις κριτικές που αφήνουν οι μαθητές, το αρχείο καταγραφής του διακομιστή ιστού που εξυπηρετεί τις σελίδες, και τους συνδέσμους μεταξύ μαθητών, καθηγητών, μαθημάτων και δεξιοτήτων. Τα ίδια δεδομένα φορτώνονται στο Elasticsearch (για αναζήτηση) και στο Neo4j (για παρακολούθηση των συνδέσμων). Βρίσκονται στο kit, σε καθαρή μορφή, σε δύο φακέλους:

text
lab1/
├── elasticsearch/donnees/        ← ce qui va dans Elasticsearch (3 fichiers NDJSON)
│   ├── cours.ndjson                 504 cours
│   ├── avis.ndjson                  609 avis
│   └── acces.ndjson              12 000 lignes de journal web
└── neo4j/import/                 ← ce qui va dans Neo4j (8 fichiers CSV)
    ├── cours.csv                    504 cours          → nœuds Cours
    ├── etudiants.csv                300 étudiants      → nœuds Etudiant
    ├── professeurs.csv               30 professeurs    → nœuds Professeur
    ├── competences.csv               22 compétences    → nœuds Competence
    ├── villes.csv                    16 villes         → nœuds Ville
    ├── inscriptions.csv           1 654 inscriptions   → relations INSCRIT_A
    ├── couvre.csv                   994 liens          → relations COUVRE
    └── prerequis.csv                230 liens          → relations PREREQUIS_DE

Άνοιξέ τα ο ίδιος, παίρνει δέκα δευτερόλεπτα και θα ξέρεις ακριβώς τι χειρίζεσαι:

powershell
# Windows (PowerShell), depuis le dossier lab1
Get-Content elasticsearch\donnees\cours.ndjson -TotalCount 2
Get-Content elasticsearch\donnees\avis.ndjson  -TotalCount 2
Get-Content elasticsearch\donnees\acces.ndjson -TotalCount 2
Get-Content neo4j\import\cours.csv -TotalCount 3
Get-Content neo4j\import\inscriptions.csv -TotalCount 3
bash
# Linux, macOS, WSL 2, Git Bash, depuis le dossier lab1
head -n 2 elasticsearch/donnees/cours.ndjson
head -n 2 elasticsearch/donnees/avis.ndjson
head -n 2 elasticsearch/donnees/acces.ndjson
head -n 3 neo4j/import/cours.csv
head -n 3 neo4j/import/inscriptions.csv

Πλευρά Elasticsearch: τρία index, τρία είδη εγγράφων

Ένα αρχείο .ndjson (newline-delimited JSON) περιέχει ένα αντικείμενο JSON ανά γραμμή. Στο kit, οι γραμμές πάνε ανά δύο: μια γραμμή εντολής («τοποθέτησε το παρακάτω στο index cours με το αναγνωριστικό C0001»), μετά το ίδιο το έγγραφο. Είναι η μορφή που στέλνει η εντολή importer στο Elasticsearch μαζικά.

text
{"index":{"_index":"cours","_id":"C0001"}}
{"id":"C0001","titre":"Docker expliqué simplement","description":"…","categorie":"DevOps", … }

Index cours: 504 έγγραφα, ένα ανά μάθημα του καταλόγου

Το πρώτο έγγραφο, όπως είναι στο Elasticsearch:

json
{
  "id": "C0001",
  "titre": "Docker expliqué simplement",
  "description": "Dans ce cours accessible sans prérequis, vous apprenez à automatiser vos applications avec Docker. …",
  "categorie": "DevOps",
  "sujet": "Docker",
  "niveau": "debutant",
  "langue": "en",
  "prix": 129,
  "gratuit": false,
  "duree_heures": 5,
  "tags": ["docker", "linux", "helm", "devops"],
  "date_publication": "2024-08-14",
  "note_moyenne": 4.4,
  "nb_avis": 327,
  "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },
  "competences": ["Conteneurisation", "Intégration continue"]
}
ΠεδίοΠαράδειγμαΤι είναι
idC0001Αναγνωριστικό του μαθήματος. C όπως cours (μάθημα), μετά ένας αριθμός. Είναι επίσης το _id του εγγράφου.
titreDocker expliqué simplementΟ τίτλος. Ελεύθερο κείμενο, πάνω σε αυτό θα γίνονται οι αναζητήσεις με λέξεις.
descriptionDans ce cours…Μια παράγραφος παρουσίασης. Επίσης ελεύθερο κείμενο.
categorieDevOpsΜία από τις έξι μεγάλες οικογένειες: Cloud, DevOps, Données (Δεδομένα), Développement web (Ανάπτυξη ιστού), IA (ΤΝ), Sécurité (Ασφάλεια). 84 μαθήματα η καθεμία.
sujetDockerΠιο συγκεκριμένο από την κατηγορία: Docker, Kubernetes, Neo4j, Elasticsearch…
niveaudebutantdebutant (αρχάριος), intermediaire ή avance.
langueenfr ή en.
prix129Σε δολάρια. 0 για δωρεάν μάθημα.
gratuitfalseΑληθές ή ψευδές.
duree_heures5Συνολική διάρκεια του μαθήματος.
tags["docker", "linux", …]Μια λίστα λέξεων-κλειδιών. Ένα πεδίο μπορεί να περιέχει πολλές τιμές.
date_publication2024-08-14Μια ημερομηνία.
note_moyenne4.4Μέσος όρος των βαθμολογιών, από το 5.
nb_avis327Αριθμός κριτικών που ελήφθησαν.
professeur{ "id": "P001", "nom": …, "ville": … }Ένα αντικείμενο μέσα στο αντικείμενο: ο καθηγητής περιγράφεται απευθείας μέσα στην κάρτα του μαθήματος, με το αναγνωριστικό, το όνομα και την πόλη του.
competences["Conteneurisation", …]Λίστα των δεξιοτήτων που καλύπτει το μάθημα.

Index avis: 609 έγγραφα, ένα ανά κριτική που άφησε ένας μαθητής

json
{
  "id": "A00001",
  "cours_id": "C0028",
  "etudiant": "Nathan",
  "ville": "Sherbrooke",
  "pays": "Canada",
  "note": 3,
  "texte": "Les vidéos sont bonnes, la partie théorique est dense.",
  "date": "2024-06-21",
  "utile": 33
}
ΠεδίοΠαράδειγμαΤι είναι
idA00001Αναγνωριστικό της κριτικής. A όπως avis (κριτική).
cours_idC0028Το σχετικό μάθημα. Είναι ο σύνδεσμος προς το index cours: αυτό το C0028 είναι το id ενός εγγράφου του cours.
etudiantNathanΤο όνομα του συγγραφέα.
ville, paysSherbrooke, CanadaΑπό πού γράφει.
note3Η βαθμολογία που δόθηκε, από 1 έως 5.
texteLes vidéos sont bonnes…Το σχόλιο. Ελεύθερο κείμενο.
date2024-06-21Ημερομηνία της κριτικής.
utile33Αριθμός ατόμων που βρήκαν αυτή την κριτική χρήσιμη.

Index acces: 12.000 έγγραφα, μία γραμμή ανά αίτημα που έλαβε ο διακομιστής ιστού

json
{
  "id": "L000001",
  "@timestamp": "2026-08-10T08:17:19.000Z",
  "methode": "GET",
  "chemin": "/robots.txt",
  "cours_id": null,
  "categorie": null,
  "statut": 200,
  "octets": 108506,
  "duree_ms": 100,
  "ip": "108.190.166.1",
  "pays": "CA",
  "appareil": "desktop",
  "navigateur": "Edge",
  "referent": "google"
}
ΠεδίοΠαράδειγμαΤι είναι
idL000001Αναγνωριστικό της γραμμής. L όπως ligne (γραμμή) αρχείου καταγραφής (log).
@timestamp2026-08-10T08:17:19.000ZΑκριβής ημερομηνία και ώρα του αιτήματος. Το @ είναι σύμβαση: είναι το πεδίο χρόνου που το Kibana εντοπίζει αυτόματα.
methodeGETGET ή POST.
chemin/robots.txtΗ διεύθυνση που ζητήθηκε στον ιστότοπο: /, /cours, /catalogue, /contact, /tarifs, ή η σελίδα ενός μαθήματος.
cours_idnull ή C0042Το μάθημα που εμφανίστηκε, αν η σελίδα αφορά κάποιο· null (κενό) αλλιώς, όπως εδώ για το /robots.txt. Σύνδεσμος προς το index cours.
categorienull ή DevOpsΚατηγορία του μαθήματος που εμφανίστηκε, αντιγραμμένη για να απλοποιεί τα γραφήματα.
statut200Ο κωδικός HTTP της απάντησης: 200 OK, 301 και 304 ανακατεύθυνση ή cache, 404 δεν βρέθηκε, 500 και 503 σφάλμα διακομιστή.
octets108506Μέγεθος της απάντησης που στάλθηκε.
duree_ms100Χρόνος απόκρισης σε χιλιοστά του δευτερολέπτου.
ip108.190.166.1Διεύθυνση IP του επισκέπτη.
paysCAΧώρα του επισκέπτη, κωδικός δύο γραμμάτων.
appareildesktopdesktop, mobile ή tablette (tablet).
navigateurEdgeChrome, Firefox, Safari, Edge…
referentgoogleΑπό πού ήρθε ο επισκέπτης: direct, google, linkedin, youtube ή newsletter.

Τα τρία index είναι συνδεδεμένα μέσω του cours_id: μια κριτική μιλά για ένα μάθημα, μια γραμμή αρχείου καταγραφής επισκέπτεται ένα μάθημα. Αλλά το Elasticsearch δεν κάνει join: κάθε index αναζητείται ξεχωριστά. Για να παρακολουθήσουμε τους συνδέσμους, περνάμε στο Neo4j.

Πλευρά Neo4j: πέντε είδη κόμβων, πέντε είδη σχέσεων

Το Neo4j λαμβάνει τα ίδια μαθήματα (ίδια αναγνωριστικά C0001…, ίδιοι τίτλοι), συν ό,τι δεν υπάρχει στο Elasticsearch: τους μαθητές, τις εγγραφές τους, τους καθηγητές, τις δεξιότητες, τις πόλεις, και τα προαπαιτούμενα μεταξύ μαθημάτων. Το μοντέλο χωράει σε ένα σχέδιο:

Τα πέντε βέλη διαβάζονται σαν φράσεις: ένας μαθητής είναι εγγεγραμμένος σε ένα μάθημα· ένας καθηγητής διδάσκει ένα μάθημα· ένα μάθημα καλύπτει μια δεξιότητα· ένα μάθημα είναι προαπαιτούμενο ενός άλλου· ένας μαθητής ή καθηγητής κατοικεί σε μια πόλη. Συνολικά 872 κόμβοι και 3.712 σχέσεις.

Τα δεδομένα φτάνουν με τη μορφή κλασικών αρχείων CSV, μια γραμμή κεφαλίδας μετά μια γραμμή ανά στοιχείο:

text
cours.csv          id,titre,categorie,sujet,niveau,prix,duree_heures,date_publication,professeur_id
                   C0001,Docker expliqué simplement,DevOps,Docker,debutant,129,5,2024-08-14,P001

etudiants.csv      id,prenom,nom,ville,pays,inscription_le,interet
                   E0001,Nathan,Ben Ali,Sherbrooke,Canada,2023-05-15,DevOps

professeurs.csv    id,prenom,nom,ville,pays,specialite,annees_experience
                   P001,Karim,Caron,Gatineau,Canada,DevOps,23

competences.csv    id,nom
                   K01,Conteneurisation

villes.csv         nom,pays,latitude,longitude
                   Montréal,Canada,45.5019,-73.5674

inscriptions.csv   etudiant_id,cours_id,date,progression,note
                   E0001,C0028,2024-04-01,100,3

couvre.csv         cours_id,competence_id
                   C0001,K01

prerequis.csv      prerequis_id,cours_id
                   C0001,C0003

Κοίτα πώς μεταφράζεται αυτό σε γράφο. Τα πέντε πρώτα αρχεία γίνονται κόμβοι· κάθε γραμμή είναι κόμβος, κάθε στήλη ιδιότητα. Τα τρία τελευταία γίνονται σχέσεις· κάθε γραμμή συνδέει δύο κόμβους μέσω των αναγνωριστικών τους. Η γραμμή E0001,C0028,2024-04-01,100,3 του inscriptions.csv γίνεται το βέλος (Etudiant E0001)-[:INSCRIT_A {date, progression, note}]->(Cours C0028): ο Nathan Ben Ali είναι εγγεγραμμένος στο μάθημα C0028, το ολοκλήρωσε στο 100%, και του έβαλε 3. Σύγκρινε με την πρώτη κριτική του index avis: είναι ο ίδιος Nathan, το ίδιο μάθημα C0028, η ίδια βαθμολογία 3.

ΚόμβοςΙδιότητεςΠαράδειγμα
Coursid, titre, categorie, sujet, niveau, prix, duree_heures, date_publicationC0001, «Docker expliqué simplement», 129 $
Etudiantid, prenom, nom, interet, inscription_leE0001, Nathan Ben Ali, ενδιαφέρον DevOps
Professeurid, prenom, nom, specialite, annees_experienceP001, Karim Caron, DevOps, 23 έτη
Competenceid, nomK01, Conteneurisation
Villenom, pays, latitude, longitudeMontréal, Canada
ΣχέσηΑπό → προςΙδιότητεςΣημασία
INSCRIT_AEtudiant → Coursdate, progression, noteΟ μαθητής παρακολουθεί αυτό το μάθημα
ENSEIGNEProfesseur → CoursΑυτός ο καθηγητής διδάσκει αυτό το μάθημα (ένας μόνο ανά μάθημα)
COUVRECours → CompetenceΑυτό το μάθημα διδάσκει αυτή τη δεξιότητα
PREREQUIS_DECours → CoursΠρέπει να έχεις παρακολουθήσει το πρώτο πριν το δεύτερο
HABITEEtudiant ή Professeur → VilleΠού μένει το άτομο

Τα αναγνωριστικά, για να προσανατολίζεσαι

Όλα τα δεδομένα χρησιμοποιούν την ίδια σύμβαση: ένα γράμμα, μετά έναν αριθμό. Όταν βλέπεις ένα αναγνωριστικό, ξέρεις αμέσως για τι μιλάμε.

ΠρόθεμαΠράγμαΠαράδειγμαΠού το βρίσκεις
CΜάθημαC0001Elasticsearch cours, avis.cours_id, acces.cours_id, Neo4j Cours
AΚριτικήA00001Elasticsearch avis
LΓραμμή αρχείου καταγραφήςL000001Elasticsearch acces
EΜαθητήςE0001Neo4j Etudiant
PΚαθηγητήςP001cours.professeur.id στο Elasticsearch, Neo4j Professeur
KΔεξιότηταK01Neo4j Competence

Κράτα ένα μόνο νήμα για όλη τη συνέχεια: το μάθημα C0001, «Docker expliqué simplement», που διδάσκεται από τον P001, Karim Caron. Θα το ξαναβρείς στο Elasticsearch (ερώτημα E9), στο Neo4j (ερωτήματα N7 και N13), και θα χρησιμεύσει για να αποδείξεις ότι οι δύο κινητήρες περιέχουν όντως τα ίδια δεδομένα.

Πρώτα ερωτήματα: εμφάνιση των δεδομένων, από το απλούστερο στο πιο εντυπωσιακό

Οι μετρητές του etat αποδεικνύουν ότι τα δεδομένα είναι εκεί· τα παρακάτω ερωτήματα τα κάνουν ορατά. Είναι όλα μόνο για ανάγνωση: μπορείς να τα ξανατρέξεις όσες φορές θέλεις, τίποτα δεν θα τροποποιηθεί. Είναι ίδια σε Windows και σε Linux, όλα γίνονται στο πρόγραμμα περιήγησης.

Ο κανόνας αυτής της ενότητας: μόνο μία καινοτομία ανά ερώτημα. Ξεκινάμε με «δείξε μου τι υπάρχει», χωρίς καμία παράμετρο, και προσθέτουμε μια έννοια σε κάθε βήμα. Μετά από κάθε ερώτημα, μια σύντομη εξήγηση· όταν χρειάζεται εμβάθυνση, ένα διπλωμένο πλαίσιο «Για να καταλάβεις καλά». Επικόλλησε τα ερωτήματα ένα προς ένα, με τη σειρά, και διάβασε την απάντηση πριν προχωρήσεις στο επόμενο.

Elasticsearch, στο Kibana Dev Tools

Afficher les 15 requêtes Elasticsearch (E1 à E15) (Εμφάνιση 15 ερωτημάτων Elasticsearch, Ε1 έως Ε15)

Άνοιξε http://localhost:5601, μετά μενού ManagementDev Tools (ή «Outils de développement»). Το αριστερό πλαίσιο είναι ένας επεξεργαστής: επικόλλησε ένα ερώτημα, τοποθέτησε τον δρομέα πάνω του, μετά Ctrl+Enter ή το τρίγωνο ▶. Η απάντηση εμφανίζεται δεξιά, με τον κωδικό HTTP (200 - OK) κάτω.

Μια εικόνα να κρατήσεις στο μυαλό σου για όλη τη συνέχεια: το Elasticsearch είναι ένα μεγάλο ντουλάπι.

text
L'armoire  = Elasticsearch                      (Το ντουλάπι = Elasticsearch)
Un tiroir  = un index          (le tiroir « cours », le tiroir « avis », le tiroir « acces »)
                                                 (Ένα συρτάρι = ένα index)
Une fiche  = un document       (une fiche par cours, une fiche par avis, une fiche par ligne de journal)
                                                 (Μία κάρτα = ένα έγγραφο)

_cat/indices  = lire les étiquettes collées sur les tiroirs : nom, état, nombre de fiches, épaisseur
                                                 (διαβάζεις τις ετικέτες στα συρτάρια)
_search       = ouvrir un tiroir et lire les fiches qui sont dedans
                                                 (ανοίγεις ένα συρτάρι και διαβάζεις τις κάρτες)

Τα ερωτήματα Ε1 έως Ε4 κοιτάζουν τις ετικέτες των συρταριών. Από το Ε5 και μετά, ανοίγουμε τα συρτάρια. Μην τα μπερδεύεις ποτέ: είναι το λάθος νούμερο ένα των αρχάριων.

E1. Απαρίθμηση των συρταριών

text
GET _cat/indices

Τι ζητά το ερώτημα: «Elasticsearch, δείξε μου τη λίστα όλων των συρταριών σου.» Απάντηση στο μηχάνημα του μαθήματος:

text
green open avis                                            W9j_JrwJT4mdzpWcS5k7xg 1 0   609 0  74.6kb  74.6kb  74.6kb
green open .internal.alerts-security.alerts-default-000001 YJq3VGeRQKqD0fqJW6Fw3w 1 0     0 0    249b    249b    249b
green open acces                                           aii68fsfQXKyt5wqkE1mPA 1 0 12000 0   1.5mb   1.5mb   1.5mb
green open cours                                           pmq403ZgSZWeHJY9uNw1Qw 1 0   504 0 183.6kb 183.6kb 183.6kb

Τέσσερις γραμμές = τέσσερα συρτάρια. Τρία είναι δικά σου: avis, acces, cours. Το τέταρτο, αυτό του οποίου το όνομα ξεκινά με τελεία (.internal.alerts-security…), δεν είναι δικό σου: το Kibana το δημιούργησε μόνο του για εσωτερική χρήση (διαχείριση ειδοποιήσεων ασφαλείας). Είναι άδειο (0 κάρτες), δεν ενοχλεί, μην το διαγράψεις. Στη συνέχεια, θα το παραλείπουμε από την εμφάνιση.

Αυτό που είναι ενοχλητικό εδώ: υπάρχουν αριθμοί παντού και καμία κεφαλίδα στήλης. Είναι σαν πίνακας Excel χωρίς γραμμή κεφαλίδας. Το διορθώνουμε στο Ε2.

Για να διαβάσεις το ίδιο το ερώτημα, λέξη προς λέξη:

ΚομμάτιΤι σημαίνει
GET«Θέλω να διαβάσω κάτι.» Δεν τροποποιώ τίποτα, δεν διαγράφω τίποτα, κοιτάζω. Χωρίς κίνδυνο.
_cat«Απάντησέ μου σε πίνακα κειμένου», αναγνώσιμο από άνθρωπο, όχι σε JSON. Η κάτω παύλα στην αρχή δηλώνει μια εντολή του ίδιου του Elasticsearch, όχι όνομα index.
indices«… τον πίνακα των index.» Είναι ο αγγλικός πληθυντικός του index.
Για να καταλάβεις καλά: τι είναι ένα index, και ανάγνωση μιας γραμμής τιμή προς τιμή

Ένα index, είναι ένα συρτάρι: ένα μέρος όπου τακτοποιούμε κάρτες που μοιάζουν μεταξύ τους. Όλες οι κάρτες «μαθήματα» πάνε στο συρτάρι cours, όλες οι κάρτες «κριτικές» στο συρτάρι avis, και κάθε γραμμή του αρχείου καταγραφής του διακομιστή ιστού είναι μια κάρτα του συρταριού acces. Αν ξέρεις SQL, το index είναι αυτό που η SQL ονομάζει πίνακα:

ElasticsearchΣτην εικόναSQL
Index coursΤο συρτάρι «μαθήματα»Πίνακας cours
Έγγραφο JSONΜια κάρτα στο συρτάριΓραμμή
ΠεδίοΈνα κουτάκι στην κάρτα (τίτλος, τιμή…)Στήλη
_id του εγγράφουΟ αριθμός γραμμένος πάνω στην κάρταΠρωτεύον κλειδί

Τα τρία συρτάρια του εργαστηρίου:

text
Elasticsearch
├── index cours  → 504 documents     (un document = un cours du catalogue)
├── index avis   → 609 documents     (un document = un avis laissé par un étudiant)
└── index acces  → 12 000 documents  (un document = une ligne de journal du serveur web)

Δύο παγίδες λεξιλογίου. Ο ενικός είναι ένα index, και ο πληθυντικός είναι index (πολλά) στα ελληνικά (το Elasticsearch γράφει indices, τον αγγλικό πληθυντικό). Και δεν είναι «ένδειξη» με τη μαθηματική ή αστυνομική έννοια: είναι index όπως αυτό στο τέλος ενός βιβλίου, η λίστα που λέει σε ποια σελίδα βρίσκεται κάθε λέξη. Το Elasticsearch κάνει ακριβώς αυτό, σε πολύ μεγάλη κλίμακα.

Τώρα, ας πάρουμε μία μόνο γραμμή από την απάντηση Ε1 και ας τη διαβάσουμε τιμή προς τιμή, από αριστερά προς τα δεξιά:

text
green open avis W9j_JrwJT4mdzpWcS5k7xg 1 0 609 0 74.6kb 74.6kb 74.6kb
ΤιμήΤι σημαίνει
greenΤο συρτάρι είναι σε καλή κατάσταση. Πράσινο = όλα καλά.
openΤο συρτάρι είναι ανοιχτό: μπορείς να διαβάσεις και να γράψεις μέσα. (Το αντίθετο θα ήταν close.)
avisΤο όνομα του συρταριού.
W9j_JrwJT4mdzpWcS5k7xgΈνας τεχνικός σειριακός αριθμός, που δημιουργείται από το Elasticsearch. Δεν θα τον χρησιμοποιήσεις ποτέ.
1Το συρτάρι είναι σε ένα μόνο κομμάτι (ένα κύριο shard). Ένα μεγάλο index μπορεί να χωριστεί σε πολλά κομμάτια κατανεμημένα σε πολλά μηχανήματα· εδώ, όχι.
0Μηδέν αντίγραφα ασφαλείας (replica). Φυσιολογικό σε ένα εργαστήριο με ένα μόνο μηχάνημα: ένα αντίγραφο δεν θα είχε πού να πάει.
609Το συρτάρι περιέχει 609 κάρτες. Είναι η τιμή που κοιτάμε πρώτη.
0Μηδέν κάρτες σημειωμένες «προς πέταγμα» σε αναμονή καθαρισμού.
74.6kbΣυνολικός χώρος στον δίσκο.
74.6kbΧώρος του κύριου κομματιού μόνο (ίδιος: υπάρχει μόνο ένα κομμάτι και κανένα αντίγραφο).
74.6kbΜέγεθος των ίδιων των δεδομένων.

Τρεις φορές 74.6kb επειδή, με ένα μόνο κομμάτι και μηδέν αντίγραφα, οι τρεις τρόποι μέτρησης δίνουν το ίδιο αποτέλεσμα. Σε ένα πραγματικό cluster με αντίγραφα, το store.size θα ήταν μεγαλύτερο από το pri.store.size.

Συνοπτικά, τα τρία σου συρτάρια:

text
avis   →    609 fiches  →  74,6 Ko
acces  → 12 000 fiches  →   1,5 Mo
cours  →    504 fiches  → 183,6 Ko

E2. Η ίδια λίστα, με τις κεφαλίδες στηλών: ?v

text
GET _cat/indices?v
text
health status index                                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   avis                                            W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.6kb         74.6kb       74.6kb
green  open   .internal.alerts-security.alerts-default-000001 YJq3VGeRQKqD0fqJW6Fw3w   1   0          0            0       249b           249b         249b
green  open   acces                                           aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   cours                                           pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Ίδια απάντηση με το Ε1, συν μία γραμμή τίτλων από πάνω. Αυτό είναι το μόνο που κάνει το ?v. Τώρα δεν χρειάζεται πια να μαντεύεις: η στήλη docs.count είναι ο αριθμός των καρτών (609, 0, 12000, 504), η στήλη store.size είναι ο χώρος στον δίσκο.

Το ?v, τι σημαίνει ακριβώς;

  • Το ? λέει: «αυτό που ακολουθεί είναι επιλογές». Χωρίζει την εντολή (_cat/indices) από τις ρυθμίσεις της.
  • Το v είναι η επιλογή verbose, «φλύαρος»: «εμφάνισε επίσης τις κεφαλίδες των στηλών».

Άρα ?v = «απάντησέ μου με τις κεφαλίδες, ώστε να καταλάβω τι διαβάζω». Απόκτησε τη συνήθεια να το βάζεις πάντα στις εντολές _cat. Χωρίς ?v, έχεις αριθμούς· με ?v, έχεις πληροφορία.

Για να καταλάβεις καλά: κάθε κεφαλίδα στήλης, σε μία φράση
ΣτήληΜε απλά λόγια
healthΥγεία του συρταριού. green: όλα καλά. yellow: οι κάρτες είναι εκεί, αλλά κάποια αντίγραφα ασφαλείας λείπουν. red: κάποιες κάρτες είναι απρόσιτες.
statusopen: χρησιμοποιήσιμο. close: κλειστό, δεν μπορείς ούτε να διαβάσεις ούτε να γράψεις.
indexΤο όνομα του συρταριού.
uuidΤεχνικός σειριακός αριθμός. Δεν τον χρησιμοποιούμε ποτέ με το χέρι.
priΣε πόσα κομμάτια (primary shards) είναι χωρισμένο το συρτάρι. 1 στο εργαστήριο.
repΠόσα αντίγραφα ασφαλείας (replicas) κάθε κομματιού. 0 στο εργαστήριο.
docs.countΑριθμός καρτών. Η στήλη που κοιτάμε πρώτη.
docs.deletedΚάρτες σημειωμένες «προς πέταγμα» που δεν έχουν ακόμα καθαριστεί από τον δίσκο.
store.sizeΣυνολικός χώρος στον δίσκο.
pri.store.sizeΧώρος μόνο των κύριων κομματιών, χωρίς τα αντίγραφα.
dataset.sizeΜέγεθος των ίδιων των δεδομένων.

Γιατί όλα είναι green στο εργαστήριο: το rep έχει τιμή 0, άρα δεν υπάρχει κανένα αντίγραφο να γίνει, άρα κανένα αντίγραφο δεν μπορεί να λείπει. Σε ένα πραγματικό cluster, rep 1 με ένα μόνο μηχάνημα θα έδινε yellow, επειδή το αντίγραφο δεν θα είχε πού να πάει.

E3. Η ετικέτα ενός μόνο συρταριού

text
GET _cat/indices/cours?v
text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Τι ζητά το ερώτημα: «Δείξε μου την ετικέτα του συρταριού cours.» Μία μόνο καινοτομία: το όνομα του συρταριού προστέθηκε μετά το _cat/indices/. Μία μόνο γραμμή σε απάντηση, αυτή του cours, με τις 504 κάρτες του. Ό,τι ακολουθεί το τελευταίο / λειτουργεί ως φίλτρο.

Κάνε το ίδιο για τα άλλα δύο συρτάρια:

text
GET _cat/indices/avis?v
GET _cat/indices/acces?v

«Δείξε μου την ετικέτα του συρταριού των κριτικών»: 609 στο docs.count. «Δείξε μου την ετικέτα του συρταριού των αρχείων καταγραφής πρόσβασης»: 12000. Στο Dev Tools, όταν πολλά ερωτήματα είναι επικολλημένα στη σειρά, μόνο αυτό όπου βρίσκεται ο δρομέας στέλνεται: τοποθέτησέ τον στη σωστή γραμμή πριν το Ctrl+Enter.

E4. Η καθαρή σύνοψη των τριών συρταριών

text
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,docs.count,store.size
text
index health docs.count store.size
acces green       12000      1.5mb
avis  green         609     74.6kb
cours green         504    183.6kb

Τι ζητά το ερώτημα: «Δείξε μου έναν συνοπτικό πίνακα των συρταριών acces, avis και cours. Ταξινόμησέ τα κατά όνομα, και εμφάνισε μόνο το όνομά τους, την υγεία τους, τον αριθμό των καρτών τους και το μέγεθός τους.» Τέσσερις γραμμές, τέσσερις στήλες, ό,τι χρειάζεται και τίποτα παραπάνω. Είναι το ερώτημα που πρέπει να έχεις πρόχειρο για να επαληθεύεις το εργαστήριο με μια ματιά.

Φαίνεται περίπλοκο επειδή είναι μακρύ, αλλά είναι απλά το Ε3 με τρεις παραπάνω επιλογές. Ανάλυση, κομμάτι προς κομμάτι:

ΚομμάτιΜε απλά λόγια
GETΖητάω μια πληροφορία, δεν τροποποιώ τίποτα.
_cat/indicesΟι ετικέτες των συρταριών, σε ευανάγνωστο πίνακα.
/acces,avis,coursΜόνο αυτά τα τρία συρτάρια (ονόματα χωρισμένα με κόμματα, χωρίς κενό). Το εσωτερικό συρτάρι του Kibana εξαφανίζεται.
?vΜε τις κεφαλίδες στηλών.
&s=indexs όπως sort: ταξινομεί τις γραμμές κατά αλφαβητική σειρά της στήλης index.
&h=index,health,docs.count,store.sizeh όπως headers: εμφανίζει μόνο αυτές τις στήλες, με αυτή τη σειρά.

Ένας κανόνας και μόνο ένας για τις επιλογές: η πρώτη ξεκινά με ?, όλες οι επόμενες με &. Γι' αυτό διαβάζουμε ?v&s=…&h=….

Και οι τέσσερις επιλεγμένες στήλες:

ΣτήληΜε απλά λόγια
indexΤο όνομα του συρταριού.
healthΗ υγεία του: green όλα λειτουργούν· yellow οι κάρτες υπάρχουν αλλά αντίγραφα ασφαλείας λείπουν· red κάποιες κάρτες είναι μη διαθέσιμες.
docs.countΠόσες κάρτες περιέχει.
store.sizeΠόσο χώρο πιάνει στον δίσκο.

Με μία φράση: τα τρία σου συρτάρια είναι ανοιχτά, σε καλή υγεία, και περιέχουν ακριβώς τις αναμενόμενες κάρτες, 12.000, 609 και 504.

Για να καταλάβεις καλά: γιατί είναι το ερώτημα προς απομνημόνευση

Χωρίς h=, το Elasticsearch επιστρέφει έντεκα στήλες, οι περισσότερες από τις οποίες δεν σου λένε τίποτα καθημερινά. Χωρίς s=, η σειρά των γραμμών είναι τυχαία και αλλάζει από κλήση σε κλήση (κοίτα το Ε2: το avis ερχόταν πριν το acces). Ονομάζοντας τα τρία συρτάρια, αποκλείεις επίσης αυτό του Kibana. Το αποτέλεσμα χωράει σε τέσσερις γραμμές και συγκρίνεται με μια ματιά με τους αναμενόμενους αριθμούς. Είναι ακριβώς αυτό που κάνει η εντολή etat του kit στα παρασκήνια.

Χρήσιμη παραλλαγή αν θέλεις να δεις επίσης ότι τα συρτάρια είναι ανοιχτά: πρόσθεσε status στη λίστα h=:

text
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,status,docs.count,store.size
text
index health status docs.count store.size
acces green  open        12000      1.5mb
avis  green  open          609     74.6kb
cours green  open          504    183.6kb

Απολογισμός Ε1 έως Ε4: ακόμη δεν έχεις διαβάσει καμία κάρτα. Κοίταξες μόνο τις ετικέτες στα συρτάρια. Ξέρεις ότι υπάρχουν 504 μαθήματα, αλλά δεν έχεις δει ακόμα ούτε έναν τίτλο μαθήματος. Αυτό κάνουμε τώρα.

E5. Μέτρηση των καρτών ενός συρταριού

text
GET cours/_count
json
{
  "count": 504,
  "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }
}

Τι ζητά το ερώτημα: «Μέτρησε τις κάρτες του συρταριού cours.» Απάντηση: 504. Κοίτα καλά τη μορφή του ερωτήματος, είναι καινούργια: δεν μιλάμε πια στο ντουλάπι (_cat/…), μιλάμε σε ένα συρτάρι. Το όνομα του συρταριού έρχεται πρώτο (cours), μετά τι θέλουμε να του κάνουμε (_count), χωρισμένα με /. Όλα τα ερωτήματα που ακολουθούν θα έχουν αυτή τη μορφή: όνομα-συρταριού/_ενέργεια.

Άλλη αλλαγή: η απάντηση δεν είναι πια πίνακας κειμένου αλλά JSON, με αγκύλες και εισαγωγικά. Είναι η κανονική μορφή απαντήσεων του Elasticsearch· το _cat ήταν η εξαίρεση. Αγνόησε το κομμάτι _shards, λέει μόνο «το κομμάτι του συρταριού απάντησε, τίποτα δεν απέτυχε».

Δοκίμασε GET avis/_count (609) και GET acces/_count (12000). Σε SQL: SELECT COUNT(*) FROM cours.

E6. Άνοιγμα του συρταριού και ανάγνωση των καρτών

text
GET cours/_search

Τι ζητά το ερώτημα: «Άνοιξε το συρτάρι cours και δείξε μου τις κάρτες που είναι μέσα.» Είναι η πρώτη φορά που βλέπεις ένα πραγματικό μάθημα: τον τίτλο του, την τιμή του, τον καθηγητή του.

Η απάντηση είναι μακριά: είναι φυσιολογικό, περιέχει δέκα πλήρεις κάρτες. Το Elasticsearch σου δίνει μόνο τις 10 πρώτες, ακόμη κι αν το συρτάρι περιέχει 504· είναι μια προστασία, για να μην σου στείλει 504 κάρτες μεμιάς χωρίς να το έχεις ζητήσει. Κοίτα τη δομή αντί για το περιεχόμενο:

json
{
  "took": 1,
  "timed_out": false,
  "_shards": {  },
  "hits": {
    "total": { "value": 504, "relation": "eq" },
    "max_score": 1,
    "hits": [
      {
        "_index": "cours",
        "_id": "C0001",
        "_score": 1,
        "_source": {
          "id": "C0001",
          "titre": "Docker expliqué simplement",
          "categorie": "DevOps",
          "niveau": "debutant",
          "prix": 129,
          "tags": ["docker", "linux", "helm", "devops"],
          "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },

        }
      },
 9 autres documents
    ]
  }
}

Το _search σημαίνει «ψάξε»· χωρίς άλλη διευκρίνιση, σημαίνει «δώσε μου κάρτες, οποιεσδήποτε». Οι δύο αριθμοί προς εντοπισμό: "total": { "value": 504 } (υπάρχουν 504 κάρτες στο συρτάρι) και η λίστα hits που περιέχει μόνο 10 (αυτές που σου δείχνει). Κάθε κάρτα είναι στο _source, με όλα τα κουτάκια της: titre, prix, categorie

Κάνε το ίδιο με τα άλλα δύο συρτάρια: GET avis/_search («άνοιξε το συρτάρι avis, δείξε μου τις 10 πρώτες κριτικές») και GET acces/_search (τις 10 πρώτες γραμμές αρχείου καταγραφής). Σε SQL: SELECT * FROM cours LIMIT 10.

Η βασική διαφορά, να μην την μπερδέψεις ποτέ ξανά.

Το GET _cat/indices/cours?v κοιτάζει την ετικέτα του συρταριού: την κατάστασή του, τον αριθμό των καρτών του, το μέγεθός του. Μία γραμμή. Δεν βλέπεις κανένα μάθημα.

Το GET cours/_search ανοίγει το συρτάρι και διαβάζει τις κάρτες: τίτλους, τιμές, κατηγορίες, καθηγητές. Δέκα κάρτες. Βλέπεις τα ίδια τα μαθήματα.

Το πρώτο απαντά «υπάρχουν 504 μαθήματα». Το δεύτερο απαντά «ορίστε μαθήματα».

Για να καταλάβεις καλά: ανάγνωση μιας απάντησης αναζήτησης, κλειδί προς κλειδί
ΚλειδίΜε απλά λόγια
tookΟ χρόνος που χρειάστηκε το Elasticsearch, σε χιλιοστά του δευτερολέπτου (εδώ 1 ms).
hits.total.valueΟ συνολικός αριθμός καρτών που ταιριάζουν: 504. Ακόμη κι αν σου δείχνει μόνο 10, σου λέει πόσες υπάρχουν συνολικά.
hits.hitsΗ λίστα των καρτών που σου δείχνει: οι 10 πρώτες εξ ορισμού.
_indexΤο συρτάρι από όπου προέρχεται η κάρτα (cours).
_idΟ αριθμός γραμμένος πάνω στην κάρτα (C0001, C0002…).
_scoreΜια βαθμολογία συνάφειας. Είναι 1 παντού εδώ επειδή δεν αναζητήσαμε τίποτα συγκεκριμένο: όλες οι κάρτες αξίζουν το ίδιο.
_sourceΗ ίδια η κάρτα, όπως τακτοποιήθηκε στο συρτάρι, με όλα τα κουτάκια της.

Η λέξη hit σημαίνει «χτύπημα», όπως στη σκοποβολή: μια κάρτα «χτυπημένη» από το ερώτημα. Το πεδίο professeur είναι μια κάρτα μέσα στην κάρτα (ένα JSON μέσα στο JSON): ο καθηγητής είναι γραμμένος απευθείας πάνω στην κάρτα του μαθήματος, με το όνομά του και την πόλη του. Η SQL δεν το κάνει αυτό φυσικά, θα χρειαζόταν έναν δεύτερο πίνακα και ένα join.

E7. Εμφάνιση περισσότερων από δέκα

json
GET cours/_search
{
  "size": 20
}

Τι ζητά το ερώτημα: «Άνοιξε το συρτάρι cours και δείξε μου 20 κάρτες αντί για τις συνηθισμένες 10.»

Μία μόνο καινοτομία, αλλά σημαντική: το ερώτημα έχει τώρα δύο μέρη. Η πρώτη γραμμή (GET cours/_search) λέει τι να κάνει· το μπλοκ μέσα σε αγκύλες από κάτω, που ονομάζουμε σώμα (body), δίνει διευκρινίσεις. Εδώ η διευκρίνιση είναι "size": 20: «μέγεθος παρτίδας = 20». Αυτό είναι όλο. Το size σημαίνει απλά «πόσες κάρτες θέλεις να σου δείξουμε».

Επαλήθευσε στην απάντηση: το hits.total.value παραμένει 504 (το συρτάρι δεν άλλαξε), αλλά η λίστα hits περιέχει τώρα 20 κάρτες αντί για 10. Σε SQL: LIMIT 20.

Για να καταλάβεις καλά: ένα GET με σώμα;

Στην κλασική HTTP, το GET δεν έχει σώμα· το Elasticsearch το δέχεται παρόλα αυτά επειδή είναι πρακτικό στο Dev Tools. Αν ένα εργαλείο αρνηθεί, το POST cours/_search με το ίδιο σώμα κάνει ακριβώς το ίδιο πράγμα. Άλλο όριο που πρέπει να ξέρεις: το size δεν μπορεί να ξεπεράσει τα 10.000 μεμιάς (παράμετρος index.max_result_window)· για να διατρέξεις περισσότερα, κάνεις σελιδοποίηση. Για τα 504 μαθήματά μας, το "size": 504 θα λειτουργούσε, αλλά η απάντηση θα είχε χιλιάδες γραμμές: δεν είναι έτσι που διαβάζουμε δεδομένα, οι συγκεντρώσεις (E13) είναι φτιαγμένες γι' αυτό.

E8. Επιλογή των πεδίων προς εμφάνιση

json
GET cours/_search
{
  "size": 3,
  "_source": ["titre", "prix"]
}
json
"hits": [
  { "_id": "C0001", "_source": { "titre": "Docker expliqué simplement", "prix": 129 } },
  { "_id": "C0002", "_source": { "titre": "Docker avancé : industrialiser des conteneurs en production", "prix": 29 } },
  { "_id": "C0003", "_source": { "titre": "Docker : le guide complet", "prix": 19 } }
]

Τι ζητά το ερώτημα: «Δείξε μου 3 κάρτες, αλλά μόνο τα κουτάκια titre και prix κάθε κάρτας.» Καινοτομία: _source με μια λίστα πεδίων. Αντί για ολόκληρη την κάρτα, κρατάμε μόνο τα κουτάκια που μας ενδιαφέρουν. Η απάντηση γίνεται ευανάγνωστη με μια ματιά. Σε SQL: SELECT titre, prix FROM cours LIMIT 3. Θα χρησιμοποιήσουμε το _source σε σχεδόν όλα τα επόμενα ερωτήματα, ακριβώς για να κρατάμε σύντομες απαντήσεις.

E9. Ένα συγκεκριμένο έγγραφο, μέσω του αναγνωριστικού του

text
GET cours/_doc/C0001
json
{
  "_index": "cours",
  "_id": "C0001",
  "_version": 6,
  "found": true,
  "_source": {
    "id": "C0001",
    "titre": "Docker expliqué simplement",
    "categorie": "DevOps",
    "sujet": "Docker",
    "niveau": "debutant",
    "prix": 129,
    "note_moyenne": 4.4,
    "nb_avis": 327,
    "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },

  }
}

Καμία αναζήτηση εδώ: ζητάμε το έγγραφο του οποίου το αναγνωριστικό είναι C0001, και το Elasticsearch το επιστρέφει απευθείας ("found": true). Είναι η ταχύτερη πρόσβαση που υπάρχει. Σε SQL: SELECT * FROM cours WHERE id = 'C0001'. Κράτα αυτό το μάθημα, «Docker expliqué simplement», του Karim Caron: θα το ξαναβρούμε στο Neo4j σε λίγο, για να αποδείξουμε ότι οι δύο κινητήρες περιέχουν τα ίδια δεδομένα.

Για να καταλάβεις καλά: _id και id, δύο διαφορετικά πράγματα

Το _id (με κάτω παύλα) είναι το τεχνικό αναγνωριστικό του εγγράφου στο Elasticsearch· το id (χωρίς παύλα) είναι ένα απλό πεδίο μέσα στο _source. Το kit τα έκανε σκόπιμα ίδια (C0001 και στις δύο πλευρές) για να είναι ευανάγνωστο. Αυτό είναι επίσης που κάνει το importer επαναλήψιμο: η αποστολή δύο φορές ενός εγγράφου με το ίδιο _id αντικαθιστά το πρώτο αντί να δημιουργήσει ένα δεύτερο, εξ ου και το _version: 6 (το έγγραφο ξαναγράφτηκε έξι φορές στο μηχάνημα του μαθήματος, χωρίς ποτέ να διπλασιαστεί).

E10. Η πρώτη πραγματική αναζήτηση

json
GET cours/_search
{
  "query": { "match": { "titre": "kubernetes" } },
  "_source": ["titre"]
}
json
"hits": {
  "total": { "value": 7, "relation": "eq" },
  "hits": [
    { "_score": 5.0897474, "_source": { "titre": "Les bases de Kubernetes" } },
    { "_score": 5.0897474, "_source": { "titre": "Kubernetes en pratique" } },
    { "_score": 5.0897474, "_source": { "titre": "Kubernetes pour les débutants" } },

  ]
}

Καινοτομία: query, το κομμάτι του σώματος που λέει τι να αναζητήσουμε. Το match είναι η βασική αναζήτηση: «τα έγγραφα των οποίων το πεδίο titre περιέχει τη λέξη kubernetes». Επτά μαθήματα ταιριάζουν, και για πρώτη φορά το _score δεν είναι πια 1: είναι η συνάφεια, και τα αποτελέσματα ταξινομούνται από το πιο συναφές στο λιγότερο συναφές. Σε SQL, το κατά προσέγγιση ισοδύναμο θα ήταν WHERE titre LIKE '%kubernetes%', αλλά κοίτα καλά: πληκτρολογήσαμε kubernetes με πεζά και βρίσκουμε «Kubernetes» με κεφαλαίο. Το LIKE δεν θα το είχε κάνει αυτό.

Για να καταλάβεις καλά: τι κάνει το match

Τη στιγμή που ένα μάθημα ευρετηριάζεται, το Elasticsearch κόβει τον τίτλο του σε λέξεις, τις μετατρέπει σε πεζά, αφαιρεί τους τόνους και επαναφέρει κάθε λέξη στη ρίζα της (το «conteneurs» γίνεται «conteneur»): αυτή είναι η ανάλυση, εδώ γίνεται από τον αναλυτή french που ορίζεται στο mapping του kit. Όταν αναζητάς, το ερώτημά σου υφίσταται την ίδια επεξεργασία, μετά το Elasticsearch συγκρίνει λέξη προς λέξη. Αποτέλεσμα: κεφαλαία, τόνοι και πληθυντικοί δεν μετράνε πια. Το _score ανεβαίνει όταν η λέξη είναι σπάνια στο index και συχνή στο έγγραφο. Η ενότητα 3 αφιερώνει πολλά μαθήματα σε αυτό· εδώ, κράτα μόνο: το match αναζητά λέξεις, όχι ακολουθίες χαρακτήρων.

E11. Το ορθογραφικό λάθος που συγχωρείται

Πρώτα η απόδειξη ότι, εξ ορισμού, ένα πραγματικό λάθος δεν βρίσκει τίποτα. «kubrenetes» (δύο γράμματα αντεστραμμένα):

json
GET cours/_count
{
  "query": { "match": { "titre": "kubrenetes" } }
}
json
{ "count": 0,  }

Μετά το ίδιο ερώτημα με μια μόνο γραμμή παραπάνω, fuzziness, που επιτρέπει μία ή δύο διαφορές γραμμάτων:

json
GET cours/_search
{
  "query": { "match": { "titre": { "query": "kubrenetes", "fuzziness": "AUTO" } } },
  "size": 3,
  "_source": ["titre", "niveau", "prix"]
}
json
"total": { "value": 7, "relation": "eq" },
"hits": [
  { "_score": 4.4535294, "_source": { "titre": "Les bases de Kubernetes", "niveau": "debutant", "prix": 0 } },
  { "_score": 4.4535294, "_source": { "titre": "Kubernetes en pratique", "niveau": "debutant", "prix": 129 } },
  { "_score": 4.4535294, "_source": { "titre": "Kubernetes pour les débutants", "niveau": "debutant", "prix": 29 } }
]

Τα 7 μαθήματα Kubernetes επιστρέφουν, παρά το λάθος. Είναι η στιγμή που πάντα εκπλήσσει στην αίθουσα, και είναι ο λόγος ύπαρξης του Elasticsearch σε μια γραμμή αναζήτησης: ο χρήστης πληκτρολογεί λάθος, ο κινητήρας καταλαβαίνει παρόλα αυτά. Ένα LIKE '%kubrenetes%' σε SQL δεν θα είχε επιστρέψει ποτέ τίποτα. Παρατήρησε τη σύνταξη: όταν το match χρειάζεται επιλογές, η τιμή του πεδίου γίνεται αντικείμενο { "query": …, "fuzziness": … } αντί για απλή αλυσίδα χαρακτήρων.

Για να καταλάβεις καλά: AUTO

Το fuzziness μετρά τις τροποποιήσεις (ένα γράμμα προστέθηκε, αφαιρέθηκε, άλλαξε ή αντάλλαξε θέση με τον γείτονά του) που ανεχόμαστε μεταξύ της λέξης που πληκτρολογήθηκε και της λέξης που ευρετηριάστηκε. Το AUTO προσαρμόζει την ανοχή στο μήκος της λέξης: 0 λάθη για λέξη 1 ή 2 γραμμάτων, 1 λάθος για 3 έως 5 γράμματα, 2 λάθη πέρα από αυτό. Το «kubrenetes» έχει 10 γράμματα, άρα 2 λάθη επιτρέπονται· έχει μόνο ένα (η αντιμετάθεση reer): βρέθηκε. Το _score είναι λίγο χαμηλότερο απ' ό,τι στο Ε10 (4,45 έναντι 5,09): το Elasticsearch τιμωρεί ελαφρώς τις κατά προσέγγιση αντιστοιχίες, κάτι που κρατά τις ακριβείς αντιστοιχίες μπροστά.

E12. Ταξινόμηση: η κατάταξη των μαθημάτων

json
GET cours/_search
{
  "query": { "range": { "nb_avis": { "gte": 10 } } },
  "sort": [{ "note_moyenne": "desc" }, { "nb_avis": "desc" }],
  "size": 5,
  "_source": ["titre", "note_moyenne", "nb_avis", "categorie"]
}

Δύο καινοτομίες, που γίνονται κατανοητές στην ανάγνωση. range με gte (greater than or equal): μόνο τα μαθήματα που έχουν τουλάχιστον 10 κριτικές, για να αποκλείσουμε βαθμολογίες βασισμένες σε μία μόνο ψήφο. sort: ταξινόμηση κατά note_moyenne φθίνουσα, μετά κατά nb_avis φθίνουσα για να ξεχωρίσουν οι ισοπαλίες. Σε SQL: WHERE nb_avis >= 10 ORDER BY note_moyenne DESC, nb_avis DESC LIMIT 5. Όταν ταξινομούμε εμείς οι ίδιοι, το _score γίνεται null: η συνάφεια δεν χρησιμεύει πια, μετράει η δική μας σειρά.

E13. Μέτρηση ανά κατηγορία χωρίς να διαβάσουμε ούτε μία γραμμή

json
GET cours/_search
{
  "size": 0,
  "aggs": {
    "par_categorie": { "terms": { "field": "categorie" } }
  }
}
json
"aggregations": {
  "par_categorie": {
    "buckets": [
      { "key": "Cloud", "doc_count": 84 },
      { "key": "DevOps", "doc_count": 84 },
      { "key": "Données", "doc_count": 84 },
      { "key": "Développement web", "doc_count": 84 },
      { "key": "IA", "doc_count": 84 },
      { "key": "Sécurité", "doc_count": 84 }
    ]
  }
}

Καινοτομία: aggs (συγκεντρώσεις). Το terms στο categorie φτιάχνει ένα πακέτο (bucket) ανά διακριτή τιμή και μετρά τα έγγραφα μέσα. Το size: 0 λέει «μη μου επιστρέψεις κανένα έγγραφο, μόνο τους μετρητές»: η απάντηση είναι μικροσκοπική και στιγμιαία, ακόμη και σε εκατομμύρια γραμμές. Σε SQL: SELECT categorie, COUNT(*) FROM cours GROUP BY categorie. Αποτέλεσμα: έξι κατηγορίες με 84 μαθήματα η καθεμία. Το par_categorie είναι ένα όνομα που επιλέγεις εσύ· χρησιμεύει μόνο για να ξαναβρείς το αποτέλεσμα στην απάντηση.

Για να καταλάβεις καλά: γιατί είναι η βάση του Kibana

Κάθε γράφημα του Kibana, κάθε πίτα, κάθε ιστόγραμμα είναι μια συγκέντρωση σαν αυτή, εκτελεσμένη από το Elasticsearch και σχεδιασμένη από το Kibana. Όταν θα κατασκευάσεις ένα ταμπλό στην ενότητα 4, θα κάνεις κλικ σε «πεδίο: categorie, συγκέντρωση: terms» και το Kibana θα στείλει ακριβώς αυτό το ερώτημα. Το να ξέρεις να το διαβάζεις σημαίνει να ξέρεις τι κάνει το ταμπλό στα παρασκήνια.

E14. Μια συγκέντρωση μέσα σε μια συγκέντρωση

json
GET cours/_search
{
  "size": 0,
  "aggs": {
    "par_categorie": {
      "terms": { "field": "categorie" },
      "aggs": { "par_niveau": { "terms": { "field": "niveau" } } }
    }
  }
}

Ίδιο ερώτημα με το Ε13, με ένα δεύτερο aggs μέσα στο πρώτο. Μέσα σε κάθε πακέτο κατηγορίας, ξαναφτιάχνουμε πακέτα κατά niveau. Αποτέλεσμα: για το «Cloud», 45 αρχάριος, 28 προχωρημένος, 11 ενδιάμεσος· και ούτω καθεξής για τις έξι κατηγορίες, σε ένα μόνο πέρασμα. Σε SQL: GROUP BY categorie, niveau, αλλά η απάντηση είναι ήδη ιεραρχημένη, έτοιμη για ένα σωρευτικό γράφημα.

E15. Τα σφάλματα διακομιστή στις 12.000 προσβάσεις

json
GET acces/_search
{
  "size": 0,
  "query": { "range": { "statut": { "gte": 500 } } },
  "aggs": { "codes": { "terms": { "field": "statut" } } }
}
json
"hits": { "total": { "value": 178, "relation": "eq" } },
"aggregations": {
  "codes": {
    "buckets": [
      { "key": 500, "doc_count": 122 },
      { "key": 503, "doc_count": 56 }
    ]
  }
}

Τίποτα καινούργιο: συνδυάζουμε range (E12) και terms (E13) στο index acces. Από τις 12.000 γραμμές αρχείου καταγραφής, 178 έχουν κωδικό HTTP ≥ 500: 122 σφάλματα 500 και 56 503. Είναι η ερώτηση που θέτει κάθε πρωί ένας υπεύθυνος πλατφόρμας, και απαντάται σε ένα χιλιοστό του δευτερολέπτου. Σε SQL: SELECT statut, COUNT(*) FROM acces WHERE statut >= 500 GROUP BY statut. Είναι ακριβώς αυτό που θα βάλεις σε ένα ταμπλό στην ενότητα 4.

Neo4j, στο Neo4j Browser

Afficher les 16 requêtes Neo4j (N1 à N16) (Εμφάνιση 16 ερωτημάτων Neo4j, Ν1 έως Ν16)

Άνοιξε http://localhost:7474. Οθόνη σύνδεσης: URL neo4j://localhost:7687, χρήστης neo4j, κωδικός aiopsatlas2026. Πάνω, μια γραμμή επεξεργασίας που ξεκινά με neo4j$: επικόλλησε ένα μόνο ερώτημα, μετά Ctrl+Enter ή το τρίγωνο ▶. Το αποτέλεσμα εμφανίζεται σε ένα πλαίσιο από κάτω, με καρτέλες αριστερά: Graph (ένα σχέδιο, όταν το αποτέλεσμα περιέχει κόμβους), Table (γραμμές και στήλες) και Text.

N1. Εμφάνιση όλων

cypher
MATCH (n) RETURN n

Το αποτέλεσμα είναι ένα σύννεφο έγχρωμων φυσαλίδων, συνδεδεμένων με βέλη, που μπορείς να μετακινήσεις με το ποντίκι. Το Neo4j Browser εμφανίζει το πολύ 300 κόμβους ταυτόχρονα (ένα μήνυμα πάνω από το αποτέλεσμα το επισημαίνει)· οι 872 είναι όλοι εκεί, απλά σχεδιάζει μόνο ένα μέρος για να παραμείνει ευανάγνωστο.

Είναι το απλούστερο ερώτημα της Cypher, της γλώσσας του Neo4j. Το MATCH σημαίνει «βρες», το (n) δηλώνει έναν οποιονδήποτε κόμβο (οι παρενθέσεις σχεδιάζουν έναν κύκλο, σαν φυσαλίδα) στον οποίο δίνουμε το όνομα n, και το RETURN n σημαίνει «δείξε μου τον». Σε SQL, δεν υπάρχει ισοδύναμο: θα ήταν «SELECT * όλων των πινάκων ταυτόχρονα», κάτι που η SQL δεν ξέρει να κάνει.

Για να καταλάβεις καλά: κόμβοι, ετικέτες, σχέσεις

Ένας γράφος αποτελείται από δύο πράγματα: κόμβους (τις φυσαλίδες) και σχέσεις (τα βέλη μεταξύ φυσαλίδων). Κάθε κόμβος φέρει μια ετικέτα που λέει τι είναι (Cours, Etudiant…) και ιδιότητες (τίτλος, τιμή…). Κάθε σχέση φέρει έναν τύπο (INSCRIT_A, ENSEIGNE…) και μια κατεύθυνση.

Neo4jSQL
Ετικέτα CoursΠίνακας cours
ΚόμβοςΓραμμή
ΙδιότηταΣτήλη
Σχέση INSCRIT_AΠίνακας σύνδεσης inscriptions + joins

Ο γράφος του εργαστηρίου:

text
Neo4j
├── 504 nœuds Cours
├── 300 nœuds Etudiant
├──  30 nœuds Professeur
├──  22 nœuds Competence
└──  16 nœuds Ville
     = 872 nœuds, reliés par 3 712 relations

Τα 504 μαθήματα είναι τα ίδια με τα 504 έγγραφα του index Elasticsearch cours: είναι δύο κινητήρες που τακτοποιούν τα ίδια δεδομένα με δύο τρόπους, ο καθένας για να απαντά σε διαφορετικές ερωτήσεις.

N2. Περιορισμός του αριθμού αποτελεσμάτων

cypher
MATCH (n) RETURN n LIMIT 25

Μία μόνο καινοτομία: LIMIT 25, ίδια λέξη με SQL. Είκοσι πέντε φυσαλίδες αντί για τριακόσιες: επιτέλους βλέπουμε κάτι. Πέρασε το ποντίκι πάνω από μια φυσαλίδα: οι ιδιότητές της εμφανίζονται στο κάτω μέρος του πλαισίου. Κάνε διπλό κλικ πάνω της: οι γείτονές της ξεδιπλώνονται.

N3. Μέτρηση αντί για εμφάνιση

cypher
MATCH (n) RETURN count(n) AS total
text
total
872

Το count(n) μετρά αντί να σχεδιάζει· το AS total ονομάζει τη στήλη. Κανένα σχέδιο πια: το αποτέλεσμα περνά μόνο του σε προβολή Table, αφού ένας αριθμός δεν σχεδιάζεται. 872, ο αριθμός του etat. Σε SQL: SELECT COUNT(*).

N4. Ένα μόνο είδος κόμβου

cypher
MATCH (c:Cours) RETURN c LIMIT 5

Καινοτομία: :Cours μετά το όνομα μεταβλητής. Είναι η ετικέτα: «μόνο οι κόμβοι που είναι μαθήματα». Πέντε φυσαλίδες, όλες του ίδιου χρώματος. Σε SQL: SELECT * FROM cours LIMIT 5. Από συνήθεια, ονομάζουμε τη μεταβλητή με το αρχικό της ετικέτας (c για Cours, e για Etudiant), αλλά και το n θα λειτουργούσε.

N5. Στήλες αντί για φυσαλίδες

cypher
MATCH (c:Cours) RETURN c.titre, c.prix LIMIT 5
text
c.titre                                                        c.prix
"Docker expliqué simplement"                                   129.0
"Docker avancé : industrialiser des conteneurs en production"  29.0
"Docker : le guide complet"                                    19.0

Καινοτομία: c.titre, c.prix. Η τελεία δίνει πρόσβαση σε μια ιδιότητα του κόμβου. Όταν επιστρέφουμε ιδιότητες αντί για ολόκληρους κόμβους, το Neo4j Browser περνά σε προβολή Table. Σε SQL: SELECT titre, prix FROM cours LIMIT 5. Σύγκρινε με το E8: ίδιοι τίτλοι, ίδιες τιμές, ίδια σειρά. Για να μάθεις όλες τις ιδιότητες ενός μαθήματος: το MATCH (c:Cours) RETURN keys(c) LIMIT 1 απαντά prix, duree_heures, date_publication, sujet, niveau, categorie, id, titre.

N6. Μέτρηση ανά ετικέτα

cypher
MATCH (n) RETURN labels(n)[0] AS type, count(*) AS nombre ORDER BY nombre DESC
text
type          nombre
"Cours"       504
"Etudiant"    300
"Professeur"  30
"Competence"  22
"Ville"       16

Το labels(n) επιστρέφει τη λίστα ετικετών του κόμβου (ένας κόμβος μπορεί να έχει πολλές· εδώ μία μόνο, εξ ου και το [0], το πρώτο στοιχείο). Το count(*) ομαδοποιεί αυτόματα με ό,τι δεν είναι συγκέντρωση: δεν χρειάζεται να γράψεις GROUP BY, η Cypher το συνάγει. Το ORDER BY nombre DESC ταξινομεί. Το άθροισμα των πέντε γραμμών κάνει 872. Σε SQL, θα χρειάζονταν πέντε SELECT COUNT(*) και UNION.

N7. Ένας συγκεκριμένος κόμβος, μέσω του αναγνωριστικού του

cypher
MATCH (c:Cours {id: 'C0001'}) RETURN c.titre, c.prix, c.niveau
text
c.titre                       c.prix  c.niveau
"Docker expliqué simplement"  129.0   "debutant"

Καινοτομία: οι αγκύλες {id: 'C0001'} μέσα στο μοτίβο. Φιλτράρουν μια ιδιότητα, όπως ένα WHERE id = 'C0001'. Και είναι το ίδιο μάθημα με το E9 στο Elasticsearch: ίδιος τίτλος, ίδια τιμή. Να η απόδειξη ότι οι δύο κινητήρες περιέχουν πράγματι τα ίδια δεδομένα· αυτό που αλλάζει είναι τι μπορούμε να τους ζητήσουμε.

N8. Φιλτράρισμα με WHERE

cypher
MATCH (c:Cours) WHERE c.titre CONTAINS 'Kubernetes' RETURN c.titre ORDER BY c.titre
text
c.titre
"Kubernetes : de zéro à la production"
"Kubernetes : le guide complet"
"Kubernetes avancé : superviser des conteneurs en production"
"Kubernetes en pratique"
"Kubernetes expliqué simplement"
"Kubernetes pour les débutants"
"Les bases de Kubernetes"

Το WHERE γράφεται όπως σε SQL και το CONTAINS αναζητά μια ακολουθία χαρακτήρων. Επτά μαθήματα, τα ίδια με το Ε10. Αλλά δοκίμασε CONTAINS 'kubernetes' με πεζά: μηδέν αποτελέσματα. Και CONTAINS 'kubrenetes': μηδέν επίσης. Το Neo4j συγκρίνει χαρακτήρες, με ακρίβεια· δεν γνωρίζει ούτε πεζά/κεφαλαία, ούτε λέξεις, ούτε λάθη. Ακριβώς γι' αυτό το εργαστήριο έχει τους δύο κινητήρες: η γραμμή αναζήτησης είναι το Elasticsearch· οι σύνδεσμοι μεταξύ πραγμάτων είναι το Neo4j.

N9. Ταξινόμηση

cypher
MATCH (c:Cours) RETURN c.titre, c.prix ORDER BY c.prix DESC LIMIT 5
text
c.titre                                                  c.prix
"Helm par la pratique : superviser un pipeline CI/CD"    199.0
"Atelier GitHub Actions : vos applications"              199.0
"Docker pour les débutants"                              199.0

Τίποτα καινούργιο: ORDER BY … DESC LIMIT 5, όπως σε SQL. Τα πέντε πιο ακριβά μαθήματα, όλα στα 199 $.

N10. Μέτρηση ανά κατηγορία

cypher
MATCH (c:Cours) RETURN c.categorie AS categorie, count(*) AS nombre ORDER BY nombre DESC
text
categorie            nombre
"DevOps"             84
"Données"            84
"IA"                 84
"Développement web"  84
"Sécurité"           84
"Cloud"              84

Ακριβώς το αποτέλεσμα της συγκέντρωσης Ε13 στο Elasticsearch: έξι κατηγορίες των 84. Ίδιο δεδομένο, δύο κινητήρες, δύο συντάξεις. Μέχρι εδώ, το Neo4j δεν έκανε τίποτα που η SQL δεν ξέρει να κάνει. Αυτό αλλάζει στο επόμενο ερώτημα.

N11. Η πρώτη σχέση

cypher
MATCH (p:Professeur)-[r:ENSEIGNE]->(c:Cours) RETURN p, r, c LIMIT 30

Πέρνα σε προβολή Graph: φυσαλίδες «καθηγητής» συνδεδεμένες με βέλη ENSEIGNE σε φυσαλίδες «μάθημα». Πιάσε έναν καθηγητή με το ποντίκι, βλέπεις όλα τα μαθήματά του να ακολουθούν.

Καινοτομία: το βέλος. Το (p:Professeur)-[r:ENSEIGNE]->(c:Cours) διαβάζεται κυριολεκτικά «ένας καθηγητής, που διδάσκει, ένα μάθημα». Οι παρενθέσεις είναι κόμβοι, οι αγκύλες είναι η σχέση, το -> δίνει την κατεύθυνση. Είναι ένα σχέδιο ASCII αυτού που ψάχνουμε, και το Neo4j βρίσκει όλα τα σημεία του γράφου που μοιάζουν με αυτό το σχέδιο. Σε SQL, θα ήταν SELECT * FROM professeurs JOIN cours ON cours.professeur_id = professeurs.id, και δεν θα σχεδιαζόταν.

Για να καταλάβεις καλά: γιατί οι σχέσεις αλλάζουν τα πάντα

Σε SQL, μια σχέση μεταξύ δύο γραμμών δεν υπάρχει πραγματικά: επανυπολογίζεται σε κάθε ερώτημα με ένα join, που συγκρίνει αναγνωριστικά. Με δέκα joins αλυσιδωτά, γίνεται αργό και δυσανάγνωστο. Στο Neo4j, η σχέση αποθηκεύεται ως φυσικό βέλος μεταξύ δύο κόμβων: η διάτρεξη ενός βέλους κοστίζει το ίδιο ανεξάρτητα από τον αριθμό κόμβων στη βάση. Αυτό είναι που κάνει δυνατά τα ερωτήματα N14 έως N16, που αλυσιδώνουν πολλά άλματα χωρίς κόπο.

N12. Μέτρηση σχέσεων ανά τύπο

cypher
MATCH ()-[r]->() RETURN type(r) AS relation, count(*) AS nombre ORDER BY nombre DESC
text
relation        nombre
"INSCRIT_A"     1654
"COUVRE"        994
"ENSEIGNE"      504
"HABITE"        330
"PREREQUIS_DE"  230

Το () είναι ένας οποιοσδήποτε κόμβος του οποίου δεν κρατάμε καν το όνομα· το [r] μια σχέση οποιουδήποτε τύπου· το type(r) ο τύπος της. Πέντε τύποι σχέσεων, 3.712 συνολικά: ο αριθμός που εμφανίζει το πλαίσιο Database information του Neo4j Browser. Διαβάζουμε το μοντέλο του εργαστηρίου σε μία γραμμή: μαθητές εγγεγραμμένοι σε μαθήματα, μαθήματα που καλύπτουν δεξιότητες, καθηγητές που διδάσκουν μαθήματα, άνθρωποι που κατοικούν σε πόλεις, και μαθήματα προαπαιτούμενα άλλων μαθημάτων. Το ENSEIGNE στο 504 λέει ότι υπάρχει ακριβώς μία σχέση ανά μάθημα: κάθε μάθημα έχει έναν και μοναδικό καθηγητή.

N13. Παρακολούθηση μιας σχέσης από έναν συγκεκριμένο κόμβο

cypher
MATCH (p:Professeur {id: 'P001'})-[:ENSEIGNE]->(c:Cours)
RETURN p.prenom + ' ' + p.nom AS professeur, count(c) AS nb_cours
text
professeur     nb_cours
"Karim Caron"  11

Συνδυάζουμε το N7 (το φίλτρο {id: 'P001'}) και το N11 (το βέλος). Το P001 είναι ο καθηγητής του «Docker expliqué simplement» που είδαμε στο E9 και N7· διδάσκει 11 μαθήματα. Το + κολλάει αλυσίδες χαρακτήρων, όπως σε SQL με το ||. Παρατήρησε το [:ENSEIGNE] χωρίς όνομα μεταβλητής: όταν δεν χρειαζόμαστε τη σχέση στο RETURN, δεν την ονομάζουμε.

N14. Η διαδρομή εκπαίδευσης

cypher
MATCH (debut:Cours {id: 'C0111'}), (fin:Cours {id: 'C0110'}),
      chemin = shortestPath((debut)-[:PREREQUIS_DE*]-(fin))
RETURN length(chemin) AS sauts, [n IN nodes(chemin) | n.titre] AS parcours
text
sauts  parcours
5      ["Neo4j : de zéro à la production", "Neo4j en pratique", "Neo4j par la pratique : interroger des journaux applicatifs", "Neo4j avancé : modéliser des journaux applicatifs", "Neo4j : le guide complet", "Maîtriser Neo4j"]

Πρώτο ερώτημα που δεν μοιάζει πια με τίποτα σε SQL. Το [:PREREQUIS_DE*] με τον αστερίσκο σημαίνει «ακολουθώντας αυτή τη σχέση όσες φορές χρειάζεται». Το shortestPath ζητά τη συντομότερη από τις δυνατές διαδρομές μεταξύ των δύο μαθημάτων. Η απάντηση: 5 άλματα, και η λίστα των έξι τίτλων προς ακολούθηση, με τη σειρά, για να πάμε από το πρώτο στο τελευταίο μάθημα της σειράς Neo4j. Είναι ένα πλάνο εκπαίδευσης που υπολογίζεται εν κινήσει. Σε SQL, θα χρειαζόταν ένα αναδρομικό ερώτημα δεκάδων γραμμών, και θα ήταν αργό.

Για να καταλάβεις καλά: η γραμμή RETURN

Το nodes(chemin) δίνει τη λίστα των διατρεχθέντων κόμβων. Το [n IN nodes(chemin) | n.titre] διαβάζεται «για κάθε κόμβο n αυτής της λίστας, κράτα τον τίτλο του»: είναι ένας συνοπτικός τρόπος μετατροπής μιας λίστας κόμβων σε λίστα τίτλων. Το length(chemin) μετρά τις διατρεχθείσες σχέσεις (5 σχέσεις για 6 κόμβους). Η ενότητα 6 αναλύει αυτές τις συναρτήσεις· εδώ το σημαντικό είναι το αποτέλεσμα: το Neo4j βρήκε ένα δρομολόγιο μέσα στον γράφο.

N15. «Οι μαθητές που παρακολούθησαν αυτό το μάθημα παρακολούθησαν επίσης…»

cypher
MATCH (x:Cours {id: 'C0213'})<-[:INSCRIT_A]-(e:Etudiant)-[:INSCRIT_A]->(autre:Cours)
WHERE autre <> x
RETURN autre.titre AS recommandation, autre.categorie AS categorie,
       count(DISTINCT e) AS etudiants_communs
ORDER BY etudiants_communs DESC, recommandation
LIMIT 5
text
recommandation                                                  categorie  etudiants_communs
"Agents IA par la pratique : évaluer un pipeline de prédiction"  "IA"       3
"Atelier NLP : des agents autonomes"                             "IA"       2
"Deep learning par la pratique : orchestrer un assistant …"      "IA"       2

Ο κινητήρας συστάσεων ενός εμπορικού ιστότοπου, σε πέντε γραμμές. Διάβασε το μοτίβο από αριστερά προς τα δεξιά: ξεκινάμε από το μάθημα x, ανεβαίνουμε το βέλος INSCRIT_A (δείχνει προς το x, εξ ου και το <-) μέχρι τους μαθητές e που το παρακολουθούν, μετά κατεβαίνουμε ένα άλλο βέλος INSCRIT_A προς τα άλλα μαθήματα autre αυτών των ίδιων μαθητών. Το WHERE autre <> x αποκλείει το ίδιο το αρχικό μάθημα. Το count(DISTINCT e) μετρά τους κοινούς μαθητές, χωρίς διπλότυπο. Τα μαθήματα στην κορυφή είναι αυτά που οι μαθητές του C0213 παρακολουθούν πιο συχνά παράλληλα: αυτές είναι οι συστάσεις. Σε SQL: δύο joins στον πίνακα εγγραφών, ένα GROUP BY, και ένα ερώτημα που το ξαναδιαβάζεις τρεις φορές πριν το καταλάβεις.

N16. Το ίδιο αποτέλεσμα, σχεδιασμένο

cypher
MATCH chemin = (x:Cours {id: 'C0213'})<-[:INSCRIT_A]-(:Etudiant)-[:INSCRIT_A]->(autre:Cours)
WHERE autre <> x
RETURN chemin LIMIT 50

Πέρνα σε προβολή Graph: το αρχικό μάθημα στο κέντρο, οι μαθητές του γύρω, και τα μαθήματα που μοιράζονται στην περιφέρεια. Είναι το ερώτημα N15 χωρίς τη μέτρηση: επιστρέφουμε τις διαδρομές ολόκληρες αντί για στήλες, και το Neo4j Browser τις σχεδιάζει. Είναι η εικόνα που δείχνεις όταν κάποιος ρωτά «σε τι χρησιμεύει μια βάση γράφου».

Το μήνυμα προς μετάδοση. Δεκαπέντε ερωτήματα Elasticsearch, δεκαέξι ερωτήματα Cypher, και είδαμε, με τη σειρά: απαρίθμηση, μέτρηση, εμφάνιση, φιλτράρισμα, ταξινόμηση, ομαδοποίηση, ό,τι κάνει επίσης η SQL· μετά συγχώρεση ενός ορθογραφικού λάθους, συγκέντρωση 12.000 γραμμών σε ένα χιλιοστό του δευτερολέπτου, υπολογισμός συντομότερης διαδρομής και παραγωγή συστάσεων, ό,τι η SQL κάνει άσχημα ή καθόλου. Το ίδιο μάθημα «Docker expliqué simplement» εμφανίστηκε και στους δύο κινητήρες: ίδια δεδομένα, διαφορετικές ερωτήσεις. Ακριβώς γι' αυτό υπάρχει αυτό το εργαστήριο, και όλη η υπόλοιπη ύλη του μαθήματος αναλύει πώς λειτουργεί καθένα από αυτά τα ερωτήματα.

Défi bonus (Bonus πρόκληση, προαιρετικό)

Ξανακάνε το βήμα 7 του παραρτήματός σου με docker compose stop neo4j: τι γίνεται με τη γραμμή Neo4j répond — nœuds : 872 στο etat; Τι λέει το Neo4j Browser, ήδη συνδεδεμένο, όταν ξανατρέχεις το ερώτημα N3 (MATCH (n) RETURN count(n)); Ποια είναι η τελευταία γραμμή του journal neo4j; Ξανάτρεξε με docker compose start neo4j, επαλήθευσε ότι οι 872 κόμβοι είναι ακόμα εκεί χωρίς να ξαναφορτώσεις τίποτα, και σημείωσε ποια από τις δύο υπηρεσίες επανεκκινεί πιο γρήγορα.

Παράρτημα Α — Λεπτομερές βήμα προς βήμα σε Windows (PowerShell)

Afficher le pas à pas Windows (A.0 à A.11) (Εμφάνιση βήμα προς βήμα Windows, A.0 έως A.11)

Όλες οι εντολές αυτού του παραρτήματος πληκτρολογούνται σε PowerShell (Windows Terminal, ή PowerShell 7), με .\labo.ps1 …. Οι έξοδοι που αναπαράγονται είναι αυτές του μηχανήματος του μαθήματος, σε Windows 11 και Docker Desktop.

A.0 — Πριν ξεκινήσεις

  • Να έχεις διαβάσει τα τέσσερα μαθήματα της ενότητας: 01, 02, 03 και 04.
  • Docker Desktop εκκινημένο (πράσινο εικονίδιο), το kit κλωνοποιημένο, ένα τερματικό PowerShell ανοιχτό μέσα στον φάκελο του kit (αυτόν που περιέχει το docker-compose.yml και το labo.ps1).
  • Αν το PowerShell αρνείται να εκτελέσει το .\labo.ps1 («η εκτέλεση σεναρίων είναι απενεργοποιημένη σε αυτό το σύστημα»): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, απάντησε O, ξανατρέξε.
  • Το εργαστήριο μπορεί να είναι εκκινημένο ή όχι: η πρακτική ξεκινά με τα προαπαιτούμενα. Στο τέλος, το .\labo.ps1 etat πρέπει να εμφανίζει (healthy) παντού, "status":"green", acces 12000 avis 609 cours 504 και nœuds : 872.
  • Όλα τα ερωτήματα αυτής της πρακτικής: elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Κανένα δεν γράφει στο cluster.

A.1 — Εκτέλεση της λίστας ελέγχου προαπαιτούμενων

Πριν ανάψεις οτιδήποτε, κάνε το σενάριο να μιλήσει.

powershell
.\labo.ps1 prerequis

Σημείο ελέγχου: μόνο πράσινα τικ και η τελική φράση.

text
== Prérequis ==
  ✔ docker : Docker version 29.3.1, build c2be9cc
  ✔ le démon Docker répond
  ✔ docker compose : 5.1.1
  ✔ mémoire disponible pour Docker : 31 Go
  ✔ processeurs : 20
  ✔ port 9200 : utilisé par le labo lui-même
  ✔ port 5601 : utilisé par le labo lui-même
  ✔ port 7474 : utilisé par le labo lui-même
  ✔ port 7687 : utilisé par le labo lui-même

Tout est prêt. Lancez : .\labo.ps1 demarrer

Στο δικό σου μηχάνημα, εκδόσεις και μνήμη διαφέρουν, και αν το εργαστήριο δεν έχει τρέξει ποτέ οι τέσσερις τελευταίες γραμμές λένε port 9200 : libre. Και οι δύο αναγνώσεις είναι καλές.

Αν βλέπεις κάτι άλλο: ένας σταυρός περιέχει τη λύση του μέσα στη φράση (Docker Desktop όχι εκκινημένο, μνήμη κάτω από 4 GB, θύρα κατειλημμένη)· διόρθωσε, ξανατρέξε, προχώρα μόνο με την πράσινη γραμμή. Για τη μνήμη: Docker Desktop → SettingsResources, ή το αρχείο %UserProfile%\.wslconfig αν το Docker Desktop χρησιμοποιεί WSL 2.

A.2 — Εκκίνηση και ανάγνωση των τριών μπλοκ

Ξεκίνα την εκκίνηση και, αυτή τη φορά, διάβασε ό,τι κυλά αντί να περιμένεις το τέλος.

powershell
.\labo.ps1 demarrer

Σημείο ελέγχου: τρία μπλοκ == … == μετά τρία prêt.

text
== Téléchargement des images (long la première fois : ~6 Go, ~12 Go avec OpenSearch) ==

== Démarrage ==

== Attente que chaque service soit prêt ==
  elasticsearch            prêt (0 s)
  kibana                   prêt (0 s)
  neo4j                   ..... prêt (15 s)

Le labo est prêt.
  Kibana                 http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
  Elasticsearch          http://localhost:9200
  Neo4j Browser          http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)

Étape suivante : .\labo.ps1 importer   puis   .\labo.ps1 charger-graphe

Το πρώτο μπλοκ είναι κενό αν οι εικόνες υπάρχουν ήδη· το δεύτερο περιέχει τις γραμμές του Compose (Container labo-elasticsearch Started, ή Running αν ήδη έτρεχε)· το τρίτο προσθέτει μια τελεία κάθε τρία δευτερόλεπτα μέχρι το prêt (… s). Κρύο εργαστήριο: ένα με δύο λεπτά· εργαστήριο ήδη σε λειτουργία: prêt (0 s).

Αν βλέπεις κάτι άλλο: unhealthy, exited ή délai dépassé.\labo.ps1 journal <service> και ο κατάλογος του μαθήματος 04· πιο συχνή αιτία σε Windows: η μνήμη που έχει διατεθεί στο Docker Desktop (Exited (137)).

A.3 — Καταγραφή των τεσσάρων αριθμών του etat

Θα πληκτρολογήσεις το etat πολλές φορές· μάθε πρώτα να εξάγεις τους τέσσερις αριθμούς που μετράνε.

powershell
.\labo.ps1 etat

Σημείο ελέγχου: πραγματική έξοδος από το μηχάνημα του μαθήματος, όπου το προφίλ OpenSearch της ενότητας 5 είναι ενεργό. Στο δικό σου, οι δύο γραμμές labo-opensearch… και ✔ OpenSearch αντικαθίστανται από — OpenSearch non démarré (profil optionnel : .\labo.ps1 demarrer opensearch).

text
== Conteneurs ==
NAME                         STATUS                    PORTS
labo-elasticsearch           Up 10 hours (healthy)     0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up 10 hours (healthy)     0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 37 seconds (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)     0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours               0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Σημείωσε τους τέσσερις αναμενόμενους αριθμούς: 12000, 609, 504, 872. Σε ένα ολοκαίνουργιο εργαστήριο διαβάζεις index : aucun index du labo και nœuds : 0: φυσιολογικό, το βήμα A.4 τα συμπληρώνει.

Αν βλέπεις κάτι άλλο: ✘ Kibana ne répond pas encore μέσα στο λεπτό που ακολουθεί το demarrer → το Kibana τελειώνει να δημιουργεί τα εσωτερικά του index· ξαναπληκτρολόγησε etat τριάντα δευτερόλεπτα αργότερα.

A.4 — Φόρτωση των δεδομένων, μετά επαναφόρτωση για να αποδειχθεί ότι τίποτα δεν κινείται

Φόρτωσε τα index μετά τον γράφο, και ξανάτρεξε τις δύο εντολές μια δεύτερη φορά: οι μετρητές δεν πρέπει να αλλάξουν ούτε μία μονάδα.

powershell
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer
.\labo.ps1 charger-graphe

Σημείο ελέγχου: στο δεύτερο πέρασμα, το importer δηλώνει ότι τα index υπάρχουν ήδη και επιστρέφει τους ίδιους μετρητές:

text
== Import dans elasticsearch ==
— index cours existe déjà — conservé
  ✔ données cours chargées
— index avis existe déjà — conservé
  ✔ données avis chargées
— index acces existe déjà — conservé
  ✔ données acces chargées

index docs.count store.size
acces      12000      1.5mb
avis         609     74.8kb
cours        504    183.6kb

Import terminé. Attendu : cours = 504, avis = 609, acces = 12000.

και το charger-graphe δίνει τον ίδιο απολογισμό με την πρώτη φορά:

text
== Chargement du graphe Neo4j ==
  ✔ contraintes et index en place
etiquette, noeuds
"Competence", 22
"Cours", 504
"Etudiant", 300
"Professeur", 30
"Ville", 16

Graphe chargé. Attendu : Competence 22, Cours 504, Etudiant 300, Professeur 30, Ville 16.

Τα έγγραφα φέρουν το δικό τους _id (C0001, A00001…), άρα ένα δεύτερο _bulk αντικαθιστά κάθε έγγραφο αντί να το προσθέτει· στο Neo4j, όλα γίνονται με MERGE. Μόνο το store.size μπορεί να κινηθεί κατά μερικά kilobyte (η Lucene κρατά για λίγο τις παλιές εκδόσεις)· τα docs.count, ποτέ.

Αν βλέπεις κάτι άλλο: ένα docs.count διαφορετικό από 504 / 609 / 12000 → κάποιος έγραψε σε αυτά τα index· .\labo.ps1 reinitialiser μετά demarrer, importer, charger-graphe σου επαναφέρει την κατάσταση αναφοράς.

A.5 — Επαλήθευση του Elasticsearch από το Dev Tools

Το σενάριο λέει ότι όλα είναι καλά· επαλήθευσέ το χωρίς αυτό. Άνοιξε http://localhost:5601, μενού ManagementOutils de développement, και στείλε αυτά τα ερωτήματα ένα προς ένα (Ctrl + Enter ή το κουμπί ▶ «Cliquer pour envoyer la requête»).

text
GET _cluster/health

Σημείο ελέγχου: "status": "green", "unassigned_shards": 0, και 200 - OK κάτω δεξιά του πλαισίου απάντησης.

json
{
  "cluster_name": "labo",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 53,
  "active_shards": 53,

  "unassigned_shards": 0,

  "active_shards_percent_as_number": 100
}
text
GET _cat/indices/cours,avis,acces?v&s=index

Σημείο ελέγχου: τρεις γραμμές green, pri 1, rep 0, και οι τρεις μετρητές στο docs.count.

text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   acces aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   avis  W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.8kb         74.8kb       74.8kb
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Τελείωσε με GET cours/_count, GET avis/_count, GET acces/_count: "count": 504, 609, 12000. Έχεις τους ίδιους αριθμούς από δύο ανεξάρτητους δρόμους: το σενάριο (curl μέσα στο κοντέινερ) και το Dev Tools (μέσω του Kibana). Αν κάποια μέρα διαφέρουν, φταίει ο δρόμος, όχι τα δεδομένα.

Αν βλέπεις κάτι άλλο: "status": "yellow" → ένα index έχει μη κατανεμημένα replica, αδύνατο με τα mappings του kit (number_of_replicas: 0GET _cat/indices?v&health=yellow δείχνει τον ένοχο, συνήθως ένα index δημιουργημένο με το χέρι.

A.6 — Μέτρηση του γράφου στο Neo4j Browser

Ίδια άσκηση για το Neo4j. Άνοιξε http://localhost:7474, συνδέσου (neo4j / aiopsatlas2026, URL localhost:7687), πληκτρολόγησε στον επεξεργαστή neo4j$ και κάνε κλικ Run:

cypher
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label

Σημείο ελέγχου: ένα πλαίσιο με δύο προβολές, Table και Raw (όχι Graph: το ερώτημα επιστρέφει αριθμούς, όχι κόμβους), δύο στήλες label και count(*), πέντε ταξινομημένες γραμμές, και κάτω δεξιά Started streaming 5 records after … ms and completed after … ms.

text
label          count(*)
"Competence"   22
"Cours"        504
"Etudiant"     300
"Professeur"   30
"Ville"        16

22 + 504 + 300 + 30 + 16 = 872, ο αριθμός του etat. Το πλαίσιο Database information (εικονίδιο Database overview, το πρώτο της πλαϊνής μπάρας) εμφανίζει Nodes (872) και Relationships (3,712).

Αν βλέπεις κάτι άλλο: μια έκτη άγνωστη ετικέτα → κόμβοι δημιουργημένοι εκτός kit (η ενότητα 6 θα σου μάθει να τους διαγράφεις σωστά)· Nodes (0) → παρέλειψες το charger-graphe, επιστροφή στο βήμα A.4.

A.7 — Σκόπιμη βλάβη του Kibana και παρατήρηση της βλάβης

Ξέρεις πώς μοιάζει ένα υγιές εργαστήριο· πρόκαλεσε μια βλάβη της οποίας γνωρίζεις την αιτία για να μάθεις να τη διαβάζεις. Σταμάτησε μόνο το Kibana, με το Compose (όχι το arreter, που θα σταματούσε τα πάντα):

powershell
docker compose stop kibana
text
 Container labo-kibana Stopping
 Container labo-kibana Stopped

Μετά οι τρεις κινήσεις του μαθήματος 04, με τη σειρά: etat, το πρόγραμμα περιήγησης, journal.

Σημείο ελέγχου 1, .\labo.ps1 etat : η γραμμή labo-kibana εξαφανίστηκε από το μπλοκ == Conteneurs == (το Compose εμφανίζει εξ ορισμού μόνο τα κοντέινερ που τρέχουν) και το μπλοκ == Services == σημειώνει έναν σταυρό:

text
== Conteneurs ==
NAME                         STATUS                   PORTS
labo-elasticsearch           Up 10 hours (healthy)    0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-neo4j                   Up 3 minutes (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)    0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours              0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✘ Kibana ne répond pas encore
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Για να δεις παρόλα αυτά το σταματημένο κοντέινερ: docker compose ps -a εμφανίζει labo-kibana Exited (0) 31 seconds ago. Το 0 λέει «καθαρή διακοπή, ζητηθείσα»· ένα 137 θα έλεγε «σκοτώθηκε, μνήμη».

Σημείο ελέγχου 2, το πρόγραμμα περιήγησης: ανανέωσε το http://localhost:5601. Όχι «Kibana server is not ready yet» (αυτή η φράση προέρχεται από το Kibana, όμως δεν υπάρχει πια Kibana για να την πει) αλλά το σφάλμα σύνδεσης του ίδιου του προγράμματος περιήγησης: στο Chrome ή στο Edge, «Ce site est inaccessible», κωδικός ERR_CONNECTION_REFUSED. Κανείς δεν ακούει στη θύρα 5601. Να θυμάσαι: σελίδα Kibana που ζητά συγγνώμη = το Kibana τρέχει αλλά περιμένει το Elasticsearch· σφάλμα του προγράμματος περιήγησης = το Kibana δεν τρέχει.

Σημείο ελέγχου 3, .\labo.ps1 journal kibana : οι εκατό τελευταίες γραμμές τελειώνουν με μια καθαρή διακοπή, χρονοσημασμένη τη στιγμή που πληκτρολόγησες το stop:

text
labo-kibana  | [2026-09-09T23:44:06.893+00:00][INFO ][root] SIGTERM received - initiating shutdown
labo-kibana  | [2026-09-09T23:44:06.894+00:00][INFO ][root] Kibana is shutting down
labo-kibana  | [2026-09-09T23:44:06.902+00:00][INFO ][plugins-system.standard] Stopping all plugins.

labo-kibana  | [2026-09-09T23:44:07.265+00:00][INFO ][plugins-system.standard] All plugins stopped.

Το SIGTERM received υπογράφει μια ζητηθείσα διακοπή (από σένα, από το docker compose stop, από επανεκκίνηση του Docker Desktop). Καμία γραμμή ERROR ούτε FATAL: τίποτα να επιδιορθωθεί, απλά χρειάζεται επανεκκίνηση. Οι δεκάδες γραμμές at OperatorSubscriber… ανάμεσα στις δύο είναι μια στοίβα κλήσεων που εκπέμπεται από ένα plugin κατά τη διακοπή: θόρυβος.

Αν βλέπεις κάτι άλλο: no configuration file provided: not found → δεν είσαι στον φάκελο του kit· no such service: kibana → πληκτρολόγησες το όνομα του κοντέινερ (labo-kibana) αντί για το όνομα υπηρεσίας του Compose (kibana).

A.8 — Επιδιόρθωση του Kibana και απόδειξη ότι το Elasticsearch δεν είδε τίποτα

Ξανάτρεξε την υπηρεσία και περίμενε το healthcheck της να ξαναγίνει πράσινο: 40 έως 60 δευτερόλεπτα, ο χρόνος να επανασυνδεθεί στο Elasticsearch και να επαληθεύσει τα εσωτερικά της index.

powershell
docker compose start kibana
text
 Container labo-elasticsearch Waiting
 Container labo-elasticsearch Healthy
 Container labo-kibana Starting
 Container labo-kibana Started

Το Compose επαλήθευσε πρώτα ότι το Elasticsearch ήταν Healthy (το depends_on … service_healthy του μαθήματος 03), μετά ξεκίνησε το Kibana. Παρακολούθησε κάθε πέντε δευτερόλεπτα:

powershell
docker inspect --format '{{.State.Health.Status}}' labo-kibana

Σημείο ελέγχου: starting για 40 έως 50 δευτερόλεπτα, μετά healthy (στο μηχάνημα του μαθήματος: starting από 0 έως 45 δ., healthy στα 50 δ.). Το etat δείχνει τότε ξανά τη γραμμή:

text
labo-kibana                  Up 52 seconds (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp

  ✔ Kibana répond (http://localhost:5601)

και το journal kibana τελειώνει με τις γραμμές που θέλουμε να δούμε:

text
labo-kibana  | [2026-09-09T23:47:49.408+00:00][INFO ][http.server.Kibana] http server running at http://0.0.0.0:5601
labo-kibana  | [2026-09-09T23:47:50.374+00:00][INFO ][status] Kibana is now available

Ξανάνοιξε το Dev Tools (αν πέσεις πάνω σε «Kibana server is not ready yet», ήσουν πιο γρήγορος από το healthy: περίμενε δέκα δευτερόλεπτα) και στείλε το ερώτημα που χρησιμοποιεί το ίδιο το etat:

text
GET _cluster/health?filter_path=status,number_of_nodes
json
{
  "status": "green",
  "number_of_nodes": 1
}

Το Elasticsearch απάντησε green καθ' όλη τη διάρκεια της βλάβης: το Kibana είναι ένα παράθυρο στα δεδομένα, όχι τα δεδομένα. Η διακοπή του Kibana δεν έσβησε ούτε επανευρετηρίασε τίποτα.

Αν βλέπεις κάτι άλλο: unhealthy μετά από δύο λεπτά → journal kibana και ψάξε για ECONNREFUSED (το Elasticsearch έπεσε στο μεταξύ)· port is already allocated → κάποιο άλλο πρόγραμμα πήρε τη θύρα 5601 κατά τη διάρκεια της διακοπής (μάθημα 04, βλάβη 1).

A.9 — Δοκίμασε μόνος σου: πρόκλησε και εξήγησε ένα 404

Αναγνωρίζεις μια σταματημένη υπηρεσία· μάθε τώρα να αναγνωρίζεις ένα λάθος ερώτημα σε μια υγιή υπηρεσία, τη συχνότερη σύγχυση στην αίθουσα. Στο Dev Tools, γράψε ένα ερώτημα αναζήτησης που επιστρέφει 404 με "type": "index_not_found_exception", μετά εξήγησε σε μία φράση γιατί το cluster παραμένει green.

Υπόδειξη: το Elasticsearch δεν μαντεύει ποτέ το όνομα ενός index. Διάλεξε ένα που δεν υπάρχει, με το πρόθεμα pratique-· τίποτα να δημιουργήσεις, τίποτα να γράψεις.

Solution (Λύση)
text
GET pratique-inexistant/_search

Απάντηση, με το σήμα 404 - Not Found κάτω δεξιά του πλαισίου εξόδου:

json
{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index [pratique-inexistant]",
        "resource.type": "index_or_alias",
        "resource.id": "pratique-inexistant",
        "index_uuid": "_na_",
        "index": "pratique-inexistant"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index [pratique-inexistant]",

  },
  "status": 404
}

Αναμενόμενη εξήγηση: το 404 είναι μια φυσιολογική και πλήρης απάντηση του Elasticsearch: «κατάλαβα το ερώτημά σου, αλλά αυτός ο πόρος δεν υπάρχει». Η υπηρεσία τρέχει, το cluster παραμένει green· μόνο το όνομα χρειάζεται διόρθωση (GET _cat/indices?v δίνει τη λίστα). Στο βήμα A.7, δεν υπήρχε καμία απάντηση καθόλου.

Δύο παραλλαγές προς δοκιμή: το GET pratique-inexistant/_count?ignore_unavailable=true επιστρέφει 200 και "count": 0 (ζητάς να αγνοηθεί το απόν index)· το GET cours/_serch επιστρέφει 400 με "no handler found for uri [/cours/_serch] and method [GET]". Ένα 400: «δεν καταλαβαίνω το ερώτημα»· ένα 404: «καταλαβαίνω, αλλά αυτό δεν υπάρχει».

A.10 — Τελικός έλεγχος

Μία μόνο εντολή αποδεικνύει ότι όλα έγιναν: etat, με το Kibana επιστρεμμένο και τους τέσσερις αριθμούς.

powershell
.\labo.ps1 etat
text
== Conteneurs ==
NAME                         STATUS                        PORTS
labo-elasticsearch           Up 10 hours (healthy)         0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up About a minute (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 7 minutes (healthy)        0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)         0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours                   0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

(Χωρίς το προφίλ OpenSearch, τα δύο κοντέινερ labo-opensearch… δεν εμφανίζονται και η τελευταία γραμμή λέει — OpenSearch non démarré …: είναι η αναμενόμενη κατάσταση μέχρι την ενότητα 5.)

  • Το prerequis τελειώνει με Tout est prêt.
  • Τα labo-elasticsearch, labo-kibana και labo-neo4j είναι Up … (healthy), το Kibana συμπεριλαμβανομένου.
  • Το etat εμφανίζει "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, αμετάβλητα μετά τη δεύτερη importer / charger-graphe.
  • Οι ίδιοι μετρητές ξαναδιαβάστηκαν στο Dev Tools και στο Neo4j Browser.
  • Ξέρεις να λες τι δείχνουν το etat, το πρόγραμμα περιήγησης και το journal kibana όταν το Kibana είναι σταματημένο, και σε τι διαφέρει από ένα 404.
  • Κράτησες την έξοδο του etat παραπάνω (αντίγραφο ή στιγμιότυπο) ως παραδοτέο.

A.11 — Καθαρισμός

Αυτή η πρακτική δεν δημιουργεί τίποτα: ούτε index, ούτε κόμβο, ούτε αντικείμενο Kibana. Δύο πράγματα να διασφαλίσεις: ότι το Kibana τρέχει (αλλιώς docker compose start kibana από τον φάκελο του kit), και ότι κανένα index εργασίας δεν έχει μείνει:

text
GET _cat/indices/pratique-*?v

Αναμενόμενη απάντηση: η γραμμή κεφαλίδας μόνη (health status index uuid pri rep docs.count …). Μην αγγίξεις τα cours, avis, acces ούτε τον γράφο: χρησιμεύουν σε όλες τις επόμενες ενότητες.

Παράρτημα Β — Λεπτομερές βήμα προς βήμα σε Linux, macOS, WSL 2 και Git Bash

Afficher le pas à pas Linux, macOS, WSL 2 et Git Bash (B.0 à B.11) (Εμφάνιση βήμα προς βήμα Linux, macOS, WSL 2 και Git Bash, B.0 έως B.11)

Όλες οι εντολές αυτού του παραρτήματος πληκτρολογούνται σε τερματικό bash (ή zsh), με ./labo.sh …. Οι έξοδοι είναι πανομοιότυπες με αυτές των Windows, εκτός από το όνομα του σεναρίου: το kit είναι το ίδιο, μόνο οι εκκινητές αλλάζουν.

B.0 — Πριν ξεκινήσεις

  • Να έχεις διαβάσει τα τέσσερα μαθήματα της ενότητας: 01, 02, 03 και 04.
  • Docker εκκινημένο (Docker Desktop σε macOS και WSL 2, η υπηρεσία docker σε Linux: το docker info πρέπει να απαντά), το kit κλωνοποιημένο, ένα τερματικό ανοιχτό μέσα στον φάκελο του kit (αυτόν που περιέχει το docker-compose.yml και το labo.sh).
  • Αν το bash απαντά Permission denied στο ./labo.sh: chmod +x labo.sh, μία φορά.
  • Το εργαστήριο μπορεί να είναι εκκινημένο ή όχι: η πρακτική ξεκινά με τα προαπαιτούμενα. Στο τέλος, το ./labo.sh etat πρέπει να εμφανίζει (healthy) παντού, "status":"green", acces 12000 avis 609 cours 504 και nœuds : 872.
  • Όλα τα ερωτήματα αυτής της πρακτικής: elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Κανένα δεν γράφει στο cluster.

B.1 — Εκτέλεση της λίστας ελέγχου προαπαιτούμενων

Πριν ανάψεις οτιδήποτε, κάνε το σενάριο να μιλήσει.

bash
./labo.sh prerequis

Σημείο ελέγχου: μόνο πράσινα τικ και η τελική φράση.

text
== Prérequis ==
  ✔ docker : Docker version 29.3.1, build c2be9cc
  ✔ le démon Docker répond
  ✔ docker compose : 5.1.1
  ✔ mémoire disponible pour Docker : 31 Go
  ✔ processeurs : 20
  ✔ port 9200 : utilisé par le labo lui-même
  ✔ port 5601 : utilisé par le labo lui-même
  ✔ port 7474 : utilisé par le labo lui-même
  ✔ port 7687 : utilisé par le labo lui-même

Tout est prêt. Lancez : ./labo.sh demarrer

Στο δικό σου μηχάνημα, εκδόσεις και μνήμη διαφέρουν, και αν το εργαστήριο δεν έχει τρέξει ποτέ οι τέσσερις τελευταίες γραμμές λένε port 9200 : libre. Και οι δύο αναγνώσεις είναι καλές.

Αν βλέπεις κάτι άλλο: ένας σταυρός περιέχει τη λύση του μέσα στη φράση (Docker όχι εκκινημένο, μνήμη κάτω από 4 GB, θύρα κατειλημμένη)· διόρθωσε, ξανατρέξε, προχώρα μόνο με την πράσινη γραμμή. Σε Linux native, η μνήμη είναι αυτή του μηχανήματος· σε macOS και WSL 2, είναι αυτή που διατέθηκε στο Docker Desktop → SettingsResources.

B.2 — Εκκίνηση και ανάγνωση των τριών μπλοκ

Ξεκίνα την εκκίνηση και, αυτή τη φορά, διάβασε ό,τι κυλά αντί να περιμένεις το τέλος.

bash
./labo.sh demarrer

Σημείο ελέγχου: τρία μπλοκ == … == μετά τρία prêt.

text
== Téléchargement des images (long la première fois : ~6 Go, ~12 Go avec OpenSearch) ==

== Démarrage ==

== Attente que chaque service soit prêt ==
  elasticsearch            prêt (0 s)
  kibana                   prêt (0 s)
  neo4j                   ..... prêt (15 s)

Le labo est prêt.
  Kibana                 http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
  Elasticsearch          http://localhost:9200
  Neo4j Browser          http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)

Étape suivante : ./labo.sh importer   puis   ./labo.sh charger-graphe

Το πρώτο μπλοκ είναι κενό αν οι εικόνες υπάρχουν ήδη· το δεύτερο περιέχει τις γραμμές του Compose (Container labo-elasticsearch Started, ή Running αν ήδη έτρεχε)· το τρίτο προσθέτει μια τελεία κάθε τρία δευτερόλεπτα μέχρι το prêt (… s). Κρύο εργαστήριο: ένα με δύο λεπτά· εργαστήριο ήδη σε λειτουργία: prêt (0 s).

Αν βλέπεις κάτι άλλο: unhealthy, exited ή délai dépassé./labo.sh journal <service> και ο κατάλογος του μαθήματος 04· πιο συχνές αιτίες: η μνήμη (Exited (137)) και, σε Linux native, το vm.max_map_count πολύ χαμηλό (sudo sysctl -w vm.max_map_count=262144, μετά ./labo.sh demarrer).

B.3 — Καταγραφή των τεσσάρων αριθμών του etat

Θα πληκτρολογήσεις το etat πολλές φορές· μάθε πρώτα να εξάγεις τους τέσσερις αριθμούς που μετράνε.

bash
./labo.sh etat

Σημείο ελέγχου: πραγματική έξοδος από το μηχάνημα του μαθήματος, όπου το προφίλ OpenSearch της ενότητας 5 είναι ενεργό. Στο δικό σου, οι δύο γραμμές labo-opensearch… και ✔ OpenSearch αντικαθίστανται από — OpenSearch non démarré (profil optionnel : ./labo.sh demarrer opensearch).

text
== Conteneurs ==
NAME                         STATUS                    PORTS
labo-elasticsearch           Up 10 hours (healthy)     0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up 10 hours (healthy)     0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 37 seconds (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)     0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours               0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Σημείωσε τους τέσσερις αναμενόμενους αριθμούς: 12000, 609, 504, 872. Σε ένα ολοκαίνουργιο εργαστήριο διαβάζεις index : aucun index du labo και nœuds : 0: φυσιολογικό, το βήμα B.4 τα συμπληρώνει.

Αν βλέπεις κάτι άλλο: ✘ Kibana ne répond pas encore μέσα στο λεπτό που ακολουθεί το demarrer → το Kibana τελειώνει να δημιουργεί τα εσωτερικά του index· ξαναπληκτρολόγησε etat τριάντα δευτερόλεπτα αργότερα.

B.4 — Φόρτωση των δεδομένων, μετά επαναφόρτωση για να αποδειχθεί ότι τίποτα δεν κινείται

Φόρτωσε τα index μετά τον γράφο, και ξανάτρεξε τις δύο εντολές μια δεύτερη φορά: οι μετρητές δεν πρέπει να αλλάξουν ούτε μία μονάδα.

bash
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer
./labo.sh charger-graphe

Σημείο ελέγχου: στο δεύτερο πέρασμα, το importer δηλώνει ότι τα index υπάρχουν ήδη και επιστρέφει τους ίδιους μετρητές:

text
== Import dans elasticsearch ==
— index cours existe déjà — conservé
  ✔ données cours chargées
— index avis existe déjà — conservé
  ✔ données avis chargées
— index acces existe déjà — conservé
  ✔ données acces chargées

index docs.count store.size
acces      12000      1.5mb
avis         609     74.8kb
cours        504    183.6kb

Import terminé. Attendu : cours = 504, avis = 609, acces = 12000.

και το charger-graphe δίνει τον ίδιο απολογισμό με την πρώτη φορά:

text
== Chargement du graphe Neo4j ==
  ✔ contraintes et index en place
etiquette, noeuds
"Competence", 22
"Cours", 504
"Etudiant", 300
"Professeur", 30
"Ville", 16

Graphe chargé. Attendu : Competence 22, Cours 504, Etudiant 300, Professeur 30, Ville 16.

Τα έγγραφα φέρουν το δικό τους _id (C0001, A00001…), άρα ένα δεύτερο _bulk αντικαθιστά κάθε έγγραφο αντί να το προσθέτει· στο Neo4j, όλα γίνονται με MERGE. Μόνο το store.size μπορεί να κινηθεί κατά μερικά kilobyte (η Lucene κρατά για λίγο τις παλιές εκδόσεις)· τα docs.count, ποτέ.

Αν βλέπεις κάτι άλλο: ένα docs.count διαφορετικό από 504 / 609 / 12000 → κάποιος έγραψε σε αυτά τα index· ./labo.sh reinitialiser μετά demarrer, importer, charger-graphe σου επαναφέρει την κατάσταση αναφοράς.

B.5 — Επαλήθευση του Elasticsearch από το Dev Tools

Το σενάριο λέει ότι όλα είναι καλά· επαλήθευσέ το χωρίς αυτό. Άνοιξε http://localhost:5601, μενού ManagementOutils de développement, και στείλε αυτά τα ερωτήματα ένα προς ένα (Ctrl + Enter, ή Cmd + Enter σε macOS, ή το κουμπί ▶ «Cliquer pour envoyer la requête»).

text
GET _cluster/health

Σημείο ελέγχου: "status": "green", "unassigned_shards": 0, και 200 - OK κάτω δεξιά του πλαισίου απάντησης.

json
{
  "cluster_name": "labo",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 53,
  "active_shards": 53,

  "unassigned_shards": 0,

  "active_shards_percent_as_number": 100
}
text
GET _cat/indices/cours,avis,acces?v&s=index

Σημείο ελέγχου: τρεις γραμμές green, pri 1, rep 0, και οι τρεις μετρητές στο docs.count.

text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   acces aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   avis  W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.8kb         74.8kb       74.8kb
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Τελείωσε με GET cours/_count, GET avis/_count, GET acces/_count: "count": 504, 609, 12000. Έχεις τους ίδιους αριθμούς από δύο ανεξάρτητους δρόμους: το σενάριο (curl μέσα στο κοντέινερ) και το Dev Tools (μέσω του Kibana). Αν κάποια μέρα διαφέρουν, φταίει ο δρόμος, όχι τα δεδομένα.

Σε bash, έχεις ακόμα και τρίτο δρόμο, χωρίς πρόγραμμα περιήγησης:

bash
curl -s 'http://localhost:9200/_cat/indices/cours,avis,acces?v&s=index'

Αν βλέπεις κάτι άλλο: "status": "yellow" → ένα index έχει μη κατανεμημένα replica, αδύνατο με τα mappings του kit (number_of_replicas: 0GET _cat/indices?v&health=yellow δείχνει τον ένοχο, συνήθως ένα index δημιουργημένο με το χέρι.

B.6 — Μέτρηση του γράφου στο Neo4j Browser

Ίδια άσκηση για το Neo4j. Άνοιξε http://localhost:7474, συνδέσου (neo4j / aiopsatlas2026, URL localhost:7687), πληκτρολόγησε στον επεξεργαστή neo4j$ και κάνε κλικ Run:

cypher
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label

Σημείο ελέγχου: ένα πλαίσιο με δύο προβολές, Table και Raw (όχι Graph: το ερώτημα επιστρέφει αριθμούς, όχι κόμβους), δύο στήλες label και count(*), πέντε ταξινομημένες γραμμές, και κάτω δεξιά Started streaming 5 records after … ms and completed after … ms.

text
label          count(*)
"Competence"   22
"Cours"        504
"Etudiant"     300
"Professeur"   30
"Ville"        16

22 + 504 + 300 + 30 + 16 = 872, ο αριθμός του etat. Το πλαίσιο Database information (εικονίδιο Database overview, το πρώτο της πλαϊνής μπάρας) εμφανίζει Nodes (872) και Relationships (3,712).

Αν βλέπεις κάτι άλλο: μια έκτη άγνωστη ετικέτα → κόμβοι δημιουργημένοι εκτός kit (η ενότητα 6 θα σου μάθει να τους διαγράφεις σωστά)· Nodes (0) → παρέλειψες το charger-graphe, επιστροφή στο βήμα B.4.

B.7 — Σκόπιμη βλάβη του Kibana και παρατήρηση της βλάβης

Ξέρεις πώς μοιάζει ένα υγιές εργαστήριο· πρόκαλεσε μια βλάβη της οποίας γνωρίζεις την αιτία για να μάθεις να τη διαβάζεις. Σταμάτησε μόνο το Kibana, με το Compose (όχι το arreter, που θα σταματούσε τα πάντα):

bash
docker compose stop kibana
text
 Container labo-kibana Stopping
 Container labo-kibana Stopped

Μετά οι τρεις κινήσεις του μαθήματος 04, με τη σειρά: etat, το πρόγραμμα περιήγησης, journal.

Σημείο ελέγχου 1, ./labo.sh etat : η γραμμή labo-kibana εξαφανίστηκε από το μπλοκ == Conteneurs == (το Compose εμφανίζει εξ ορισμού μόνο τα κοντέινερ που τρέχουν) και το μπλοκ == Services == σημειώνει έναν σταυρό:

text
== Conteneurs ==
NAME                         STATUS                   PORTS
labo-elasticsearch           Up 10 hours (healthy)    0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-neo4j                   Up 3 minutes (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)    0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours              0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✘ Kibana ne répond pas encore
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Για να δεις παρόλα αυτά το σταματημένο κοντέινερ: docker compose ps -a εμφανίζει labo-kibana Exited (0) 31 seconds ago. Το 0 λέει «καθαρή διακοπή, ζητηθείσα»· ένα 137 θα έλεγε «σκοτώθηκε, μνήμη».

Σημείο ελέγχου 2, το πρόγραμμα περιήγησης: ανανέωσε το http://localhost:5601. Όχι «Kibana server is not ready yet» (αυτή η φράση προέρχεται από το Kibana, όμως δεν υπάρχει πια Kibana για να την πει) αλλά το σφάλμα σύνδεσης του ίδιου του προγράμματος περιήγησης: στο Chrome, «Ce site est inaccessible», κωδικός ERR_CONNECTION_REFUSED· στο Firefox, «Impossible de se connecter». Στη γραμμή εντολών, το curl -s http://localhost:5601 || echo REFUSE εμφανίζει REFUSE: κανείς δεν ακούει στη θύρα 5601. Να θυμάσαι: σελίδα Kibana που ζητά συγγνώμη = το Kibana τρέχει αλλά περιμένει το Elasticsearch· σφάλμα του προγράμματος περιήγησης = το Kibana δεν τρέχει.

Σημείο ελέγχου 3, ./labo.sh journal kibana : οι εκατό τελευταίες γραμμές τελειώνουν με μια καθαρή διακοπή, χρονοσημασμένη τη στιγμή που πληκτρολόγησες το stop:

text
labo-kibana  | [2026-09-09T23:44:06.893+00:00][INFO ][root] SIGTERM received - initiating shutdown
labo-kibana  | [2026-09-09T23:44:06.894+00:00][INFO ][root] Kibana is shutting down
labo-kibana  | [2026-09-09T23:44:06.902+00:00][INFO ][plugins-system.standard] Stopping all plugins.

labo-kibana  | [2026-09-09T23:44:07.265+00:00][INFO ][plugins-system.standard] All plugins stopped.

Το SIGTERM received υπογράφει μια ζητηθείσα διακοπή (από σένα, από το docker compose stop, από επανεκκίνηση του Docker). Καμία γραμμή ERROR ούτε FATAL: τίποτα να επιδιορθωθεί, απλά χρειάζεται επανεκκίνηση. Οι δεκάδες γραμμές at OperatorSubscriber… ανάμεσα στις δύο είναι μια στοίβα κλήσεων που εκπέμπεται από ένα plugin κατά τη διακοπή: θόρυβος.

Αν βλέπεις κάτι άλλο: no configuration file provided: not found → δεν είσαι στον φάκελο του kit· no such service: kibana → πληκτρολόγησες το όνομα του κοντέινερ (labo-kibana) αντί για το όνομα υπηρεσίας του Compose (kibana).

B.8 — Επιδιόρθωση του Kibana και απόδειξη ότι το Elasticsearch δεν είδε τίποτα

Ξανάτρεξε την υπηρεσία και περίμενε το healthcheck της να ξαναγίνει πράσινο: 40 έως 60 δευτερόλεπτα, ο χρόνος να επανασυνδεθεί στο Elasticsearch και να επαληθεύσει τα εσωτερικά της index.

bash
docker compose start kibana
text
 Container labo-elasticsearch Waiting
 Container labo-elasticsearch Healthy
 Container labo-kibana Starting
 Container labo-kibana Started

Το Compose επαλήθευσε πρώτα ότι το Elasticsearch ήταν Healthy (το depends_on … service_healthy του μαθήματος 03), μετά ξεκίνησε το Kibana. Παρακολούθησε κάθε πέντε δευτερόλεπτα:

bash
docker inspect --format '{{.State.Health.Status}}' labo-kibana

ή, για να μην ξαναπληκτρολογείς, το watch -n 5 docker inspect --format '{{.State.Health.Status}}' labo-kibana (Ctrl + C για έξοδο· το watch λείπει από το macOS εξ ορισμού, ξαναπληκτρολόγησε την εντολή με το χέρι).

Σημείο ελέγχου: starting για 40 έως 50 δευτερόλεπτα, μετά healthy (στο μηχάνημα του μαθήματος: starting από 0 έως 45 δ., healthy στα 50 δ.). Το etat δείχνει τότε ξανά τη γραμμή:

text
labo-kibana                  Up 52 seconds (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp

  ✔ Kibana répond (http://localhost:5601)

και το journal kibana τελειώνει με τις γραμμές που θέλουμε να δούμε:

text
labo-kibana  | [2026-09-09T23:47:49.408+00:00][INFO ][http.server.Kibana] http server running at http://0.0.0.0:5601
labo-kibana  | [2026-09-09T23:47:50.374+00:00][INFO ][status] Kibana is now available

Ξανάνοιξε το Dev Tools (αν πέσεις πάνω σε «Kibana server is not ready yet», ήσουν πιο γρήγορος από το healthy: περίμενε δέκα δευτερόλεπτα) και στείλε το ερώτημα που χρησιμοποιεί το ίδιο το etat:

text
GET _cluster/health?filter_path=status,number_of_nodes
json
{
  "status": "green",
  "number_of_nodes": 1
}

Το Elasticsearch απάντησε green καθ' όλη τη διάρκεια της βλάβης: το Kibana είναι ένα παράθυρο στα δεδομένα, όχι τα δεδομένα. Η διακοπή του Kibana δεν έσβησε ούτε επανευρετηρίασε τίποτα.

Αν βλέπεις κάτι άλλο: unhealthy μετά από δύο λεπτά → journal kibana και ψάξε για ECONNREFUSED (το Elasticsearch έπεσε στο μεταξύ)· port is already allocated → κάποιο άλλο πρόγραμμα πήρε τη θύρα 5601 κατά τη διάρκεια της διακοπής (μάθημα 04, βλάβη 1).

B.9 — Δοκίμασε μόνος σου: πρόκλησε και εξήγησε ένα 404

Αναγνωρίζεις μια σταματημένη υπηρεσία· μάθε τώρα να αναγνωρίζεις ένα λάθος ερώτημα σε μια υγιή υπηρεσία, τη συχνότερη σύγχυση στην αίθουσα. Στο Dev Tools, γράψε ένα ερώτημα αναζήτησης που επιστρέφει 404 με "type": "index_not_found_exception", μετά εξήγησε σε μία φράση γιατί το cluster παραμένει green.

Υπόδειξη: το Elasticsearch δεν μαντεύει ποτέ το όνομα ενός index. Διάλεξε ένα που δεν υπάρχει, με το πρόθεμα pratique-· τίποτα να δημιουργήσεις, τίποτα να γράψεις.

Solution (Λύση)
text
GET pratique-inexistant/_search

Απάντηση, με το σήμα 404 - Not Found κάτω δεξιά του πλαισίου εξόδου:

json
{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index [pratique-inexistant]",
        "resource.type": "index_or_alias",
        "resource.id": "pratique-inexistant",
        "index_uuid": "_na_",
        "index": "pratique-inexistant"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index [pratique-inexistant]",

  },
  "status": 404
}

Το ίδιο στη γραμμή εντολών, για να δεις τον γυμνό κωδικό HTTP: το curl -s -o /dev/null -w '%{http_code}\n' http://localhost:9200/pratique-inexistant/_search εμφανίζει 404.

Αναμενόμενη εξήγηση: το 404 είναι μια φυσιολογική και πλήρης απάντηση του Elasticsearch: «κατάλαβα το ερώτημά σου, αλλά αυτός ο πόρος δεν υπάρχει». Η υπηρεσία τρέχει, το cluster παραμένει green· μόνο το όνομα χρειάζεται διόρθωση (GET _cat/indices?v δίνει τη λίστα). Στο βήμα B.7, δεν υπήρχε καμία απάντηση καθόλου.

Δύο παραλλαγές προς δοκιμή: το GET pratique-inexistant/_count?ignore_unavailable=true επιστρέφει 200 και "count": 0 (ζητάς να αγνοηθεί το απόν index)· το GET cours/_serch επιστρέφει 400 με "no handler found for uri [/cours/_serch] and method [GET]". Ένα 400: «δεν καταλαβαίνω το ερώτημα»· ένα 404: «καταλαβαίνω, αλλά αυτό δεν υπάρχει».

B.10 — Τελικός έλεγχος

Μία μόνο εντολή αποδεικνύει ότι όλα έγιναν: etat, με το Kibana επιστρεμμένο και τους τέσσερις αριθμούς.

bash
./labo.sh etat
text
== Conteneurs ==
NAME                         STATUS                        PORTS
labo-elasticsearch           Up 10 hours (healthy)         0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up About a minute (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 7 minutes (healthy)        0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)         0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours                   0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

(Χωρίς το προφίλ OpenSearch, τα δύο κοντέινερ labo-opensearch… δεν εμφανίζονται και η τελευταία γραμμή λέει — OpenSearch non démarré …: είναι η αναμενόμενη κατάσταση μέχρι την ενότητα 5.)

  • Το prerequis τελειώνει με Tout est prêt.
  • Τα labo-elasticsearch, labo-kibana και labo-neo4j είναι Up … (healthy), το Kibana συμπεριλαμβανομένου.
  • Το etat εμφανίζει "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, αμετάβλητα μετά τη δεύτερη importer / charger-graphe.
  • Οι ίδιοι μετρητές ξαναδιαβάστηκαν στο Dev Tools και στο Neo4j Browser.
  • Ξέρεις να λες τι δείχνουν το etat, το πρόγραμμα περιήγησης και το journal kibana όταν το Kibana είναι σταματημένο, και σε τι διαφέρει από ένα 404.
  • Κράτησες την έξοδο του etat παραπάνω (αντίγραφο ή στιγμιότυπο) ως παραδοτέο.

B.11 — Καθαρισμός

Αυτή η πρακτική δεν δημιουργεί τίποτα: ούτε index, ούτε κόμβο, ούτε αντικείμενο Kibana. Δύο πράγματα να διασφαλίσεις: ότι το Kibana τρέχει (αλλιώς docker compose start kibana από τον φάκελο του kit), και ότι κανένα index εργασίας δεν έχει μείνει:

text
GET _cat/indices/pratique-*?v

Αναμενόμενη απάντηση: η γραμμή κεφαλίδας μόνη (health status index uuid pri rep docs.count …). Μην αγγίξεις τα cours, avis, acces ούτε τον γράφο: χρησιμεύουν σε όλες τις επόμενες ενότητες.

Παράρτημα Γ — Αν κολλήσεις (όλα τα συστήματα)

Afficher les cas qui coincent (Εμφάνιση περιπτώσεων που κολλάνε)
  • Το docker compose stop kibana απαντά no configuration file provided: not found → Το Compose ψάχνει το docker-compose.yml στον τρέχοντα φάκελο. Κάνε cd προς τη ρίζα του kit (αυτή που περιέχει το labo.sh και το labo.ps1) και ξαναρχίσε. Το σενάριο επανατοποθετείται μόνο του· οι εντολές docker compose πληκτρολογημένες με το χέρι, όχι.

  • Μετά το stop, το etat δεν δείχνει πια το labo-kibana και νομίζεις ότι το διέγραψες → Όχι: το docker compose ps κρύβει τα σταματημένα κοντέινερ. Το docker compose ps -a το απαριθμεί ως Exited (0), και το docker compose start kibana το επανεκκινεί με τα δεδομένα του. Ένα πραγματικά διαγραμμένο κοντέινερ δεν θα εμφανιζόταν ούτε καν με το -a· το demarrer θα το ξαναδημιουργούσε.

  • Το Kibana παραμένει starting μετά περνά unhealthy, και το journal kibana επαναλαμβάνει Unable to retrieve version information from Elasticsearch nodes. connect ECONNREFUSED 172.x.x.x:9200 → Το Kibana επέστρεψε αλλά το Elasticsearch έπεσε στο μεταξύ (συχνά Exited (137), η μνήμη). Επιδιόρθωσε πρώτα το Elasticsearch (μάθημα 04, βλάβη 2)· το Kibana επανασυνδέεται μόνο του.

  • Το Dev Tools επιστρέφει 400 αντί για το αναμενόμενο 404, με no handler found for uri [/pratique-inexistant/_serch] and method [GET] → Το λάθος αφορά το API (_serch), όχι το index. Το Elasticsearch επικυρώνει πρώτα τη διαδρομή, μετά το index: διόρθωσε σε _search και το 404 εμφανίζεται.

  • Μόνο Windows — σε PowerShell 5.1, το demarrer εμφανίζει με κόκκινο docker : Image docker.elastic.co/kibana/kibana:9.5.3 Pulling … NativeCommandError αλλά τελειώνει με Le labo est prêt → Αυτό συμβαίνει μόνο αν ανακατευθύνεις την έξοδο (2>&1, | Tee-Object): το Compose γράφει την πρόοδό του στη ροή σφάλματος και το PowerShell 5.1 την ντύνει ως εξαίρεση. Δεν είναι σφάλμα· εκτέλεσε το σενάριο χωρίς ανακατεύθυνση, ή πέρνα σε PowerShell 7.

  • Μόνο Windows — το .\labo.ps1 απορρίπτεται: «l'exécution de scripts est désactivée sur ce système»Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, απάντησε O, ξανατρέξε. Μία φορά ανά μηχάνημα.

  • Μόνο Linux native — το Elasticsearch βγαίνει με Exited (78) και το journal elasticsearch λέει max virtual memory areas vm.max_map_count [65530] is too lowsudo sysctl -w vm.max_map_count=262144 μετά ./labo.sh demarrer. Για να επιβιώσει μετά από επανεκκίνηση: πρόσθεσε vm.max_map_count=262144 στο /etc/sysctl.conf.

  • macOS και bash — το ./labo.sh απαντά Permission deniedchmod +x labo.sh, μία φορά. Αν bash: ./labo.sh: /bin/bash^M: bad interpreter, το αρχείο έχει τέλη γραμμής Windows: git config core.autocrlf input μετά ξανακλωνοποίηση, ή sed -i '' 's/\r$//' labo.sh.