ورشة عمل أساسيّة 1 — Elasticsearch : فهرس واحد ، مستند واحد ، طلب GET واحد

تدريب موجَّه12 دقيقة
المدة
20 دقيقة
الوحدة
1/7
المتطلّبات
المختبر يعمل (etat يعرض (healthy) في كلّ مكان) ، Kibana Dev Tools مفتوح
ستبني
فهرسًا لك ، pratique-mini، بمستندين ستقرأهما ، تُكمّلهما ، تبحث فيهما ثم تحذفهما
التسليمة
استجابة GET pratique-mini/_doc/1 بعد الخطوة 5 ، بحقولها الثلاثة

كيف تقرأ هذه الصفحة. ثماني خطوات ، استعلام واحد في كلّ مرّة. لكلّ خطوة : الاستعلام الذي تكتبه ، الاستجابة الدقيقة للمختبر ، وما يجب النظر إليه فيها. اكتب كلّ استعلام بنفسك (بلا نسخ ولصق) : بكتابة PUT و GET و _doc تدخل الكلمات في ذهنك. فقرات « لفهم أعمق » اختياريّة ؛ افتحها إذا تركتك خطوة في شكّ. إذا لم يكن المختبر مُشغَّلًا ، ارجع إلى التطبيق الموجّه : قسم بإيجاز يعطي الأوامر ، بما فيها الحزمة (https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr).

الهدف

جعلك التطبيق الموجّه تُحمّل 504 دورة و609 رأي و12000 سطر سجلّ دفعة واحدة ، بنصّ برمجيّ. رأيت الأرقام ، لكنّك لم تكتب شيئًا بنفسك بعد. هنا ، تبدأ من الصفر : فهرس فارغ تُنشئه ، بطاقة أولى تضعها فيه ، تُعيد قراءتها ، تُكمّلها ، ثم ثانية ، بحث ، حذف. في النهاية ، تعرف ما هو الفهرس والمستند لأنّك صنعت واحدًا ، لا لأنّهم قالوا لك ذلك.

المصطلحات في صورة واحدة

الفهرس هو خزانة. المستند هو بطاقة مرتَّبة في الخزانة : نصّ JSON صغير بحقول. لكلّ بطاقة رقم ، _id الخاصّ بها ، يسمح باستعادتها مباشرة دون البحث. لا حاجة لتعريف الأعمدة مسبقًا : البطاقة الأولى المُرتَّبة تُنشئ الربط (mapping، مخطّط الخزانة) بمفردها.

Elasticsearchقاعدة SQL كلاسيكيّةفي هذا التطبيق
فهرسجدولpratique-mini
مستندصفّ{"titre": "Mon premier document"}
حقلعمودtitre, auteur, note
_idمفتاح أساسيّ1, 2
ربط (mapping)مخطّط الجدول (CREATE TABLE …)يُنشَأ تلقائيًّا في الخطوة 3
_sourceالصفّ كما كتبتهما يُعيده لك GET _doc/1

أين تكتب ، وكيف تقرأ استعلامًا

افتح http://localhost:5601، القائمة ☰ → ManagementDev Tools. اللوحة اليسرى تستقبل الاستعلامات ، اليمنى تعرض الاستجابة. تُرسل بـ Ctrl + Enter (Cmd + Enter على macOS) أو الزرّ ▶ إلى يمين السطر.

كلّ استعلام له الصيغة نفسها : فعل ، مسار ، وأحيانًا متن JSON تحته.

الفعلما يفعلهمعادل SQL
GETالقراءة ، بدون تغيير شيءSELECT
PUTالإنشاء ، أو الاستبدال الكاملCREATE TABLE, INSERT (أو استبدال الصفّ)
POSTالتصرّف : تحديث ، بحث بمتنUPDATE
DELETEالحذفDROP TABLE, DELETE

المسار يقول على ماذا نتصرّف : pratique-mini (الخزانة) ، pratique-mini/_doc/1 (البطاقة رقم 1 من الخزانة) ، pratique-mini/_search (البحث في الخزانة). الكلمات التي تبدأ بـ _ هي أوامر خاصّة بـ Elasticsearch ، ليست أسماء تخصّك.

الخطوة 1 — إنشاء الخزانة ، فارغة

text
PUT pratique-mini

ما يطلبه الاستعلام : أنشئ فهرسًا اسمه pratique-mini. لا شيء آخر : لا أعمدة ، لا محتوى.

json
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "pratique-mini"
}

انظر إلى : "acknowledged": true، « تمّ الأمر » ، والاسم في الصدى. الشارة في أعلى يمين الاستجابة تقول 200 - OK.

لفهم أعمق
  • لماذا pratique- في البداية؟ كلّ الفهارس التي تُنشئها في هذه الدورة تحمل هذه البادئة. بهذه الطريقة GET _cat/indices/pratique-*?v يسرد كلّ ما هو لك ولا شيء آخر ، ويبقى cours و avis و acces بلا لمس.
  • اسم الفهرس يكون بحروف صغيرة ، بلا مسافة ولا حرف كبير ولا /. Pratique-Mini سيُرفَض.
  • مرّة ثانية الاستعلام نفسه يُجيب بـ 400 مع resource_already_exists_exception : الخزانة موجودة بالفعل. ليس عطلًا ، إنّها استجابة.

الخطوة 2 — رؤيتها ، وتصحيح لونها

text
GET _cat/indices/pratique-mini?v

ما يطلبه الاستعلام : سطر ملخّص عن هذا الفهرس ، مع سطر العنوان (?v، verbose).

text
health status index         uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
yellow open   pratique-mini YHjAfGXBTwSZcl7CLcc2jw   1   1          0            0       227b           227b         227b

انظر إلى : docs.count 0، الخزانة فارغة. و health yellow مع rep 1 : خطّط Elasticsearch نسخة احتياطيّة (réplica) لفهرسك على آلة ثانية ، ولا يمتلك المختبر إلّا آلة واحدة. لا يمكن وضع النسخة في أيّ مكان ، من هنا الأصفر. الفهارس الثلاثة للحزمة خضراء لأنّ ربطها يضبط number_of_replicas: 0. فعِل الشيء نفسه ، باستعلام واحد :

text
PUT pratique-mini/_settings
{
  "index": { "number_of_replicas": 0 }
}
json
{
  "acknowledged": true
}

أعد كتابة GET _cat/indices/pratique-mini?v :

text
health status index         uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   pratique-mini YHjAfGXBTwSZcl7CLcc2jw   1   0          0            0       227b           227b         227b

انظر إلى : green، rep 0. سيكون uuid لديك مختلفًا : إنّه المعرّف الداخليّ للفهرس ، يُسحَب عشوائيًّا عند الإنشاء.

لفهم أعمق
  • الأصفر ليس مكسورًا. فهرس أصفر يُقرأ ويُكتَب بشكل طبيعيّ. إنّه تحذير : « النسخة الاحتياطيّة التي طلبتها غير موجودة ». على آلة واحدة ، لا يمكن أن توجد.
  • بينما كان فهرسك أصفر ، GET _cluster/health كان يقول "status": "yellow" لكلّ المجموعة : لون المجموعة هو أسوأ لون بين فهارسها. هذا هو شرح عطل « yellow » في كتالوج الدرس 04.
  • GET pratique-mini (بدون _cat) يُعيد بطاقة الفهرس الكاملة : "mappings": { } (فارغة ، لا بطاقة مُرتَّبة) و "settings" مع number_of_replicas.

الخطوة 3 — ترتيب بطاقة أولى

text
PUT pratique-mini/_doc/1
{
  "titre": "Mon premier document"
}

ما يطلبه الاستعلام : في الخزانة pratique-mini، رتّب بطاقة (_doc) رقم 1 تحتوي على حقل titre.

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}

انظر إلى : "result": "created" و "_version": 1 : أوّل إصدار من البطاقة 1. معادل SQL : INSERT INTO pratique_mini (id, titre) VALUES (1, 'Mon premier document')، إلّا أنّه لم يكن هناك حاجة لأيّ CREATE TABLE.

لفهم أعمق
  • الربط وُلد للتوّ. اكتب GET pratique-mini/_mapping : الحقل titre مُعرَّف الآن من نوع text (للبحث عن كلمات فيه) مع حقل فرعيّ titre.keyword (للترتيب أو الترشيح على القيمة الدقيقة). استنتج Elasticsearch ذلك من القيمة "Mon premier document"، سلسلة حروف.
  • الـ 1 في _doc/1، أنت من اخترته. تفعل مستندات الحزمة الشيء نفسه (C0001، A00001…). إذا كتبت POST pratique-mini/_doc بدون رقم ، سيخترع Elasticsearch _id من عشرين حرفًا ؛ عمليّ للسجلّات ، مزعج لبطاقة تريد إيجادها يدويًّا.
  • _shards, _seq_no, _primary_term هي محاسبة داخليّة (على كم قطعة تمّ تأكيد الكتابة ، أيّ رقم ترتيب). لا تحتاجها في هذه الدورة.

الخطوة 4 — إعادة قراءة البطاقة

text
GET pratique-mini/_doc/1

ما يطلبه الاستعلام : أعطني البطاقة رقم 1 من pratique-mini، مباشرة ، بدون بحث.

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 1,
  "_seq_no": 0,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "titre": "Mon premier document"
  }
}

انظر إلى : "found": true، و _source، بطاقتك كما كتبتها ، بالحرف الواحد. معادل SQL : SELECT * FROM pratique_mini WHERE id = 1.

جرّب بطاقة غير موجودة : GET pratique-mini/_doc/3.

json
{
  "_index": "pratique-mini",
  "_id": "3",
  "found": false
}

لا خطأ ، لا _source : "found": false، شارة 404 - Not Found. فهم Elasticsearch السؤال ؛ الجواب هو « لا يوجد شيء بهذا الرقم ».

الخطوة 5 — إضافة قيم إلى البطاقة

text
POST pratique-mini/_update/1
{
  "doc": {
    "auteur": "Alice",
    "note": 5
  }
}

ما يطلبه الاستعلام : حدّث (_update) البطاقة 1 بـ إضافة هذين الحقلين. ما لم يُذكَر (titre) يبقى كما هو.

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}

انظر إلى : "result": "updated"، "_version": 2. أعد قراءة البطاقة بـ GET pratique-mini/_doc/1 :

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 2,
  "_seq_no": 1,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "titre": "Mon premier document",
    "note": 5,
    "auteur": "Alice"
  }
}

ثلاثة حقول. العنوان لا يزال موجودًا. معادل SQL : UPDATE pratique_mini SET auteur = 'Alice', note = 5 WHERE id = 1، بفرق أنّه في SQL يجب أن تكون الأعمدة auteur و note موجودة مسبقًا. هذه استجابتك-تسليمتك : احتفظ بها.

لفهم أعمق
  • الكلمة doc في المتن تعني « ها هي الحقول التي يجب دمجها ». بدونها ، لا يعرف _update ماذا يفعل.
  • كبر الربط. يعرض GET pratique-mini/_mapping الآن auteur (text + keyword، كـ titre) و note من نوع long، عدد صحيح. استنتج Elasticsearch النوع من 5. لو كتبت "note": "5" بين علامتي اقتباس ، لأعلن نصًّا ، ولم تعد قادرًا على حساب متوسّط عليه. هذا موضوع درس الربط في الوحدة 2.
  • _version تعدّ الكتابات على هذه البطاقة ، لا القراءات : GET لا يزيدها أبدًا.

الخطوة 6 — الفخّ : PUT يستبدل كلّ شيء

أعد إرسال استعلام الخطوة 3 بالضبط :

text
PUT pratique-mini/_doc/1
{
  "titre": "Mon premier document"
}
json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}

انظر إلى : "result": "updated" (ليس created : البطاقة 1 كانت موجودة) و "_version": 3. ثم أعد قراءتها :

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 3,
  "_seq_no": 3,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "titre": "Mon premier document"
  }
}

اختفى auteur و note. PUT _doc/1 لا يُعدّل البطاقة 1 : يستبدلها بما ترسله. لإكمالها دون خسارة ، الطريقة هي POST _update/1 مع doc. احتفظ بالقاعدة مع الفعلين : PUT يستبدل ، _update يُكمّل. أعِد الحقلين بطلب الخطوة 5 قبل الاستمرار (تحصل على _version: 4).

الخطوة 7 — بطاقة ثانية ، العدّ ، البحث

text
PUT pratique-mini/_doc/2
{
  "titre": "Deuxième document, écrit par Bob",
  "auteur": "Bob",
  "note": 3
}

الاستجابة : "_id": "2"، "result": "created"، "_version": 1. ثم عدّ :

text
GET pratique-mini/_count
json
{
  "count": 2,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  }
}

معادل SQL : SELECT COUNT(*) FROM pratique_mini. الآن ، انظر إلى كلّ ما يحتويه :

text
GET pratique-mini/_search

ما يطلبه الاستعلام : بحث في pratique-mini، بدون معيار ، إذن كلّ شيء.

json
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1.0,
    "hits": [
      {
        "_index": "pratique-mini",
        "_id": "2",
        "_score": 1.0,
        "_source": {
          "titre": "Deuxième document, écrit par Bob",
          "auteur": "Bob",
          "note": 3
        }
      },
      {
        "_index": "pratique-mini",
        "_id": "1",
        "_score": 1.0,
        "_source": {
          "titre": "Mon premier document",
          "note": 5,
          "auteur": "Alice"
        }
      }
    ]
  }
}

انظر إلى : hits.total.value: 2 (كم بطاقة تُجيب) ثم hits.hits، قائمة البطاقات ، كلّ واحدة بـ _id و _source خاصّتها. يمكن أن يتغيّر ترتيب الاثنتين : بدون معيار ، كلّها بنفس _score يساوي 1.0. معادل SQL : SELECT * FROM pratique_mini.

وأخيرًا ، ابحث عن كلمة :

text
GET pratique-mini/_search
{
  "query": {
    "match": {
      "titre": "premier"
    }
  }
}

ما يطلبه الاستعلام : البطاقات التي يحتوي حقل titre فيها على كلمة premier.

json
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.3788134,
    "hits": [
      {
        "_index": "pratique-mini",
        "_id": "1",
        "_score": 0.3788134,
        "_source": {
          "titre": "Mon premier document",
          "note": 5,
          "auteur": "Alice"
        }
      }
    ]
  }
}

انظر إلى : بطاقة واحدة فقط ، الـ 1 ، و _score ليست 1.0 بعد الآن : إنّها الصلة ، « إلى أيّ حدّ تُجيب هذه البطاقة على السؤال ». الوحدة 3 مخصّصة لهذا الرقم. معادل SQL تقريبيّ : SELECT * FROM pratique_mini WHERE titre LIKE '%premier%'، إلّا أنّ match ستجد أيضًا Premier بحرف كبير ، وستُفسّر لك الوحدة 2 السبب.

لفهم أعمق
  • يُعيد _count 0 أو 1 مباشرة بعد كتابة؟ يجعل Elasticsearch البطاقات الجديدة مرئيّة للبحث كلّ ثانية، لا فورًا. أعد تشغيل _count : أصبح محدَّثًا. GET _doc/1، هي دائمًا فوريّة لأنّها لا تبحث ، تذهب مباشرة إلى الرقم. إذا أردت إجبار الرؤية الفوريّة في اختبار : PUT pratique-mini/_doc/2?refresh=true.
  • took هو زمن البحث بالميلّي ثانية. timed_out: false : انتهى في الوقت المحدَّد.
  • لماذا GET مع متن؟ هذه خاصيّة لدى Elasticsearch : البحث قراءة ، إذن GET، لكنّ السؤال يتّسع في متن JSON. يعمل POST pratique-mini/_search بالمتن نفسه أيضًا ؛ الاثنان مقبولان.

الخطوة 8 — حذف بطاقة ، ثم الخزانة

text
DELETE pratique-mini/_doc/2
json
{
  "_index": "pratique-mini",
  "_id": "2",
  "_version": 2,
  "result": "deleted",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 4,
  "_primary_term": 1
}

انظر إلى : "result": "deleted". GET pratique-mini/_count يُعيد 1 (بعد ثانية). معادل SQL : DELETE FROM pratique_mini WHERE id = 2.

ثم احذف الخزانة كاملة ، بما فيها البطاقات :

text
DELETE pratique-mini
json
{
  "acknowledged": true
}

الدليل على أنّها لم تعد موجودة ، GET pratique-mini/_doc/1 :

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

انظر إلى : الفرق مع الخطوة 4. بطاقة غائبة في خزانة موجودة : "found": false، بلا خطأ. خزانة غائبة : index_not_found_exception، 404. الاثنان استجابتان طبيعيّتان لخدمة تعمل. معادل SQL : DROP TABLE pratique_mini.

التحقّق النهائيّ

text
GET _cat/indices/pratique-*?v

الاستجابة المتوقَّعة : سطر العنوان وحده. لا شيء لك مُتبقٍّ في المجموعة ، و GET _cat/indices/cours,avis,acces?v يعرض دائمًا 504، 609، 12000.

  • أنشأت pratique-mini وتعرف لماذا كان أصفر ، ثم أخضر.
  • رتّبت البطاقة 1 بـ PUT _doc/1 وأعدت قراءتها بـ GET _doc/1.
  • أضفت auteur و note بـ POST _update/1 دون فقدان titre.
  • رأيت PUT _doc/1 يحذف الحقلين ، وتعرف قول القاعدة : PUT يستبدل ، _update يُكمّل.
  • قال _count 2، سرد _search البطاقتين ، احتفظ match بواحدة فقط.
  • حذفت البطاقة 2 ثم الفهرس ، و pratique-* فارغ.
  • احتفظت باستجابة GET pratique-mini/_doc/1 بثلاثة حقول (الخطوة 5) كتسليمة.

إذا عطّلت

عرض الحالات المتكرّرة
  • 400 مع Unexpected character أو was expecting double-quote to start field name → متن JSON غير سليم : كلّ اسم حقل وكلّ نصّ بين علامتي اقتباس مزدوجتين "، فاصلة بين الحقول ، بلا فاصلة بعد الأخير. يُظلّل Dev Tools المكان.
  • 400 مع resource_already_exists_exception على PUT pratique-mini → الفهرس موجود بالفعل (أعدت الخطوة 1). استمرّ إلى الخطوة 2 ، أو DELETE pratique-mini للبدء من الصفر.
  • 400 مع no handler found for uri → خطأ إملائيّ في كلمة بـ _ : _serch، _doc/ مُنسيّ ، _udpate. يتحقّق Elasticsearch من المسار قبل كلّ شيء آخر.
  • 405 مع Incorrect HTTP method for uri [/pratique-mini/_update/1] and method [GET], allowed: [POST] → فعل خاطئ لهذا المسار. تقول الرسالة نفسها أيّ فعل مقبول.
  • 400 مع [UpdateRequest] unknown field [titre] → أرسلت الحقول مباشرة إلى _update، بدون تغليفها في "doc": { … }. أضف الغلاف.
  • لا يرى _count أو _search البطاقة التي كتبتها للتوّ → انتظر ثانية وأعد التشغيل (انظر « لفهم أعمق » في الخطوة 7). GET _doc/1 يراها فورًا.
  • "result": "noop" على _update → كانت القيم المُرسَلة هي نفسها بطاقة موجودة بالفعل ؛ لا شيء يجب تغييره ، لم تتحرّك _version. ليس خطأً.
  • يستمرّ "status": "yellow" في GET _cluster/health بعد الخطوة 2 → فهرس آخر لك لا يزال بـ rep 1. GET _cat/indices?v&health=yellow يُحدّده ؛ طبّق عليه نفس _settings، أو احذفه.
  • يعرض Dev Tools « Kibana server is not ready yet » → يُعيد Kibana التشغيل أو ينتظر Elasticsearch ؛ .\labo.ps1 etat أو ./labo.sh etat، ثم الدرس 04.