تطبيق موجّه — تشغيل والتحقّق والكسر والإصلاح للمختبر

تدريب موجَّه58 دقيقة
المدة
45 إلى 60 دقيقة
الوحدة
1/7
ستبني
مختبرًا مُحمَّلًا ، مُتحقَّقًا منه بثلاثة مسارات (النصّ البرمجيّ و Dev Tools و Neo4j Browser) ، ثم مكسورًا عمدًا ومُصلَحًا
التسليمة
المخرجات الكاملة لـ etat مع (healthy) في كلّ مكان ، 504 / 609 / 12000 مستند و872 عقدة ، بالإضافة إلى سطرين يشرحان العطل الذي تسبّبت به

كيف تقرأ هذه الصفحة. كلّ قسم مطويّ تحت عنوانه : اضغط على « عرض … » لفتحه ، وأغلقه عندما تنتهي لتبقى الصفحة قابلة للقراءة. ترتيب القراءة : الهدف ، ثم بإيجاز (الأوامر التي تكتبها) ، ثم مجموعة البيانات (تُقرأ قبل أيّ استعلام) ، ثم استعلامات 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)

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

تحقّق من العناوين الثلاثة في المتصفّح :

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 (« l'exécution de scripts est désactivée ») : 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

تحقّق من العناوين الثلاثة في المتصفّح :

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

مجموعة البيانات : ما ستتعامل معه

عرض مجموعة البيانات

قبل كتابة استعلام واحد ، انظر إلى البيانات. يدور المختبر كلّه حول منصّة دورات عبر الإنترنت خياليّة : فهرس دورات ، الآراء التي يتركها الطلاب ، سجلّ خادم الويب الذي يخدم الصفحات ، والروابط بين الطلاب والمعلّمين والدورات والمهارات. تُحمَّل نفس البيانات في Elasticsearch (للبحث) وفي Neo4j (لتتبّع الروابط). هي موجودة في الحزمة ، بوضوح ، في مجلّدين :

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 : ثلاثة فهارس ، ثلاثة أنواع من المستندات

ملفّ .ndjson (newline-delimited JSON) يحتوي على كائن JSON واحد لكلّ سطر. في الحزمة ، تأتي الأسطر بالزوج : سطر تعليمة (« ضع ما يلي في الفهرس cours تحت المعرّف C0001 ») ، ثم المستند نفسه. هذا هو التنسيق الذي يرسله أمر importer إلى Elasticsearch بشكل جماعيّ.

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

فهرس 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", …]قائمة المهارات التي تغطّيها الدورة.

فهرس 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الدورة المعنيّة. هذا هو الرابط إلى فهرس cours : هذا C0028 هو id مستند من cours.
etudiantNathanالاسم الأوّل لصاحب الرأي.
ville, paysSherbrooke, Canadaمن أين يكتب.
note3الدرجة المُعطاة ، من 1 إلى 5.
texteLes vidéos sont bonnes…التعليق. نصّ حرّ.
date2024-06-21تاريخ الرأي.
utile33عدد الأشخاص الذين وجدوا هذا الرأي مفيدًا.

فهرس acces : 12000 مستند ، سطر واحد لكلّ طلب استقبله خادم الويب

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 كما في سطر السجلّ (log).
@timestamp2026-08-10T08:17:19.000Zتاريخ ووقت الطلب بالضبط. الـ @ اتّفاقيّة : إنّه حقل الوقت الذي يكتشفه Kibana تلقائيًّا.
methodeGETGET أو POST.
chemin/robots.txtالعنوان المطلوب على الموقع : /، /cours، /catalogue، /contact، /tarifs، أو صفحة دورة.
cours_idnull أو C0042الدورة المُستعرَضة ، إذا كانت الصفحة تخصّها ؛ null (فارغ) في غير ذلك ، كما هنا لـ /robots.txt. رابط إلى فهرس cours.
categorienull أو DevOpsفئة الدورة المُستعرَضة ، منسوخة لتبسيط الرسوم البيانيّة.
statut200رمز HTTP للاستجابة : 200 نجاح ، 301 و 304 إعادة توجيه أو ذاكرة مؤقّتة ، 404 غير موجود ، 500 و 503 خطأ خادم.
octets108506حجم الاستجابة المُرسَلة.
duree_ms100زمن الاستجابة بالميلّي ثانية.
ip108.190.166.1عنوان IP للزائر.
paysCAبلد الزائر ، رمز حرفين.
appareildesktopdesktop أو mobile أو tablette.
navigateurEdgeChrome و Firefox و Safari و Edge…
referentgoogleمن أين أتى الزائر : direct و google و linkedin و youtube أو newsletter.

الفهارس الثلاثة مرتبطة بـ cours_id : رأي يتحدّث عن دورة ، سطر سجلّ يستعرض دورة. لكن Elasticsearch لا يقوم بربط (jointure) : يُستعلَم كلّ فهرس بشكل منفصل. لتتبّع الروابط ، ننتقل إلى Neo4j.

جانب Neo4j : خمسة أنواع من العقد ، خمسة أنواع من العلاقات

يستلم Neo4j الدورات نفسها (نفس المعرّفات C0001…، نفس العناوين) ، بالإضافة إلى ما لا يوجد في Elasticsearch : الطلاب وتسجيلاتهم والمعلّمون والمهارات والمدن والمتطلّبات بين الدورات. النموذج يتّسع في رسم واحد :

تُقرأ الأسهم الخمسة كجمل : طالب مسجّل في دورة ؛ معلّم يعلّم دورة ؛ دورة تغطّي مهارة ؛ دورة متطلّب لـ دورة أخرى ؛ طالب أو معلّم يعيش في مدينة. إجمالًا 872 عقدة و3712 علاقة.

تصل البيانات بصيغة ملفّات 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) : ناتان بن علي مسجّل في الدورة C0028 ، أنهاها بنسبة 100% ، وأعطاها 3. قارن مع أوّل رأي في فهرس avis : إنّه نفس ناتان ، نفس الدورة C0028 ، نفس الدرجة 3.

العقدةالخصائصمثال
Coursid, titre, categorie, sujet, niveau, prix, duree_heures, date_publicationC0001، « Docker expliqué simplement »، 129 دولار
Etudiantid, prenom, nom, interet, inscription_leE0001، ناتان بن علي ، اهتمام DevOps
Professeurid, prenom, nom, specialite, annees_experienceP001، كريم كارون ، DevOps ، 23 سنة
Competenceid, nomK01، Conteneurisation
Villenom, pays, latitude, longitudeمونتريال ، كندا
العلاقةمن → إلىالخصائصالمعنى
INSCRIT_AEtudiant → Coursdate, progression, noteالطالب يتابع هذه الدورة
ENSEIGNEProfesseur → Coursهذا المعلّم يقدّم هذه الدورة (واحد فقط لكلّ دورة)
COUVRECours → Competenceهذه الدورة تعلّم هذه المهارة
PREREQUIS_DECours → Coursيجب متابعة الأولى قبل الثانية
HABITEEtudiant ou 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، كريم كارون. ستجدها في Elasticsearch (الاستعلام E9) ، وفي Neo4j (الاستعلامان N7 و N13) ، وستُستخدَم لإثبات أنّ المحرّكين يحتويان بالفعل على نفس الشيء.

أوّل الاستعلامات : إظهار البيانات ، من الأبسط إلى الأكثر إثارة للإعجاب

عدّادات etat تُثبت أنّ البيانات موجودة ؛ الاستعلامات أدناه تُظهرها. كلّها للقراءة فقط : يمكنك إعادة تنفيذها كما تشاء ، لن يتغيّر شيء. هي متطابقة تحت Windows وتحت Linux ، كلّ شيء يحدث في المتصفّح.

قاعدة هذا القسم : نوفيّة واحدة فقط لكلّ استعلام. نبدأ بـ « أظهر لي ما يوجد » ، بدون أيّ معامل ، ونضيف فكرة واحدة في كلّ خطوة. بعد كلّ استعلام ، شرح قصير ؛ عندما يجب التعمّق ، فقرة مطويّة « لفهم أعمق ». الصق الاستعلامات واحدة تلو الأخرى ، بالترتيب ، واقرأ الاستجابة قبل الانتقال إلى ما يليها.

Elasticsearch ، في Kibana Dev Tools

عرض استعلامات Elasticsearch الخمسة عشر (E1 إلى E15)

افتح http://localhost:5601، ثم القائمة ManagementDev Tools (أو « Outils de développement »). اللوحة اليسرى محرّر : الصق استعلامًا ، ضع المؤشّر عليه ، ثم Ctrl+Enter أو المثلّث ▶. تظهر الاستجابة إلى اليمين ، مع رمز HTTP (200 - OK) في الأسفل.

صورة يجب الاحتفاظ بها في الذهن لكلّ ما يلي : Elasticsearch خزانة كبيرة.

text
الخزانة   = Elasticsearch
الدرج     = فهرس (index)        (درج « cours »، درج « avis »، درج « acces »)
البطاقة   = مستند (document)    (بطاقة لكلّ دورة ، بطاقة لكلّ رأي ، بطاقة لكلّ سطر سجلّ)

_cat/indices  = قراءة الملصقات المُثبَّتة على الأدراج : الاسم ، الحالة ، عدد البطاقات ، السُّمك
_search       = فتح درج وقراءة البطاقات الموجودة داخله

الاستعلامات E1 إلى E4 تنظر إلى بطاقات الأدراج. من E5 فصاعدًا ، نفتح الأدراج. لا تخلط أبدًا بين الاثنين : هذا هو الخطأ رقم واحد عند المبتدئين.

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 بلا سطر عنوان. نصحّح ذلك في E2.

لقراءة الاستعلام نفسه ، كلمة بكلمة :

المقطعما يعنيه
GET« أريد القراءة. » لا أُغيّر شيئًا ، لا أحذف شيئًا ، أنظر فقط. بلا خطر.
_cat« أجبني بجدول نصّيّ» ، مقروء من إنسان ، لا بصيغة JSON. الشرطة السفليّة في البداية تُشير إلى أمر خاصّ بـ Elasticsearch نفسه ، ليس اسم فهرس.
indices« … جدول الفهارس. » وهو الجمع الإنجليزيّ لـ index.
لفهم أعمق : ما هو الفهرس ، وكيف تُقرأ سطرًا قيمةً قيمةً

الفهرس هو درج : مكان تُرتَّب فيه بطاقات متشابهة. كلّ بطاقات « الدورات » تذهب إلى درج cours ، كلّ بطاقات « الآراء » إلى درج avis ، وكلّ سطر من سجلّ خادم الويب هو بطاقة في درج acces. إذا كنت تعرف SQL ، الفهرس هو ما تسمّيه SQL جدولًا :

Elasticsearchفي الصورةSQL
فهرس coursدرج « 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)

فخّان في المصطلحات. المفرد هو فهرس واحد ، والجمع هو فهارس بالفرنسيّة (يكتب Elasticsearch indices ، الجمع الإنجليزيّ). ولا يعني « مؤشّرًا » بالمعنى الرياضيّ أو الشرطيّ : إنّه فهرس كذلك الموجود في نهاية كتاب ، القائمة التي تقول في أيّ صفحة توجد كلّ كلمة. يفعل Elasticsearch ذلك بالضبط ، على نطاق واسع جدًّا.

الآن ، لنأخذ سطرًا واحدًا من استجابة E1 ونقرأه قيمة بقيمة ، من اليسار إلى اليمين :

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). يمكن تقسيم فهرس كبير إلى عدّة قطع موزّعة على عدّة آلات ؛ هنا ، لا.
0صفر نسخة احتياطيّة (replica). طبيعيّ في مختبر بآلة واحدة : لن يكون للنسخة مكان تذهب إليه.
609يحتوي الدرج على 609 بطاقة. هذه القيمة التي ننظر إليها أوّلًا.
0صفر بطاقة مُعلَّمة « للحذف » في انتظار التنظيف.
74.6kbالحجم الإجماليّ المُستهلَك على القرص.
74.6kbالحجم الذي تشغله القطعة الأساسيّة وحدها (مطابق : ليس هناك إلّا قطعة واحدة ولا نسخة).
74.6kbحجم البيانات نفسها.

ثلاث مرّات 74.6kb لأنّه ، بقطعة واحدة وصفر نسخة ، الطرق الثلاث للقياس تعطي النتيجة نفسها. على مجموعة حقيقيّة مع نسخ ، سيكون 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

نفس استجابة E1 ، بالإضافة إلى سطر عناوين فوقها. هذا كلّ ما يفعله ?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 ، إذن لا نسخة يجب القيام بها ، إذن لا نسخة يمكن أن تنقص. على مجموعة حقيقيّة ، 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. رتّبها حسب الاسم ، ولا تعرض إلّا اسمها وصحّتها وعدد بطاقاتها وحجمها. » أربعة أسطر ، أربعة أعمدة ، كلّ ما يحتاجه الأمر ولا أكثر. هذا الاستعلام الذي يجب الاحتفاظ به لفحص المختبر بنظرة واحدة.

يبدو معقّدًا لأنّه طويل ، لكنّه فقط E3 مع ثلاثة خيارات إضافيّة. التفصيل ، مقطعًا بمقطع :

المقطعبوضوح
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الحجم الذي يشغله على القرص.

بجملة واحدة : أدراجك الثلاثة مفتوحة ، في صحّة جيّدة ، وتحتوي بالضبط على البطاقات المتوقّعة ، 12000 و609 و504.

لفهم أعمق : لماذا هذا هو الاستعلام الذي يجب حفظه

بدون h= ، يُعيد Elasticsearch أحد عشر عمودًا لا يُعلّمك أغلبها شيئًا يوميًّا. بدون s= ، ترتيب الأسطر عشوائيّ ويتغيّر من استدعاء إلى آخر (انظر E2 : كان avis يأتي قبل acces). بتسمية الأدراج الثلاثة ، تستثني أيضًا درج Kibana. النتيجة تتّسع في أربعة أسطر وتُقارَن بنظرة واحدة مع الأرقام المتوقّعة. هذا بالضبط ما يفعله أمر etat من الحزمة خلف الكواليس.

بديل مفيد إذا أردت أيضًا رؤية أنّ الأدراج مفتوحة : أضف 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

حصيلة E1 إلى E4 : لم تقرأ بعد أيّ بطاقة. نظرت فقط إلى البطاقات المُلصَقة على الأدراج. تعرف أنّ هناك 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 إلّا العشر الأوّل ، حتّى لو كان الدرج يحتوي على 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 مللي ثانية).
hits.total.valueالعدد الإجماليّ للبطاقات المطابقة : 504. حتّى إذا لم يُعرَض لك إلّا 10 ، يقول لك كم يوجد في المجموع.
hits.hitsقائمة البطاقات المعروضة لك : العشر الأوّل افتراضيًّا.
_indexالدرج الذي أتت منه البطاقة (cours).
_idالرقم المكتوب في أعلى البطاقة (C0001، C0002…).
_scoreدرجة صلة. تساوي 1 في كلّ مكان هنا لأنّنا لم نبحث عن شيء محدَّد : كلّ البطاقات متساوية.
_sourceالبطاقة نفسها ، كما رُتِّبت في الدرج ، بكلّ خاناتها.

الكلمة hit تعني « إصابة » ، كما في الرمي : بطاقة « أُصيبت » بالاستعلام. الحقل professeur هو بطاقة داخل البطاقة (JSON داخل JSON) : المعلّم مكتوب مباشرة على بطاقة الدورة ، باسمه ومدينته. SQL لا يفعل ذلك طبيعيًّا ، سيحتاج جدولًا ثانيًا وربطًا.

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 قيمة 10000 دفعة واحدة (المعامل index.max_result_window) ؛ للتصفّح أكثر ، نُصفّح (pagination). لدوراتنا الـ 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 »، لكريم كارون : سنجدها في Neo4j بعد قليل ، لإثبات أنّ المحرّكين يحتويان على نفس البيانات.

لفهم أعمق : _id و id ، شيئان مختلفان

_id (بشرطة سفليّة) هو المعرّف التقنيّ للمستند في Elasticsearch ؛ id (بلا شرطة) هو حقل عاديّ داخل _source. جعلتهما الحزمة متطابقين عمدًا (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 المُعرَّف في ربط الحزمة. عندما تبحث ، يخضع استعلامك للمعالجة نفسها ، ثم يقارن Elasticsearch كلمة بكلمة. النتيجة : الحروف الكبيرة والحروف الخاصّة والجموع لا تُحسَب بعد الآن. يرتفع _score عندما تكون الكلمة نادرة في الفهرس ومتكرّرة في المستند. تخصّص الوحدة 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 } }
]

تعود الدورات السبع لـ Kubernetes ، رغم الخطأ. هذه اللحظة التي تُفاجئ دائمًا في قاعة الدرس ، وهي سبب وجود Elasticsearch في شريط بحث : يكتب المستخدم خطأ ، يفهم المحرّك مع ذلك. LIKE '%kubrenetes%' بـ SQL ما كان سيُعيد شيئًا أبدًا. لاحظ الصيغة : عندما يحتاج match إلى خيارات ، تصبح قيمة الحقل كائنًا { "query": …, "fuzziness": … } عوض سلسلة نصّيّة بسيطة.

لفهم أعمق : AUTO

يعدّ fuzziness التعديلات (حرف مُضاف أو مُزال أو مُغيَّر أو مُبدَّل مع جاره) التي نتسامح بها بين الكلمة المكتوبة والكلمة المفهرسة. AUTO يُكيّف التسامح مع طول الكلمة : 0 خطأ لكلمة من 1 أو 2 حرف ، خطأ واحد من 3 إلى 5 أحرف ، خطآن فما فوق. « kubrenetes » تحتوي 10 أحرف ، إذن يُسمَح بخطأين ؛ لا تحتوي إلّا واحدًا (تبديل reer) : وُجدت. الـ _score أقلّ قليلًا من E10 (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 ، ستضغط على « champ : categorie, agrégation : terms » وسيُرسل Kibana بالضبط هذا الاستعلام. معرفة قراءته يعني معرفة ما تفعله لوحة المراقبة خلف الكواليس.

E14. تجميع داخل تجميع

json
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، لكنّ الاستجابة هرميّة بالفعل ، جاهزة لرسم بيانيّ مكدَّس.

E15. أخطاء الخادم بين 12000 وصول

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) على فهرس acces. من بين 12000 سطر سجلّ ، 178 لها رمز HTTP ≥ 500 : 122 خطأ 500 و56 503. هذا السؤال الذي يطرحه مسؤول منصّة كلّ صباح ، ويُجاب عليه في ميلّي ثانية واحدة. بـ SQL : SELECT statut, COUNT(*) FROM acces WHERE statut >= 500 GROUP BY statut. هذا بالضبط ما ستضعه على لوحة مراقبة في الوحدة 4.

Neo4j ، في Neo4j Browser

عرض استعلامات Neo4j الستّة عشر (N1 إلى N16)

افتح 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 كيف تفعله.

لفهم أعمق : العقد ، العلامات ، العلاقات

الرسم البيانيّ مبنيّ من شيئين : عقد (الفقّاعات) و علاقات (الأسهم بين الفقّاعات). تحمل كلّ عقدة علامة (étiquette) تقول ما هي (Cours, Etudiant…) و خصائص (العنوان ، السعر…). تحمل كلّ علاقة نوعًا (INSCRIT_A, ENSEIGNE…) واتّجاهًا.

Neo4jSQL
العلامة Coursجدول cours
عقدةصفّ
خاصيّةعمود
علاقة INSCRIT_Aجدول ربط inscriptions + عمليّات ربط

رسم بيانيّ المختبر :

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 في فهرس 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 تبحث عن تتابع حروف. سبع دورات ، نفسها كما في E10. لكن جرّب 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

نفس نتيجة التجميع E13 بالضبط في 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 ، لا توجد علاقة بين سطرين بشكل حقيقيّ فعلًا : تُحسَب من جديد عند كلّ استعلام بواسطة ربط (jointure) ، يُقارِن معرّفات. مع عشر عمليّات ربط متتالية ، يصبح ذلك بطيئًا وغير مقروء. في 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) نوعها. خمسة أنواع من العلاقات ، 3712 إجمالًا : الرقم المعروض في لوحة 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 : عمليّتا ربط على جدول التسجيلات ، 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 أيضًا ؛ ثم التسامح مع خطأ إملائيّ ، تجميع 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 عقدة موجودة دائمًا دون إعادة تحميل أيّ شيء ، ولاحظ أيّ من الخدمتين تُعيد التشغيل بشكل أسرع.

الملحق A — الخطوات التفصيليّة تحت Windows (PowerShell)

عرض الخطوات التفصيليّة لـ Windows (A.0 إلى A.11)

كلّ أوامر هذا الملحق تُكتَب في PowerShell (Windows Terminal، أو PowerShell 7)، بـ .\labo.ps1 …. المخرجات المُعاد إنتاجها هي مخرجات جهاز الدورة ، تحت Windows 11 وDocker Desktop.

A.0 — قبل البدء

  • قراءة الدروس الأربعة للوحدة : 01، 02، 03 و 04.
  • Docker Desktop مُشغَّل (أيقونة خضراء) ، الحزمة منسوخة ، طرفيّة PowerShell مفتوحة في مجلّد الحزمة (الذي يحتوي على docker-compose.yml و labo.ps1).
  • إذا رفض PowerShell تنفيذ .\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. لا واحد منها يكتب في المجموعة.

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 جيجابايت ، منفذ محجوز) ؛ صحّح ، أعد التشغيل ، لا تتقدّم إلّا مع السطر الأخضر. للذاكرة : 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 إنشاء فهارسه الداخليّة ؛ أعد كتابة etat بعد ثلاثين ثانية.

A.4 — تحميل البيانات ، ثم إعادة التحميل لإثبات أنّ شيئًا لا يتحرّك

حمّل الفهارس ثم الرسم البيانيّ ، و أعد تشغيل الأمرين مرّة ثانية : لا يجب أن تتغيّر العدّادات بوحدة واحدة.

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

نقطة التحقّق : في المرور الثاني ، يُشير importer إلى أنّ الفهارس موجودة بالفعل ويُعيد نفس العدّادات :

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.

تحمل المستندات معرّفها الخاصّ (C0001، A00001…) ، لذا _bulk ثانٍ يستبدل كلّ مستند عوض إضافته ؛ من جانب Neo4j ، كلّ شيء بـ MERGE. فقط store.size قد يتحرّك بعدّة كيلوبايت (يحتفظ Lucene بالإصدارات القديمة لبعض الوقت) ؛ docs.count، لا تتغيّر أبدًا.

إذا رأيت شيئًا آخر : docs.count مختلف عن 504 / 609 / 12000 → كتب شخص في هذه الفهارس ؛ .\labo.ps1 reinitialiser ثم demarrer و importer و charger-graphe تُعيد لك الحالة المرجعيّة.

A.5 — التحقّق من Elasticsearch من Dev Tools

يقول النصّ البرمجيّ أنّ كلّ شيء جيّد ؛ تحقّق منه بدونه. افتح http://localhost:5601، القائمة ManagementOutils de développement، وأرسل هذه الاستعلامات واحدًا تلو الآخر (Ctrl + Enter أو الزرّ ▶ « اضغط لإرسال الاستعلام »).

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" → فهرس لديه نسخ احتياطيّة غير مُخصَّصة ، مستحيل مع ربط الحزمة (number_of_replicas: 0) ؛ GET _cat/indices?v&health=yellow يُحدّد المتسبّب ، عادةً فهرس أُنشئ يدويًّا.

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).

إذا رأيت شيئًا آخر : علامة سادسة غير معروفة → عقد أُنشئت خارج الحزمة (تُعلّمك الوحدة 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… بين الاثنين هي كومة استدعاءات (pile d'appels) يُصدرها ملحق أثناء الإيقاف : ضجيج.

إذا رأيت شيئًا آخر : no configuration file provided: not found → لست في مجلّد الحزمة ؛ no such service: kibana → كتبت اسم الحاوية (labo-kibana) عوض اسم خدمة Compose (kibana).

A.8 — إصلاح Kibana وإثبات أنّ Elasticsearch لم يرَ شيئًا

أعد تشغيل الخدمة وانتظر عودة فحصها الصحّيّ إلى الأخضر : 40 إلى 60 ثانية ، الوقت اللازم لإعادة الاتّصال بـ Elasticsearch والتحقّق من فهارسه الداخليّة.

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"، ثم اشرح بجملة واحدة لماذا تبقى المجموعة green.

مؤشّر : لا يُخمّن Elasticsearch أبدًا اسم فهرس. اختر واحدًا غير موجود ، بالبادئة pratique- ؛ لا شيء يُنشَأ ، لا شيء يُكتَب.

الحلّ
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 : « فهمت استعلامك ، لكن هذا المورد غير موجود ». الخدمة تعمل ، تبقى المجموعة 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 : « أفهم ، لكن هذا غير موجود ».

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 — التنظيف

هذا التطبيق لا يُنشئ شيئًا : لا فهرس ، لا عقدة ، لا كائن Kibana. شيئان يجب ضمانهما : أنّ Kibana يعمل (وإلّا docker compose start kibana من مجلّد الحزمة) ، وأنّه لا يوجد فهرس عمل مُتبقٍّ :

text
GET _cat/indices/pratique-*?v

الاستجابة المتوقَّعة : سطر العنوان وحده (health status index uuid pri rep docs.count …). لا تلمس cours أو avis أو acces ولا الرسم البيانيّ : تخدم كلّ الوحدات التالية.

الملحق B — الخطوات التفصيليّة تحت Linux و macOS و WSL 2 و Git Bash

عرض الخطوات التفصيليّة لـ Linux وmacOS وWSL 2 وGit Bash (B.0 إلى B.11)

كلّ أوامر هذا الملحق تُكتَب في طرفيّة bash (أو zsh)، بـ ./labo.sh …. المخرجات مطابقة لتلك الخاصّة بـ Windows باستثناء اسم النصّ البرمجيّ : الحزمة نفسها ، فقط المُشغِّلات تتغيّر.

B.0 — قبل البدء

  • قراءة الدروس الأربعة للوحدة : 01، 02، 03 و 04.
  • Docker مُشغَّل (Docker Desktop على macOS و WSL 2، خدمة docker على Linux : يجب أن يُجيب docker info)، الحزمة منسوخة ، طرفيّة مفتوحة في مجلّد الحزمة (الذي يحتوي على 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. لا واحد منها يكتب في المجموعة.

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 جيجابايت ، منفذ محجوز) ؛ صحّح ، أعد التشغيل ، لا تتقدّم إلّا مع السطر الأخضر. تحت Linux الأصليّ ، الذاكرة هي ذاكرة الجهاز ؛ تحت 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 الأصليّ ، 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 إنشاء فهارسه الداخليّة ؛ أعد كتابة etat بعد ثلاثين ثانية.

B.4 — تحميل البيانات ، ثم إعادة التحميل لإثبات أنّ شيئًا لا يتحرّك

حمّل الفهارس ثم الرسم البيانيّ ، و أعد تشغيل الأمرين مرّة ثانية : لا يجب أن تتغيّر العدّادات بوحدة واحدة.

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

نقطة التحقّق : في المرور الثاني ، يُشير importer إلى أنّ الفهارس موجودة بالفعل ويُعيد نفس العدّادات :

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.

تحمل المستندات معرّفها الخاصّ (C0001، A00001…) ، لذا _bulk ثانٍ يستبدل كلّ مستند عوض إضافته ؛ من جانب Neo4j ، كلّ شيء بـ MERGE. فقط store.size قد يتحرّك بعدّة كيلوبايت (يحتفظ Lucene بالإصدارات القديمة لبعض الوقت) ؛ docs.count، لا تتغيّر أبدًا.

إذا رأيت شيئًا آخر : docs.count مختلف عن 504 / 609 / 12000 → كتب شخص في هذه الفهارس ؛ ./labo.sh reinitialiser ثم demarrer و importer و charger-graphe تُعيد لك الحالة المرجعيّة.

B.5 — التحقّق من Elasticsearch من Dev Tools

يقول النصّ البرمجيّ أنّ كلّ شيء جيّد ؛ تحقّق منه بدونه. افتح http://localhost:5601، القائمة ManagementOutils de développement، وأرسل هذه الاستعلامات واحدًا تلو الآخر (Ctrl + Enter، أو Cmd + Enter على macOS، أو الزرّ ▶ « اضغط لإرسال الاستعلام »).

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" → فهرس لديه نسخ احتياطيّة غير مُخصَّصة ، مستحيل مع ربط الحزمة (number_of_replicas: 0) ؛ GET _cat/indices?v&health=yellow يُحدّد المتسبّب ، عادةً فهرس أُنشئ يدويًّا.

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).

إذا رأيت شيئًا آخر : علامة سادسة غير معروفة → عقد أُنشئت خارج الحزمة (تُعلّمك الوحدة 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… بين الاثنين هي كومة استدعاءات يُصدرها ملحق أثناء الإيقاف : ضجيج.

إذا رأيت شيئًا آخر : no configuration file provided: not found → لست في مجلّد الحزمة ؛ no such service: kibana → كتبت اسم الحاوية (labo-kibana) عوض اسم خدمة Compose (kibana).

B.8 — إصلاح Kibana وإثبات أنّ Elasticsearch لم يرَ شيئًا

أعد تشغيل الخدمة وانتظر عودة فحصها الصحّيّ إلى الأخضر : 40 إلى 60 ثانية ، الوقت اللازم لإعادة الاتّصال بـ Elasticsearch والتحقّق من فهارسه الداخليّة.

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"، ثم اشرح بجملة واحدة لماذا تبقى المجموعة green.

مؤشّر : لا يُخمّن Elasticsearch أبدًا اسم فهرس. اختر واحدًا غير موجود ، بالبادئة pratique- ؛ لا شيء يُنشَأ ، لا شيء يُكتَب.

الحلّ
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 : « فهمت استعلامك ، لكن هذا المورد غير موجود ». الخدمة تعمل ، تبقى المجموعة 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 : « أفهم ، لكن هذا غير موجود ».

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 — التنظيف

هذا التطبيق لا يُنشئ شيئًا : لا فهرس ، لا عقدة ، لا كائن Kibana. شيئان يجب ضمانهما : أنّ Kibana يعمل (وإلّا docker compose start kibana من مجلّد الحزمة) ، وأنّه لا يوجد فهرس عمل مُتبقٍّ :

text
GET _cat/indices/pratique-*?v

الاستجابة المتوقَّعة : سطر العنوان وحده (health status index uuid pri rep docs.count …). لا تلمس cours أو avis أو acces ولا الرسم البيانيّ : تخدم كلّ الوحدات التالية.

الملحق C — إذا واجهتَ عقبة (كلّ الأنظمة)

عرض الحالات التي يتعطّل فيها العمل
  • 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 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.