كيف تقرأ هذه الصفحة. كلّ قسم مطويّ تحت عنوانه : اضغط على « عرض … » لفتحه ، وأغلقه عندما تنتهي لتبقى الصفحة قابلة للقراءة. ترتيب القراءة : الهدف ، ثم بإيجاز (الأوامر التي تكتبها) ، ثم مجموعة البيانات (تُقرأ قبل أيّ استعلام) ، ثم استعلامات Elasticsearch و Neo4j ، مرتّبة من الأبسط (
GET _cat/indices،MATCH (n) RETURN n) إلى الأكثر إثارة للإعجاب ، مع شرح بعد كلّ واحدة. الخطوات التفصيليّة ، مع المخرجات المتوقّعة لكلّ أمر والأعطال التي يجب إثارتها ، في الملحق : الملحق A لـ Windows (PowerShell) ، الملحق B لـ Linux و macOS و WSL 2 و Git Bash. افتح ملحقًا واحدًا فقط ، ذلك الخاصّ بنظامك. الملحق C ، المشترك ، يجمع الحالات التي يتعطّل فيها العمل.
تلتحق بالفرقة التي تبني محرّك البحث لمنصّة الدورات عبر الإنترنت. تُسلّمك رئيستك حزمة المختبر : « صباح الغد ، أريد مختبرًا يعمل على جهازك ، البيانات محمَّلة ، والتأكّد من أنّك تعرف كيف تُصلحه دون الاتّصال بي. » ستُشغّل الحزمة ، تُثبت أنّ المحرّكات الثلاثة تحتوي على ما يجب ، ثم تُوقف خدمة عمدًا لترى كيف يُقرأ العطل في etat وفي المتصفّح وفي السجلّ ، وتعيدها للعمل. التعرّف على « هذه الخدمة متوقّفة » في عشر ثوانٍ ، هذا ما يوفّر ساعات من البحث في المكان الخاطئ.
الخطوات التسع في هذا المخطّط مفصَّلة ، مع المخرجات المتوقّعة لكلّ أمر ، في الملحق A (Windows) أو الملحق B (Linux, macOS) في أسفل الصفحة.
حزمة المختبر : https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr
تنسخ الحزمة في مجلّد lab1 ، تتحقّق من أنّ Docker جاهز ، تشغّل الخدمات الثلاث (Elasticsearch و Kibana و Neo4j) ، تفتح صفحاتها الويب الثلاث ، ثم تحمّل البيانات. تشغّل importer و charger-graphe مرّتين : لا يجب أن تُغيّر المرّة الثانية العدّادات ، وهذا هو الدليل على أنّ التحميل قابل لإعادة التنفيذ دون تكرار. في النهاية ، يجب أن يعرض etat (healthy) في كلّ مكان ، acces 12000 avis 609 cours 504 و nœuds : 872. ابدأ بتنفيذ هذه الفقرة.
Windows (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تحقّق من العناوين الثلاثة في المتصفّح :
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).\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 (« l'exécution de scripts est désactivée ») : Set-ExecutionPolicy -Scope CurrentUser RemoteSigned، أجب O، أعد التشغيل.
Linux, macOS, WSL 2, Git 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تحقّق من العناوين الثلاثة في المتصفّح :
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)./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قبل كتابة استعلام واحد ، انظر إلى البيانات. يدور المختبر كلّه حول منصّة دورات عبر الإنترنت خياليّة : فهرس دورات ، الآراء التي يتركها الطلاب ، سجلّ خادم الويب الذي يخدم الصفحات ، والروابط بين الطلاب والمعلّمين والدورات والمهارات. تُحمَّل نفس البيانات في Elasticsearch (للبحث) وفي Neo4j (لتتبّع الروابط). هي موجودة في الحزمة ، بوضوح ، في مجلّدين :
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افتحها أنت بنفسك ، تأخذ عشر ثوانٍ وستعرف بالضبط ما تتعامل معه :
# 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# 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ملفّ .ndjson (newline-delimited JSON) يحتوي على كائن JSON واحد لكلّ سطر. في الحزمة ، تأتي الأسطر بالزوج : سطر تعليمة (« ضع ما يلي في الفهرس cours تحت المعرّف C0001 ») ، ثم المستند نفسه. هذا هو التنسيق الذي يرسله أمر importer إلى Elasticsearch بشكل جماعيّ.
{"index":{"_index":"cours","_id":"C0001"}}
{"id":"C0001","titre":"Docker expliqué simplement","description":"…","categorie":"DevOps", … }cours : 504 مستند ، واحد لكلّ دورة في الفهرس الموضوعيّالمستند الأوّل ، كما هو في Elasticsearch :
{
"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"]
}| الحقل | مثال | ما هو |
|---|---|---|
id | C0001 | معرّف الدورة. C كما في cours ، ثم رقم. وهو أيضًا _id المستند. |
titre | Docker expliqué simplement | العنوان. نصّ حرّ ، عليه ستُبنى عمليّات البحث بالكلمات. |
description | Dans ce cours… | فقرة تقديميّة. نصّ حرّ أيضًا. |
categorie | DevOps | واحدة من الفئات الكبرى الستّ : Cloud و DevOps و Données و Développement web و IA و Sécurité. 84 دورة لكلّ فئة. |
sujet | Docker | أدقّ من الفئة : Docker و Kubernetes و Neo4j و Elasticsearch… |
niveau | debutant | debutant أو intermediaire أو avance. |
langue | en | fr أو en. |
prix | 129 | بالدولار. 0 لدورة مجانيّة. |
gratuit | false | صحيح أو خطأ. |
duree_heures | 5 | مدّة الدورة الكاملة. |
tags | ["docker", "linux", …] | قائمة كلمات مفتاحيّة. يمكن لحقل أن يحتوي على عدّة قيم. |
date_publication | 2024-08-14 | تاريخ. |
note_moyenne | 4.4 | متوسّط الدرجات المستلمة ، من 5. |
nb_avis | 327 | عدد الآراء المستلمة. |
professeur | { "id": "P001", "nom": …, "ville": … } | كائن داخل الكائن : المعلّم موصوف مباشرة في بطاقة الدورة ، بمعرّفه واسمه ومدينته. |
competences | ["Conteneurisation", …] | قائمة المهارات التي تغطّيها الدورة. |
avis : 609 مستند ، واحد لكلّ رأي تركه طالب{
"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
}| الحقل | مثال | ما هو |
|---|---|---|
id | A00001 | معرّف الرأي. A كما في avis. |
cours_id | C0028 | الدورة المعنيّة. هذا هو الرابط إلى فهرس cours : هذا C0028 هو id مستند من cours. |
etudiant | Nathan | الاسم الأوّل لصاحب الرأي. |
ville, pays | Sherbrooke, Canada | من أين يكتب. |
note | 3 | الدرجة المُعطاة ، من 1 إلى 5. |
texte | Les vidéos sont bonnes… | التعليق. نصّ حرّ. |
date | 2024-06-21 | تاريخ الرأي. |
utile | 33 | عدد الأشخاص الذين وجدوا هذا الرأي مفيدًا. |
acces : 12000 مستند ، سطر واحد لكلّ طلب استقبله خادم الويب{
"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"
}| الحقل | مثال | ما هو |
|---|---|---|
id | L000001 | معرّف السطر. L كما في سطر السجلّ (log). |
@timestamp | 2026-08-10T08:17:19.000Z | تاريخ ووقت الطلب بالضبط. الـ @ اتّفاقيّة : إنّه حقل الوقت الذي يكتشفه Kibana تلقائيًّا. |
methode | GET | GET أو POST. |
chemin | /robots.txt | العنوان المطلوب على الموقع : /، /cours، /catalogue، /contact، /tarifs، أو صفحة دورة. |
cours_id | null أو C0042 | الدورة المُستعرَضة ، إذا كانت الصفحة تخصّها ؛ null (فارغ) في غير ذلك ، كما هنا لـ /robots.txt. رابط إلى فهرس cours. |
categorie | null أو DevOps | فئة الدورة المُستعرَضة ، منسوخة لتبسيط الرسوم البيانيّة. |
statut | 200 | رمز HTTP للاستجابة : 200 نجاح ، 301 و 304 إعادة توجيه أو ذاكرة مؤقّتة ، 404 غير موجود ، 500 و 503 خطأ خادم. |
octets | 108506 | حجم الاستجابة المُرسَلة. |
duree_ms | 100 | زمن الاستجابة بالميلّي ثانية. |
ip | 108.190.166.1 | عنوان IP للزائر. |
pays | CA | بلد الزائر ، رمز حرفين. |
appareil | desktop | desktop أو mobile أو tablette. |
navigateur | Edge | Chrome و Firefox و Safari و Edge… |
referent | google | من أين أتى الزائر : direct و google و linkedin و youtube أو newsletter. |
الفهارس الثلاثة مرتبطة بـ cours_id : رأي يتحدّث عن دورة ، سطر سجلّ يستعرض دورة. لكن Elasticsearch لا يقوم بربط (jointure) : يُستعلَم كلّ فهرس بشكل منفصل. لتتبّع الروابط ، ننتقل إلى Neo4j.
يستلم Neo4j الدورات نفسها (نفس المعرّفات C0001…، نفس العناوين) ، بالإضافة إلى ما لا يوجد في Elasticsearch : الطلاب وتسجيلاتهم والمعلّمون والمهارات والمدن والمتطلّبات بين الدورات. النموذج يتّسع في رسم واحد :
تُقرأ الأسهم الخمسة كجمل : طالب مسجّل في دورة ؛ معلّم يعلّم دورة ؛ دورة تغطّي مهارة ؛ دورة متطلّب لـ دورة أخرى ؛ طالب أو معلّم يعيش في مدينة. إجمالًا 872 عقدة و3712 علاقة.
تصل البيانات بصيغة ملفّات CSV كلاسيكيّة ، سطر عنوان ثم سطر لكلّ عنصر :
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) : ناتان بن علي مسجّل في الدورة C0028 ، أنهاها بنسبة 100% ، وأعطاها 3. قارن مع أوّل رأي في فهرس avis : إنّه نفس ناتان ، نفس الدورة C0028 ، نفس الدرجة 3.
| العقدة | الخصائص | مثال |
|---|---|---|
Cours | id, titre, categorie, sujet, niveau, prix, duree_heures, date_publication | C0001، « Docker expliqué simplement »، 129 دولار |
Etudiant | id, prenom, nom, interet, inscription_le | E0001، ناتان بن علي ، اهتمام DevOps |
Professeur | id, prenom, nom, specialite, annees_experience | P001، كريم كارون ، DevOps ، 23 سنة |
Competence | id, nom | K01، Conteneurisation |
Ville | nom, pays, latitude, longitude | مونتريال ، كندا |
| العلاقة | من → إلى | الخصائص | المعنى |
|---|---|---|---|
INSCRIT_A | Etudiant → Cours | date, progression, note | الطالب يتابع هذه الدورة |
ENSEIGNE | Professeur → Cours | — | هذا المعلّم يقدّم هذه الدورة (واحد فقط لكلّ دورة) |
COUVRE | Cours → Competence | — | هذه الدورة تعلّم هذه المهارة |
PREREQUIS_DE | Cours → Cours | — | يجب متابعة الأولى قبل الثانية |
HABITE | Etudiant ou Professeur → Ville | — | حيث يعيش الشخص |
كلّ البيانات تستخدم الاتّفاقيّة نفسها : حرف ، ثم رقم. عندما ترى معرّفًا ، تعرف فورًا عمّا نتحدّث.
| البادئة | الشيء | مثال | أين نجده |
|---|---|---|---|
C | دورة | C0001 | Elasticsearch cours، avis.cours_id، acces.cours_id، Neo4j Cours |
A | رأي | A00001 | Elasticsearch avis |
L | سطر سجلّ | L000001 | Elasticsearch acces |
E | طالب | E0001 | Neo4j Etudiant |
P | معلّم | P001 | cours.professeur.id في Elasticsearch، Neo4j Professeur |
K | مهارة | K01 | Neo4j Competence |
احتفظ بخيط واحد لكلّ ما يلي : الدورة C0001، « Docker expliqué simplement »، التي يعلّمها P001، كريم كارون. ستجدها في Elasticsearch (الاستعلام E9) ، وفي Neo4j (الاستعلامان N7 و N13) ، وستُستخدَم لإثبات أنّ المحرّكين يحتويان بالفعل على نفس الشيء.
عدّادات etat تُثبت أنّ البيانات موجودة ؛ الاستعلامات أدناه تُظهرها. كلّها للقراءة فقط : يمكنك إعادة تنفيذها كما تشاء ، لن يتغيّر شيء. هي متطابقة تحت Windows وتحت Linux ، كلّ شيء يحدث في المتصفّح.
قاعدة هذا القسم : نوفيّة واحدة فقط لكلّ استعلام. نبدأ بـ « أظهر لي ما يوجد » ، بدون أيّ معامل ، ونضيف فكرة واحدة في كلّ خطوة. بعد كلّ استعلام ، شرح قصير ؛ عندما يجب التعمّق ، فقرة مطويّة « لفهم أعمق ». الصق الاستعلامات واحدة تلو الأخرى ، بالترتيب ، واقرأ الاستجابة قبل الانتقال إلى ما يليها.
افتح http://localhost:5601، ثم القائمة ☰ → Management → Dev Tools (أو « Outils de développement »). اللوحة اليسرى محرّر : الصق استعلامًا ، ضع المؤشّر عليه ، ثم Ctrl+Enter أو المثلّث ▶. تظهر الاستجابة إلى اليمين ، مع رمز HTTP (200 - OK) في الأسفل.
صورة يجب الاحتفاظ بها في الذهن لكلّ ما يلي : Elasticsearch خزانة كبيرة.
الخزانة = Elasticsearch
الدرج = فهرس (index) (درج « cours »، درج « avis »، درج « acces »)
البطاقة = مستند (document) (بطاقة لكلّ دورة ، بطاقة لكلّ رأي ، بطاقة لكلّ سطر سجلّ)
_cat/indices = قراءة الملصقات المُثبَّتة على الأدراج : الاسم ، الحالة ، عدد البطاقات ، السُّمك
_search = فتح درج وقراءة البطاقات الموجودة داخلهالاستعلامات E1 إلى E4 تنظر إلى بطاقات الأدراج. من E5 فصاعدًا ، نفتح الأدراج. لا تخلط أبدًا بين الاثنين : هذا هو الخطأ رقم واحد عند المبتدئين.
GET _cat/indicesما يطلبه الاستعلام : « Elasticsearch ، أظهر لي قائمة كلّ أدراجك. » الاستجابة على جهاز الدورة :
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 بلا سطر عنوان. نصحّح ذلك في E2.
لقراءة الاستعلام نفسه ، كلمة بكلمة :
| المقطع | ما يعنيه |
|---|---|
GET | « أريد القراءة. » لا أُغيّر شيئًا ، لا أحذف شيئًا ، أنظر فقط. بلا خطر. |
_cat | « أجبني بجدول نصّيّ» ، مقروء من إنسان ، لا بصيغة JSON. الشرطة السفليّة في البداية تُشير إلى أمر خاصّ بـ Elasticsearch نفسه ، ليس اسم فهرس. |
indices | « … جدول الفهارس. » وهو الجمع الإنجليزيّ لـ index. |
الفهرس هو درج : مكان تُرتَّب فيه بطاقات متشابهة. كلّ بطاقات « الدورات » تذهب إلى درج cours ، كلّ بطاقات « الآراء » إلى درج avis ، وكلّ سطر من سجلّ خادم الويب هو بطاقة في درج acces. إذا كنت تعرف SQL ، الفهرس هو ما تسمّيه SQL جدولًا :
| Elasticsearch | في الصورة | SQL |
|---|---|---|
فهرس cours | درج « cours » | جدول cours |
| مستند JSON | بطاقة في الدرج | صفّ |
| حقل | خانة على البطاقة (العنوان ، السعر…) | عمود |
_id المستند | الرقم المكتوب في أعلى البطاقة | مفتاح أساسيّ |
الأدراج الثلاثة للمختبر :
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)فخّان في المصطلحات. المفرد هو فهرس واحد ، والجمع هو فهارس بالفرنسيّة (يكتب Elasticsearch indices ، الجمع الإنجليزيّ). ولا يعني « مؤشّرًا » بالمعنى الرياضيّ أو الشرطيّ : إنّه فهرس كذلك الموجود في نهاية كتاب ، القائمة التي تقول في أيّ صفحة توجد كلّ كلمة. يفعل Elasticsearch ذلك بالضبط ، على نطاق واسع جدًّا.
الآن ، لنأخذ سطرًا واحدًا من استجابة E1 ونقرأه قيمة بقيمة ، من اليسار إلى اليمين :
green open avis W9j_JrwJT4mdzpWcS5k7xg 1 0 609 0 74.6kb 74.6kb 74.6kb| القيمة | ما تعنيه |
|---|---|
green | الدرج في حالة جيّدة. أخضر = كلّ شيء على ما يرام. |
open | الدرج مفتوح : يمكن القراءة والكتابة فيه. (العكس هو close.) |
avis | اسم الدرج. |
W9j_JrwJT4mdzpWcS5k7xg | رقم تسلسليّ تقنيّ ، يُنتجه Elasticsearch. لن تستخدمه أبدًا يدويًّا. |
1 | الدرج في قطعة واحدة (شظيّة أساسيّة واحدة ، shard). يمكن تقسيم فهرس كبير إلى عدّة قطع موزّعة على عدّة آلات ؛ هنا ، لا. |
0 | صفر نسخة احتياطيّة (replica). طبيعيّ في مختبر بآلة واحدة : لن يكون للنسخة مكان تذهب إليه. |
609 | يحتوي الدرج على 609 بطاقة. هذه القيمة التي ننظر إليها أوّلًا. |
0 | صفر بطاقة مُعلَّمة « للحذف » في انتظار التنظيف. |
74.6kb | الحجم الإجماليّ المُستهلَك على القرص. |
74.6kb | الحجم الذي تشغله القطعة الأساسيّة وحدها (مطابق : ليس هناك إلّا قطعة واحدة ولا نسخة). |
74.6kb | حجم البيانات نفسها. |
ثلاث مرّات 74.6kb لأنّه ، بقطعة واحدة وصفر نسخة ، الطرق الثلاث للقياس تعطي النتيجة نفسها. على مجموعة حقيقيّة مع نسخ ، سيكون store.size أكبر من pri.store.size.
باختصار ، أدراجك الثلاثة :
avis → 609 fiches → 74,6 Ko
acces → 12 000 fiches → 1,5 Mo
cours → 504 fiches → 183,6 Ko?vGET _cat/indices?vhealth 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نفس استجابة E1 ، بالإضافة إلى سطر عناوين فوقها. هذا كلّ ما يفعله ?v. الآن لا تحتاج إلى التخمين : العمود docs.count هو عدد البطاقات (609، 0، 12000، 504) ، العمود store.size هو الحجم على القرص.
?v ، ماذا يعني بالضبط؟
؟ يقول : « ما يلي هي خيارات. » يفصل الأمر (_cat/indices) عن ضبطه.v هو الخيار verbose ، « الثرثار » : « أظهر أيضًا عناوين الأعمدة ».فـ ?v = « أجبني بالعناوين ، حتّى أفهم ما أقرأه ». اعتد على وضعه دائمًا على أوامر _cat. بدون ?v ، لديك أرقام ؛ مع ?v ، لديك معلومات.
| العمود | بوضوح |
|---|---|
health | صحّة الدرج. green : كلّ شيء بخير. yellow : البطاقات موجودة ، لكن بعض النسخ الاحتياطيّة ناقصة. red : بعض البطاقات غير قابلة للوصول. |
status | open : قابل للاستخدام. close : مغلق ، لا يمكن القراءة ولا الكتابة. |
index | اسم الدرج. |
uuid | رقم تسلسليّ تقنيّ. لا نستخدمه أبدًا يدويًّا. |
pri | في كم قطعة (primary shards) يُقسَّم الدرج. 1 في المختبر. |
rep | كم نسخة احتياطيّة (replicas) لكلّ قطعة. 0 في المختبر. |
docs.count | عدد البطاقات. العمود الذي يجب النظر إليه أوّلًا. |
docs.deleted | بطاقات مُعلَّمة « للحذف » لم تُنظَّف بعد من القرص. |
store.size | الحجم الإجماليّ على القرص. |
pri.store.size | حجم القطع الأساسيّة وحدها ، بدون النسخ. |
dataset.size | حجم البيانات نفسها. |
لماذا كلّ شيء green في المختبر : rep تساوي 0 ، إذن لا نسخة يجب القيام بها ، إذن لا نسخة يمكن أن تنقص. على مجموعة حقيقيّة ، rep 1 بآلة واحدة يعطي yellow ، لأنّ النسخة ليس لها مكان تذهب إليه.
GET _cat/indices/cours?vhealth 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. ما يتبع آخر / يُستخدَم كـ مرشّح.
نفّذ الشيء نفسه للدرجين الآخرين :
GET _cat/indices/avis?v
GET _cat/indices/acces?v« أظهر لي بطاقة درج الآراء » : 609 في docs.count. « أظهر لي بطاقة درج سجلّات الوصول » : 12000. في Dev Tools ، عندما تُلصَق عدّة استعلامات على التوالي ، يُرسَل فقط الاستعلام الذي يوجد فيه المؤشّر : ضعه على السطر المطلوب قبل Ctrl+Enter.
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,docs.count,store.sizeindex health docs.count store.size
acces green 12000 1.5mb
avis green 609 74.6kb
cours green 504 183.6kbما يطلبه الاستعلام : « أظهر لي جدولًا ملخَّصًا للأدراج acces و avis و cours. رتّبها حسب الاسم ، ولا تعرض إلّا اسمها وصحّتها وعدد بطاقاتها وحجمها. » أربعة أسطر ، أربعة أعمدة ، كلّ ما يحتاجه الأمر ولا أكثر. هذا الاستعلام الذي يجب الاحتفاظ به لفحص المختبر بنظرة واحدة.
يبدو معقّدًا لأنّه طويل ، لكنّه فقط E3 مع ثلاثة خيارات إضافيّة. التفصيل ، مقطعًا بمقطع :
| المقطع | بوضوح |
|---|---|
GET | أطلب معلومة ، لا أُغيّر شيئًا. |
_cat/indices | بطاقات الأدراج ، بجدول مقروء. |
/acces,avis,cours | هذه الأدراج الثلاثة فقط (أسماء مفصولة بفواصل ، بلا مسافة). درج Kibana الداخليّ يختفي. |
?v | مع عناوين الأعمدة. |
&s=index | s كما في sort : يرتّب الأسطر حسب الترتيب الأبجديّ لعمود index. |
&h=index,health,docs.count,store.size | h كما في headers : يعرض فقط هذه الأعمدة ، بهذا الترتيب. |
قاعدة واحدة فقط للخيارات : الأولى تبدأ بـ ? ، كلّ ما يليها بـ &. لهذا نقرأ ?v&s=…&h=….
والأعمدة الأربعة المُختارة :
| العمود | بوضوح |
|---|---|
index | اسم الدرج. |
health | صحّته : green كلّ شيء يعمل ؛ yellow البطاقات موجودة لكن نسخ احتياطيّة ناقصة ؛ red بعض البطاقات غير متوفّرة. |
docs.count | عدد البطاقات التي يحتويها. |
store.size | الحجم الذي يشغله على القرص. |
بجملة واحدة : أدراجك الثلاثة مفتوحة ، في صحّة جيّدة ، وتحتوي بالضبط على البطاقات المتوقّعة ، 12000 و609 و504.
بدون h= ، يُعيد Elasticsearch أحد عشر عمودًا لا يُعلّمك أغلبها شيئًا يوميًّا. بدون s= ، ترتيب الأسطر عشوائيّ ويتغيّر من استدعاء إلى آخر (انظر E2 : كان avis يأتي قبل acces). بتسمية الأدراج الثلاثة ، تستثني أيضًا درج Kibana. النتيجة تتّسع في أربعة أسطر وتُقارَن بنظرة واحدة مع الأرقام المتوقّعة. هذا بالضبط ما يفعله أمر etat من الحزمة خلف الكواليس.
بديل مفيد إذا أردت أيضًا رؤية أنّ الأدراج مفتوحة : أضف status إلى قائمة h= :
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,status,docs.count,store.sizeindex health status docs.count store.size
acces green open 12000 1.5mb
avis green open 609 74.6kb
cours green open 504 183.6kbحصيلة E1 إلى E4 : لم تقرأ بعد أيّ بطاقة. نظرت فقط إلى البطاقات المُلصَقة على الأدراج. تعرف أنّ هناك 504 دورة ، لكنّك لم ترَ بعد عنوان دورة واحدة. هذا ما نفعله الآن.
GET cours/_count{
"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.
GET cours/_searchما يطلبه الاستعلام : « افتح درج cours وأظهر لي البطاقات الموجودة فيه. » هذه أوّل مرّة ترى دورة حقيقيّة : عنوانها ، سعرها ، معلّمها.
الاستجابة طويلة : هذا طبيعيّ ، تحتوي على عشر بطاقات كاملة. لا يعطيك Elasticsearch إلّا العشر الأوّل ، حتّى لو كان الدرج يحتوي على 504 ؛ هذه حماية ، لعدم إرسال 504 بطاقة دفعة واحدة دون أن تطلب ذلك. انظر إلى البنية أكثر من المحتوى :
{
"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 مللي ثانية). |
hits.total.value | العدد الإجماليّ للبطاقات المطابقة : 504. حتّى إذا لم يُعرَض لك إلّا 10 ، يقول لك كم يوجد في المجموع. |
hits.hits | قائمة البطاقات المعروضة لك : العشر الأوّل افتراضيًّا. |
_index | الدرج الذي أتت منه البطاقة (cours). |
_id | الرقم المكتوب في أعلى البطاقة (C0001، C0002…). |
_score | درجة صلة. تساوي 1 في كلّ مكان هنا لأنّنا لم نبحث عن شيء محدَّد : كلّ البطاقات متساوية. |
_source | البطاقة نفسها ، كما رُتِّبت في الدرج ، بكلّ خاناتها. |
الكلمة hit تعني « إصابة » ، كما في الرمي : بطاقة « أُصيبت » بالاستعلام. الحقل professeur هو بطاقة داخل البطاقة (JSON داخل JSON) : المعلّم مكتوب مباشرة على بطاقة الدورة ، باسمه ومدينته. SQL لا يفعل ذلك طبيعيًّا ، سيحتاج جدولًا ثانيًا وربطًا.
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.
في HTTP الكلاسيكيّ ، لا يمتلك GET متنًا ؛ يقبله Elasticsearch مع ذلك لأنّه عمليّ في Dev Tools. إذا رفضته أداة ، فإنّ POST cours/_search بالمتن نفسه يفعل الشيء نفسه بالضبط. حدّ آخر يجب معرفته : لا يمكن أن يتجاوز size قيمة 10000 دفعة واحدة (المعامل index.max_result_window) ؛ للتصفّح أكثر ، نُصفّح (pagination). لدوراتنا الـ 504 ، سيعمل "size": 504، لكنّ الاستجابة ستصبح آلاف الأسطر : هذه ليست الطريقة التي نقرأ بها البيانات ، التجميعات (E13) مصمَّمة لذلك.
GET cours/_search
{
"size": 3,
"_source": ["titre", "prix"]
}"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 في تقريبًا كلّ الاستعلامات التالية ، بالضبط لجعل الاستجابات قصيرة.
GET cours/_doc/C0001{
"_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 »، لكريم كارون : سنجدها في Neo4j بعد قليل ، لإثبات أنّ المحرّكين يحتويان على نفس البيانات.
_id (بشرطة سفليّة) هو المعرّف التقنيّ للمستند في Elasticsearch ؛ id (بلا شرطة) هو حقل عاديّ داخل _source. جعلتهما الحزمة متطابقين عمدًا (C0001 من الجانبين) لتبقى الأمور مقروءة. هذا أيضًا ما يجعل importer قابلًا لإعادة التنفيذ : إرسال مستند مرّتين بنفس _id يستبدل الأوّل بدل إنشاء ثانٍ ، من هنا _version: 6 (أُعيدت كتابة المستند ستّ مرّات على جهاز الدورة ، دون أن يتكرّر أبدًا).
GET cours/_search
{
"query": { "match": { "titre": "kubernetes" } },
"_source": ["titre"]
}"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 لم كان سيفعل ذلك.
في لحظة فهرسة دورة ، يُقسّم Elasticsearch عنوانها إلى كلمات ، يحوّلها إلى حروف صغيرة ، يزيل الحروف الخاصّة ويُعيد كلّ كلمة إلى جذرها (« conteneurs » تصبح « conteneur ») : هذا هو التحليل ، يُنفَّذ هنا بواسطة المحلّل french المُعرَّف في ربط الحزمة. عندما تبحث ، يخضع استعلامك للمعالجة نفسها ، ثم يقارن Elasticsearch كلمة بكلمة. النتيجة : الحروف الكبيرة والحروف الخاصّة والجموع لا تُحسَب بعد الآن. يرتفع _score عندما تكون الكلمة نادرة في الفهرس ومتكرّرة في المستند. تخصّص الوحدة 3 عدّة دروس لهذا ؛ هنا ، احتفظ فقط بـ : match يبحث عن كلمات ، لا عن تتابعات حروف.
أوّلًا الدليل على أنّه ، بشكل افتراضيّ ، خطأ حقيقيّ لا يجد شيئًا. « kubrenetes » (حرفان معكوسان) :
GET cours/_count
{
"query": { "match": { "titre": "kubrenetes" } }
}{ "count": 0, … }ثم نفس الاستعلام بسطر واحد إضافيّ ، fuzziness، الذي يسمح بفرق حرف أو حرفين :
GET cours/_search
{
"query": { "match": { "titre": { "query": "kubrenetes", "fuzziness": "AUTO" } } },
"size": 3,
"_source": ["titre", "niveau", "prix"]
}"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 } }
]تعود الدورات السبع لـ Kubernetes ، رغم الخطأ. هذه اللحظة التي تُفاجئ دائمًا في قاعة الدرس ، وهي سبب وجود Elasticsearch في شريط بحث : يكتب المستخدم خطأ ، يفهم المحرّك مع ذلك. LIKE '%kubrenetes%' بـ SQL ما كان سيُعيد شيئًا أبدًا. لاحظ الصيغة : عندما يحتاج match إلى خيارات ، تصبح قيمة الحقل كائنًا { "query": …, "fuzziness": … } عوض سلسلة نصّيّة بسيطة.
يعدّ fuzziness التعديلات (حرف مُضاف أو مُزال أو مُغيَّر أو مُبدَّل مع جاره) التي نتسامح بها بين الكلمة المكتوبة والكلمة المفهرسة. AUTO يُكيّف التسامح مع طول الكلمة : 0 خطأ لكلمة من 1 أو 2 حرف ، خطأ واحد من 3 إلى 5 أحرف ، خطآن فما فوق. « kubrenetes » تحتوي 10 أحرف ، إذن يُسمَح بخطأين ؛ لا تحتوي إلّا واحدًا (تبديل re ↔ er) : وُجدت. الـ _score أقلّ قليلًا من E10 (4.45 مقابل 5.09) : يُعاقِب Elasticsearch قليلًا التطابقات التقريبيّة ، ما يُبقي التطابقات الدقيقة في المقدّمة.
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 : الصلة لا تُستخدَم بعد الآن ، ترتيبك هو ما يُهمّ.
GET cours/_search
{
"size": 0,
"aggs": {
"par_categorie": { "terms": { "field": "categorie" } }
}
}"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 ، كلّ رسم دائريّ ، كلّ مدرّج تكراريّ هو تجميع كهذا ، يُنفَّذه Elasticsearch ويرسمه Kibana. عندما تبني لوحة مراقبة في الوحدة 4 ، ستضغط على « champ : categorie, agrégation : terms » وسيُرسل Kibana بالضبط هذا الاستعلام. معرفة قراءته يعني معرفة ما تفعله لوحة المراقبة خلف الكواليس.
GET cours/_search
{
"size": 0,
"aggs": {
"par_categorie": {
"terms": { "field": "categorie" },
"aggs": { "par_niveau": { "terms": { "field": "niveau" } } }
}
}
}نفس استعلام E13 ، مع aggs ثانٍ داخل الأوّل. في كلّ حزمة فئة ، نُعيد إنشاء حزم حسب niveau. النتيجة : لـ « Cloud »، 45 مبتدئ ، 28 متقدّم ، 11 متوسّط ؛ وكذلك للفئات الستّ ، في مرور واحد. بـ SQL : GROUP BY categorie, niveau، لكنّ الاستجابة هرميّة بالفعل ، جاهزة لرسم بيانيّ مكدَّس.
GET acces/_search
{
"size": 0,
"query": { "range": { "statut": { "gte": 500 } } },
"aggs": { "codes": { "terms": { "field": "statut" } } }
}"hits": { "total": { "value": 178, "relation": "eq" } },
"aggregations": {
"codes": {
"buckets": [
{ "key": 500, "doc_count": 122 },
{ "key": 503, "doc_count": 56 }
]
}
}لا شيء جديد : نجمع range (E12) و terms (E13) على فهرس acces. من بين 12000 سطر سجلّ ، 178 لها رمز HTTP ≥ 500 : 122 خطأ 500 و56 503. هذا السؤال الذي يطرحه مسؤول منصّة كلّ صباح ، ويُجاب عليه في ميلّي ثانية واحدة. بـ SQL : SELECT statut, COUNT(*) FROM acces WHERE statut >= 500 GROUP BY statut. هذا بالضبط ما ستضعه على لوحة مراقبة في الوحدة 4.
افتح http://localhost:7474. شاشة الاتّصال : URL neo4j://localhost:7687، المستخدم neo4j، كلمة السرّ aiopsatlas2026. في الأعلى ، شريط تحرير يبدأ بـ neo4j$ : الصق استعلامًا واحدًا فقط ، ثم Ctrl+Enter أو المثلّث ▶. تظهر النتيجة في إطار تحته ، مع تبويبات إلى اليسار : Graph (رسم ، عندما تحتوي النتيجة على عقد) ، Table (أسطر وأعمدة) و Text.
MATCH (n) RETURN nالنتيجة سحابة من الفقّاعات الملوَّنة ، مرتبطة بأسهم ، يمكنك تحريكها بالفأرة. يعرض Neo4j Browser 300 عقدة على الأكثر في المرّة الواحدة (رسالة في أعلى النتيجة تُشير إلى ذلك) ؛ الـ 872 موجودة بالفعل ، لا يرسم إلّا جزءًا منها لتبقى مقروءة.
هذا أبسط استعلام في Cypher ، لغة Neo4j. MATCH تعني « اعثر » ، (n) تُشير إلى عقدة عشوائيّة (تُرسم الأقواس دائرة ، كفقّاعة) نُسمّيها n، و RETURN n تعني « أظهرها لي ». في SQL ، لا يوجد معادل : سيكون « SELECT * لكلّ الجداول دفعة واحدة »، وهو ما لا تعرف SQL كيف تفعله.
الرسم البيانيّ مبنيّ من شيئين : عقد (الفقّاعات) و علاقات (الأسهم بين الفقّاعات). تحمل كلّ عقدة علامة (étiquette) تقول ما هي (Cours, Etudiant…) و خصائص (العنوان ، السعر…). تحمل كلّ علاقة نوعًا (INSCRIT_A, ENSEIGNE…) واتّجاهًا.
| Neo4j | SQL |
|---|---|
العلامة Cours | جدول cours |
| عقدة | صفّ |
| خاصيّة | عمود |
علاقة INSCRIT_A | جدول ربط inscriptions + عمليّات ربط |
رسم بيانيّ المختبر :
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 في فهرس Elasticsearch cours : إنّهما محرّكان يُرتّبان نفس البيانات بطريقتين ، كلّ واحد للإجابة على أسئلة مختلفة.
MATCH (n) RETURN n LIMIT 25جديد واحد فقط : LIMIT 25، نفس الكلمة كما في SQL. خمس وعشرون فقّاعة عوض ثلاثمائة : نرى أخيرًا شيئًا. مرّر الفأرة على فقّاعة : تظهر خصائصها في أسفل الإطار. اضغط مزدوجًا عليها : تنفتح جيرانها.
MATCH (n) RETURN count(n) AS totaltotal
872count(n) يعدّ عوض أن يرسم ؛ AS total يُسمّي العمود. لا رسم بعد الآن : تنتقل النتيجة تلقائيًّا إلى معاينة Table ، بما أنّ رقمًا لا يُرسَم. 872 ، رقم etat. بـ SQL : SELECT COUNT(*).
MATCH (c:Cours) RETURN c LIMIT 5الجديد : :Cours بعد اسم المتغيّر. هذه العلامة : « فقط العقد التي هي دورات ». خمس فقّاعات ، كلّها بالألوان نفسها. بـ SQL : SELECT * FROM cours LIMIT 5. من باب العادة ، نُسمّي المتغيّر بالحرف الأوّل من العلامة (c لـ Cours، e لـ Etudiant) ، لكن n سيعمل أيضًا.
MATCH (c:Cours) RETURN c.titre, c.prix LIMIT 5c.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.
MATCH (n) RETURN labels(n)[0] AS type, count(*) AS nombre ORDER BY nombre DESCtype nombre
"Cours" 504
"Etudiant" 300
"Professeur" 30
"Competence" 22
"Ville" 16labels(n) يُعيد قائمة علامات العقدة (يمكن أن تحمل عقدة عدّة علامات ؛ هنا واحدة فقط ، من هنا [0]، العنصر الأوّل). يجمّع count(*) نفسه تلقائيًّا حسب كلّ ما ليس تجميعًا : لا حاجة لكتابة GROUP BY، تستنتجه Cypher. ORDER BY nombre DESC يرتّب. مجموع الأسطر الخمسة يساوي 872. بـ SQL ، سنحتاج خمس SELECT COUNT(*) و UNION.
MATCH (c:Cours {id: 'C0001'}) RETURN c.titre, c.prix, c.niveauc.titre c.prix c.niveau
"Docker expliqué simplement" 129.0 "debutant"الجديد : الأقواس المُعقَّفة {id: 'C0001'} في النموذج. تُرشّح على خاصيّة ، كـ WHERE id = 'C0001'. وهي نفس الدورة كما في E9 في Elasticsearch : نفس العنوان ، نفس السعر. هذا الدليل على أنّ المحرّكين يحتويان بالفعل على نفس البيانات ؛ ما يتغيّر هو ما يمكن أن نطلبه منهما.
MATCH (c:Cours) WHERE c.titre CONTAINS 'Kubernetes' RETURN c.titre ORDER BY c.titrec.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 تبحث عن تتابع حروف. سبع دورات ، نفسها كما في E10. لكن جرّب CONTAINS 'kubernetes' بحروف صغيرة : صفر نتيجة. و CONTAINS 'kubrenetes' : صفر أيضًا. يُقارِن Neo4j الحروف ، بالضبط ؛ لا يعرف لا الحالة ولا الكلمات ولا الأخطاء. لهذا بالضبط يمتلك المختبر المحرّكين : شريط البحث ، هو Elasticsearch ؛ الروابط بين الأشياء ، هو Neo4j.
MATCH (c:Cours) RETURN c.titre, c.prix ORDER BY c.prix DESC LIMIT 5c.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 دولار.
MATCH (c:Cours) RETURN c.categorie AS categorie, count(*) AS nombre ORDER BY nombre DESCcategorie nombre
"DevOps" 84
"Données" 84
"IA" 84
"Développement web" 84
"Sécurité" 84
"Cloud" 84نفس نتيجة التجميع E13 بالضبط في Elasticsearch : ستّ فئات من 84. نفس البيانات ، محرّكان ، صيغتان. حتّى الآن ، لم يفعل Neo4j شيئًا لا تعرف SQL فعله. يتغيّر هذا في الاستعلام التالي.
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 ، لا توجد علاقة بين سطرين بشكل حقيقيّ فعلًا : تُحسَب من جديد عند كلّ استعلام بواسطة ربط (jointure) ، يُقارِن معرّفات. مع عشر عمليّات ربط متتالية ، يصبح ذلك بطيئًا وغير مقروء. في Neo4j ، العلاقة مُخزَّنة كسهم فعليّ بين عقدتين : اتّباع سهم يكلّف السعر نفسه بغضّ النظر عن عدد العقد في القاعدة. هذا ما يجعل الاستعلامات N14 إلى N16 ممكنة ، والتي تتابع عدّة قفزات دون جهد.
MATCH ()-[r]->() RETURN type(r) AS relation, count(*) AS nombre ORDER BY nombre DESCrelation nombre
"INSCRIT_A" 1654
"COUVRE" 994
"ENSEIGNE" 504
"HABITE" 330
"PREREQUIS_DE" 230() عقدة عشوائيّة لا نحتفظ حتّى باسمها ؛ [r] علاقة من أيّ نوع كان ؛ type(r) نوعها. خمسة أنواع من العلاقات ، 3712 إجمالًا : الرقم المعروض في لوحة Database information في Neo4j Browser. نقرأ نموذج المختبر في سطر واحد : طلاب مسجّلون في دورات ، دورات تغطّي مهارات ، معلّمون يعلّمون دورات ، أشخاص يعيشون في مدن ، ودورات متطلّبة لـ دورات أخرى. ENSEIGNE عند 504 يقول أنّ هناك بالضبط علاقة واحدة لكلّ دورة : كلّ دورة لها معلّم واحد فقط لا غير.
MATCH (p:Professeur {id: 'P001'})-[:ENSEIGNE]->(c:Cours)
RETURN p.prenom + ' ' + p.nom AS professeur, count(c) AS nb_coursprofesseur nb_cours
"Karim Caron" 11نجمع N7 (المرشّح {id: 'P001'}) وN11 (السهم). P001، هو معلّم « Docker expliqué simplement » الذي رأيناه في E9 وN7 ؛ يُعلّم 11 دورة. الـ + يلصق سلاسل الحروف ، كما في SQL بـ ||. لاحظ [:ENSEIGNE] بلا اسم متغيّر : عندما لا نحتاج إلى العلاقة في RETURN، لا نُسمّيها.
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 parcourssauts 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 ، سنحتاج استعلامًا تكراريًّا من عشرات الأسطر ، وسيكون بطيئًا.
nodes(chemin) يُعطي قائمة العقد المُتقاطَعة. [n IN nodes(chemin) | n.titre] تُقرَأ « لكلّ عقدة n من هذه القائمة ، احتفظ بعنوانها » : هذه طريقة مضغوطة لتحويل قائمة عقد إلى قائمة عناوين. length(chemin) يعدّ العلاقات المُتقاطَعة (5 علاقات لـ6 عقد). تُفصّل الوحدة 6 هذه الدوالّ ؛ هنا المهمّ هو النتيجة : وجد Neo4j مسارًا في الرسم البيانيّ.
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 5recommandation 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 : عمليّتا ربط على جدول التسجيلات ، GROUP BY، واستعلام نقرؤه ثلاث مرّات قبل فهمه.
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 أيضًا ؛ ثم التسامح مع خطأ إملائيّ ، تجميع 12000 سطر في ميلّي ثانية واحدة ، حساب أقصر مسار وإنتاج توصيات ، ما تفعله SQL بشكل سيّء أو لا تفعله أبدًا. ظهرت نفس الدورة « Docker expliqué simplement » في المحرّكين : نفس البيانات ، أسئلة مختلفة. هذا بالضبط سبب وجود هذا المختبر ، وبقيّة الدورة كلّها تُفصّل كيف يعمل كلّ من هذه الاستعلامات.
أعِد الخطوة 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 عقدة موجودة دائمًا دون إعادة تحميل أيّ شيء ، ولاحظ أيّ من الخدمتين تُعيد التشغيل بشكل أسرع.
كلّ أوامر هذا الملحق تُكتَب في PowerShell (Windows Terminal، أو PowerShell 7)، بـ .\labo.ps1 …. المخرجات المُعاد إنتاجها هي مخرجات جهاز الدورة ، تحت Windows 11 وDocker Desktop.
docker-compose.yml و labo.ps1)..\labo.ps1 (« l'exécution de scripts est désactivée sur ce système ») : 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. لا واحد منها يكتب في المجموعة.قبل تشغيل أيّ شيء ، دع النصّ البرمجيّ يتحدّث.
.\labo.ps1 prerequisنقطة التحقّق : فقط علامات صحّ خضراء والجملة الأخيرة.
== 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 جيجابايت ، منفذ محجوز) ؛ صحّح ، أعد التشغيل ، لا تتقدّم إلّا مع السطر الأخضر. للذاكرة : Docker Desktop → Settings → Resources، أو الملفّ %UserProfile%\.wslconfig إذا كان Docker Desktop يستخدم WSL 2.
شغّل البدء وهذه المرّة ، اقرأ ما يمرّ عوض انتظار النهاية.
.\labo.ps1 demarrerنقطة التحقّق : ثلاث فقرات == … == ثم ثلاثة prêt.
== 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)).
etatستكتب etat عدّة مرّات ؛ تعلّم أوّلًا استخراج الأرقام الأربعة المهمّة منها.
.\labo.ps1 etatنقطة التحقّق : مخرجات حقيقيّة من جهاز الدورة ، حيث ملف OpenSearch للوحدة 5 مفعَّل. عندك ، السطران labo-opensearch… و ✔ OpenSearch يُستبدَلان بـ — OpenSearch non démarré (profil optionnel : .\labo.ps1 demarrer opensearch).
== 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 إنشاء فهارسه الداخليّة ؛ أعد كتابة etat بعد ثلاثين ثانية.
حمّل الفهارس ثم الرسم البيانيّ ، و أعد تشغيل الأمرين مرّة ثانية : لا يجب أن تتغيّر العدّادات بوحدة واحدة.
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer
.\labo.ps1 charger-grapheنقطة التحقّق : في المرور الثاني ، يُشير importer إلى أنّ الفهارس موجودة بالفعل ويُعيد نفس العدّادات :
== 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 نفس الحصيلة كالمرّة الأولى :
== 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.تحمل المستندات معرّفها الخاصّ (C0001، A00001…) ، لذا _bulk ثانٍ يستبدل كلّ مستند عوض إضافته ؛ من جانب Neo4j ، كلّ شيء بـ MERGE. فقط store.size قد يتحرّك بعدّة كيلوبايت (يحتفظ Lucene بالإصدارات القديمة لبعض الوقت) ؛ docs.count، لا تتغيّر أبدًا.
إذا رأيت شيئًا آخر : docs.count مختلف عن 504 / 609 / 12000 → كتب شخص في هذه الفهارس ؛ .\labo.ps1 reinitialiser ثم demarrer و importer و charger-graphe تُعيد لك الحالة المرجعيّة.
يقول النصّ البرمجيّ أنّ كلّ شيء جيّد ؛ تحقّق منه بدونه. افتح http://localhost:5601، القائمة ☰ → Management → Outils de développement، وأرسل هذه الاستعلامات واحدًا تلو الآخر (Ctrl + Enter أو الزرّ ▶ « اضغط لإرسال الاستعلام »).
GET _cluster/healthنقطة التحقّق : "status": "green"، "unassigned_shards": 0، و 200 - OK في أسفل يمين لوحة الاستجابة.
{
"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
}GET _cat/indices/cours,avis,acces?v&s=indexنقطة التحقّق : ثلاثة أسطر green، pri 1، rep 0، والعدّادات الثلاثة في docs.count.
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" → فهرس لديه نسخ احتياطيّة غير مُخصَّصة ، مستحيل مع ربط الحزمة (number_of_replicas: 0) ؛ GET _cat/indices?v&health=yellow يُحدّد المتسبّب ، عادةً فهرس أُنشئ يدويًّا.
نفس التمرين لـ Neo4j. افتح http://localhost:7474، تسجيل الدخول (neo4j / aiopsatlas2026، URL localhost:7687)، اكتب في المحرّر neo4j$ واضغط Run :
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.
label count(*)
"Competence" 22
"Cours" 504
"Etudiant" 300
"Professeur" 30
"Ville" 1622 + 504 + 300 + 30 + 16 = 872، رقم etat. تعرض لوحة Database information (أيقونة Database overview، الأولى في الشريط الجانبيّ) Nodes (872) و Relationships (3,712).
إذا رأيت شيئًا آخر : علامة سادسة غير معروفة → عقد أُنشئت خارج الحزمة (تُعلّمك الوحدة 6 كيفيّة حذفها بشكل نظيف) ؛ Nodes (0) → تجاوزت charger-graphe، رجوع إلى الخطوة A.4.
تعرف كيف يبدو مختبر سليم ؛ أثِر عطلًا تعرف سببه لتتعلّم قراءته. أوقف فقط Kibana ، بـ Compose (لا arreter، الذي سيوقف كلّ شيء) :
docker compose stop kibana Container labo-kibana Stopping
Container labo-kibana Stoppedثم الحركات الثلاث من الدرس 04 ، بالترتيب : etat، المتصفّح ، journal.
نقطة التحقّق 1، .\labo.ps1 etat : اختفى سطر labo-kibana من فقرة == Conteneurs == (لا يعرض Compose افتراضيًّا إلّا الحاويات قيد التشغيل) وتضع فقرة == Services == علامة خطأ :
== 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 :
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… بين الاثنين هي كومة استدعاءات (pile d'appels) يُصدرها ملحق أثناء الإيقاف : ضجيج.
إذا رأيت شيئًا آخر : no configuration file provided: not found → لست في مجلّد الحزمة ؛ no such service: kibana → كتبت اسم الحاوية (labo-kibana) عوض اسم خدمة Compose (kibana).
أعد تشغيل الخدمة وانتظر عودة فحصها الصحّيّ إلى الأخضر : 40 إلى 60 ثانية ، الوقت اللازم لإعادة الاتّصال بـ Elasticsearch والتحقّق من فهارسه الداخليّة.
docker compose start kibana 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. راقب كلّ خمس ثوانٍ :
docker inspect --format '{{.State.Health.Status}}' labo-kibanaنقطة التحقّق : starting لمدّة 40 إلى 50 ثانية ، ثم healthy (على جهاز الدورة : starting من 0 إلى 45 ثانية ، healthy عند 50 ثانية). يُظهر etat بعد ذلك السطر :
labo-kibana Up 52 seconds (healthy) 0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
…
✔ Kibana répond (http://localhost:5601)وينتهي journal kibana بالأسطر التي نريد رؤيتها :
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 نفسه :
GET _cluster/health?filter_path=status,number_of_nodes{
"status": "green",
"number_of_nodes": 1
}أجاب Elasticsearch بـ green طوال العطل : Kibana نافذة على البيانات ، ليس البيانات. إيقاف Kibana لم يحذف ولم يُعيد فهرسة أيّ شيء.
إذا رأيت شيئًا آخر : unhealthy بعد دقيقتين → journal kibana وابحث عن ECONNREFUSED (تعطّل Elasticsearch في الوقت نفسه) ؛ port is already allocated → برنامج آخر أخذ المنفذ 5601 خلال الإيقاف (الدرس 04 ، العطل 1).
تتعرّف على خدمة متوقّفة ؛ تعلّم الآن التعرّف على استعلام خاطئ على خدمة سليمة، الالتباس الأكثر شيوعًا في قاعة الدرس. في Dev Tools ، اكتب استعلام بحث يُعيد 404 مع "type": "index_not_found_exception"، ثم اشرح بجملة واحدة لماذا تبقى المجموعة green.
مؤشّر : لا يُخمّن Elasticsearch أبدًا اسم فهرس. اختر واحدًا غير موجود ، بالبادئة pratique- ؛ لا شيء يُنشَأ ، لا شيء يُكتَب.
GET pratique-inexistant/_searchالاستجابة ، مع شارة 404 - Not Found في أسفل يمين لوحة المخرجات :
{
"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 : « فهمت استعلامك ، لكن هذا المورد غير موجود ». الخدمة تعمل ، تبقى المجموعة green ؛ لا يوجد إلّا الاسم يجب تصحيحه (GET _cat/indices?v يعطي القائمة). في الخطوة A.7 ، لم كن هناك أيّ استجابة على الإطلاق.
بديلان يجب تجربتهما : GET pratique-inexistant/_count?ignore_unavailable=true يُعيد 200 و "count": 0 (تطلب تجاهل الفهرس الغائب) ؛ GET cours/_serch يُعيد 400 مع "no handler found for uri [/cours/_serch] and method [GET]". الـ 400 : « لا أفهم الاستعلام » ؛ الـ 404 : « أفهم ، لكن هذا غير موجود ».
أمر واحد يُثبت أنّ كلّ شيء تمّ : etat، مع رجوع Kibana والأرقام الأربعة.
.\labo.ps1 etat== 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 الثاني.etat والمتصفّح و journal kibana عندما تكون Kibana متوقّفة ، وكيف يختلف ذلك عن 404.etat أعلاه (نسخة أو لقطة شاشة) كتسليمة.هذا التطبيق لا يُنشئ شيئًا : لا فهرس ، لا عقدة ، لا كائن Kibana. شيئان يجب ضمانهما : أنّ Kibana يعمل (وإلّا docker compose start kibana من مجلّد الحزمة) ، وأنّه لا يوجد فهرس عمل مُتبقٍّ :
GET _cat/indices/pratique-*?vالاستجابة المتوقَّعة : سطر العنوان وحده (health status index uuid pri rep docs.count …). لا تلمس cours أو avis أو acces ولا الرسم البيانيّ : تخدم كلّ الوحدات التالية.
كلّ أوامر هذا الملحق تُكتَب في طرفيّة bash (أو zsh)، بـ ./labo.sh …. المخرجات مطابقة لتلك الخاصّة بـ Windows باستثناء اسم النصّ البرمجيّ : الحزمة نفسها ، فقط المُشغِّلات تتغيّر.
docker على Linux : يجب أن يُجيب docker info)، الحزمة منسوخة ، طرفيّة مفتوحة في مجلّد الحزمة (الذي يحتوي على docker-compose.yml و labo.sh).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. لا واحد منها يكتب في المجموعة.قبل تشغيل أيّ شيء ، دع النصّ البرمجيّ يتحدّث.
./labo.sh prerequisنقطة التحقّق : فقط علامات صحّ خضراء والجملة الأخيرة.
== 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 جيجابايت ، منفذ محجوز) ؛ صحّح ، أعد التشغيل ، لا تتقدّم إلّا مع السطر الأخضر. تحت Linux الأصليّ ، الذاكرة هي ذاكرة الجهاز ؛ تحت macOS وWSL 2، هي تلك المُخصَّصة في Docker Desktop → Settings → Resources.
شغّل البدء وهذه المرّة ، اقرأ ما يمرّ عوض انتظار النهاية.
./labo.sh demarrerنقطة التحقّق : ثلاث فقرات == … == ثم ثلاثة prêt.
== 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 الأصليّ ، vm.max_map_count منخفض جدًّا (sudo sysctl -w vm.max_map_count=262144، ثم ./labo.sh demarrer).
etatستكتب etat عدّة مرّات ؛ تعلّم أوّلًا استخراج الأرقام الأربعة المهمّة منها.
./labo.sh etatنقطة التحقّق : مخرجات حقيقيّة من جهاز الدورة ، حيث ملف OpenSearch للوحدة 5 مفعَّل. عندك ، السطران labo-opensearch… و ✔ OpenSearch يُستبدَلان بـ — OpenSearch non démarré (profil optionnel : ./labo.sh demarrer opensearch).
== 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 إنشاء فهارسه الداخليّة ؛ أعد كتابة etat بعد ثلاثين ثانية.
حمّل الفهارس ثم الرسم البيانيّ ، و أعد تشغيل الأمرين مرّة ثانية : لا يجب أن تتغيّر العدّادات بوحدة واحدة.
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer
./labo.sh charger-grapheنقطة التحقّق : في المرور الثاني ، يُشير importer إلى أنّ الفهارس موجودة بالفعل ويُعيد نفس العدّادات :
== 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 نفس الحصيلة كالمرّة الأولى :
== 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.تحمل المستندات معرّفها الخاصّ (C0001، A00001…) ، لذا _bulk ثانٍ يستبدل كلّ مستند عوض إضافته ؛ من جانب Neo4j ، كلّ شيء بـ MERGE. فقط store.size قد يتحرّك بعدّة كيلوبايت (يحتفظ Lucene بالإصدارات القديمة لبعض الوقت) ؛ docs.count، لا تتغيّر أبدًا.
إذا رأيت شيئًا آخر : docs.count مختلف عن 504 / 609 / 12000 → كتب شخص في هذه الفهارس ؛ ./labo.sh reinitialiser ثم demarrer و importer و charger-graphe تُعيد لك الحالة المرجعيّة.
يقول النصّ البرمجيّ أنّ كلّ شيء جيّد ؛ تحقّق منه بدونه. افتح http://localhost:5601، القائمة ☰ → Management → Outils de développement، وأرسل هذه الاستعلامات واحدًا تلو الآخر (Ctrl + Enter، أو Cmd + Enter على macOS، أو الزرّ ▶ « اضغط لإرسال الاستعلام »).
GET _cluster/healthنقطة التحقّق : "status": "green"، "unassigned_shards": 0، و 200 - OK في أسفل يمين لوحة الاستجابة.
{
"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
}GET _cat/indices/cours,avis,acces?v&s=indexنقطة التحقّق : ثلاثة أسطر green، pri 1، rep 0، والعدّادات الثلاثة في docs.count.
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 ، لديك حتّى مسار ثالث ، بلا متصفّح :
curl -s 'http://localhost:9200/_cat/indices/cours,avis,acces?v&s=index'إذا رأيت شيئًا آخر : "status": "yellow" → فهرس لديه نسخ احتياطيّة غير مُخصَّصة ، مستحيل مع ربط الحزمة (number_of_replicas: 0) ؛ GET _cat/indices?v&health=yellow يُحدّد المتسبّب ، عادةً فهرس أُنشئ يدويًّا.
نفس التمرين لـ Neo4j. افتح http://localhost:7474، تسجيل الدخول (neo4j / aiopsatlas2026، URL localhost:7687)، اكتب في المحرّر neo4j$ واضغط Run :
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.
label count(*)
"Competence" 22
"Cours" 504
"Etudiant" 300
"Professeur" 30
"Ville" 1622 + 504 + 300 + 30 + 16 = 872، رقم etat. تعرض لوحة Database information (أيقونة Database overview، الأولى في الشريط الجانبيّ) Nodes (872) و Relationships (3,712).
إذا رأيت شيئًا آخر : علامة سادسة غير معروفة → عقد أُنشئت خارج الحزمة (تُعلّمك الوحدة 6 كيفيّة حذفها بشكل نظيف) ؛ Nodes (0) → تجاوزت charger-graphe، رجوع إلى الخطوة B.4.
تعرف كيف يبدو مختبر سليم ؛ أثِر عطلًا تعرف سببه لتتعلّم قراءته. أوقف فقط Kibana ، بـ Compose (لا arreter، الذي سيوقف كلّ شيء) :
docker compose stop kibana Container labo-kibana Stopping
Container labo-kibana Stoppedثم الحركات الثلاث من الدرس 04 ، بالترتيب : etat، المتصفّح ، journal.
نقطة التحقّق 1، ./labo.sh etat : اختفى سطر labo-kibana من فقرة == Conteneurs == (لا يعرض Compose افتراضيًّا إلّا الحاويات قيد التشغيل) وتضع فقرة == Services == علامة خطأ :
== 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 :
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… بين الاثنين هي كومة استدعاءات يُصدرها ملحق أثناء الإيقاف : ضجيج.
إذا رأيت شيئًا آخر : no configuration file provided: not found → لست في مجلّد الحزمة ؛ no such service: kibana → كتبت اسم الحاوية (labo-kibana) عوض اسم خدمة Compose (kibana).
أعد تشغيل الخدمة وانتظر عودة فحصها الصحّيّ إلى الأخضر : 40 إلى 60 ثانية ، الوقت اللازم لإعادة الاتّصال بـ Elasticsearch والتحقّق من فهارسه الداخليّة.
docker compose start kibana 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. راقب كلّ خمس ثوانٍ :
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 بعد ذلك السطر :
labo-kibana Up 52 seconds (healthy) 0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
…
✔ Kibana répond (http://localhost:5601)وينتهي journal kibana بالأسطر التي نريد رؤيتها :
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 نفسه :
GET _cluster/health?filter_path=status,number_of_nodes{
"status": "green",
"number_of_nodes": 1
}أجاب Elasticsearch بـ green طوال العطل : Kibana نافذة على البيانات ، ليس البيانات. إيقاف Kibana لم يحذف ولم يُعيد فهرسة أيّ شيء.
إذا رأيت شيئًا آخر : unhealthy بعد دقيقتين → journal kibana وابحث عن ECONNREFUSED (تعطّل Elasticsearch في الوقت نفسه) ؛ port is already allocated → برنامج آخر أخذ المنفذ 5601 خلال الإيقاف (الدرس 04 ، العطل 1).
تتعرّف على خدمة متوقّفة ؛ تعلّم الآن التعرّف على استعلام خاطئ على خدمة سليمة، الالتباس الأكثر شيوعًا في قاعة الدرس. في Dev Tools ، اكتب استعلام بحث يُعيد 404 مع "type": "index_not_found_exception"، ثم اشرح بجملة واحدة لماذا تبقى المجموعة green.
مؤشّر : لا يُخمّن Elasticsearch أبدًا اسم فهرس. اختر واحدًا غير موجود ، بالبادئة pratique- ؛ لا شيء يُنشَأ ، لا شيء يُكتَب.
GET pratique-inexistant/_searchالاستجابة ، مع شارة 404 - Not Found في أسفل يمين لوحة المخرجات :
{
"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 : « فهمت استعلامك ، لكن هذا المورد غير موجود ». الخدمة تعمل ، تبقى المجموعة green ؛ لا يوجد إلّا الاسم يجب تصحيحه (GET _cat/indices?v يعطي القائمة). في الخطوة B.7 ، لم كن هناك أيّ استجابة على الإطلاق.
بديلان يجب تجربتهما : GET pratique-inexistant/_count?ignore_unavailable=true يُعيد 200 و "count": 0 (تطلب تجاهل الفهرس الغائب) ؛ GET cours/_serch يُعيد 400 مع "no handler found for uri [/cours/_serch] and method [GET]". الـ 400 : « لا أفهم الاستعلام » ؛ الـ 404 : « أفهم ، لكن هذا غير موجود ».
أمر واحد يُثبت أنّ كلّ شيء تمّ : etat، مع رجوع Kibana والأرقام الأربعة.
./labo.sh etat== 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 الثاني.etat والمتصفّح و journal kibana عندما تكون Kibana متوقّفة ، وكيف يختلف ذلك عن 404.etat أعلاه (نسخة أو لقطة شاشة) كتسليمة.هذا التطبيق لا يُنشئ شيئًا : لا فهرس ، لا عقدة ، لا كائن Kibana. شيئان يجب ضمانهما : أنّ Kibana يعمل (وإلّا docker compose start kibana من مجلّد الحزمة) ، وأنّه لا يوجد فهرس عمل مُتبقٍّ :
GET _cat/indices/pratique-*?vالاستجابة المتوقَّعة : سطر العنوان وحده (health status index uuid pri rep docs.count …). لا تلمس cours أو avis أو acces ولا الرسم البيانيّ : تخدم كلّ الوحدات التالية.
docker compose stop kibana يُجيب بـ no configuration file provided: not found → يبحث Compose عن docker-compose.yml في المجلّد الحاليّ. cd إلى جذر الحزمة (الذي يحتوي على 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] → الخطأ يقع على الواجهة (_serch)، ليس على الفهرس. يتحقّق Elasticsearch أوّلًا من المسار ، ثم الفهرس : صحّح إلى _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 الأصليّ فقط — يخرج Elasticsearch بـ Exited (78) ويقول journal elasticsearch : max virtual memory areas vm.max_map_count [65530] is too low → sudo sysctl -w vm.max_map_count=262144 ثم ./labo.sh demarrer. ليدوم بعد إعادة التشغيل : أضف vm.max_map_count=262144 في /etc/sysctl.conf.
macOS وbash — ./labo.sh يُجيب بـ Permission denied → chmod +x labo.sh، مرّة واحدة فقط. إذا bash: ./labo.sh: /bin/bash^M: bad interpreter، الملفّ يحتوي على نهايات أسطر Windows : git config core.autocrlf input ثم أعد النسخ ، أو sed -i '' 's/\r$//' labo.sh.