السجلات على نطاق واسع: ELK وLoki

Elasticsearch للمشغّلين

18 دقيقة الدرس 4 من 28

Elasticsearch للمشغّلين

تشغيل Elasticsearch في بيئة الإنتاج يختلف تمامًا عن تشغيله في بيئة تجريبية. في بيئات الحجم الكبير، الفرق بين كلاستر يصمد أمام ذروة استيعاب بيانات تبلغ 200 غيغابايت يوميًا وآخر ينهار يعود إلى أربعة أمور يجب أن تتحكم فيها بوصفك مشغّلًا: تصميم الـ Index، واستراتيجية الـ Shard، والالتزام بالـ Mapping، وإدارة دورة حياة الـ Index (ILM). هذا الدرس هو دليلك الميداني لكل هذه المحاور، إضافةً إلى إشارات صحة الكلاستر التي تنبّهك مبكرًا قبل وقوع الكارثة.

الـ Indices وما هي في الواقع

الـ Index في Elasticsearch هو الفضاء المنطقي الذي يجمع مجموعة من مستندات JSON المترابطة. تحت الغطاء، كل Index هو مجموعة من إندكسات Apache Lucene الفعلية. كل عملية كتابة تذهب إلى الـ Primary Shard ثم تُنسخ إلى الـ Replica Shards. القراءة يمكن أن تُخدَّم من أي منها — وبهذا يحقق Elasticsearch المتانة وقابلية التوسع في القراءة من نفس الكلاستر.

في حالة السجلات، نادرًا ما تُنشئ Index واحدًا ضخمًا. بدلًا من ذلك، تستخدم Data Stream، وهو سلسلة من الـ Indices المدعومة بطابع زمني وتُدار تلقائيًا. كل عملية كتابة على Data Stream تذهب دائمًا إلى الـ Index الحالي للكتابة (يُسمى "write backing index"). الـ Indices القديمة تصبح للقراءة فقط ويتولى ILM تدوير حذفها وفق السياسة المعرّفة.

الـ Data Streams هي النموذج الصحيح للسجلات. لا تُنشئ Indices مباشرة لبيانات السجلات ذات الطابع الزمني في عام 2025. الـ Data Streams توفر التدوير التلقائي والتكامل مع ILM وضمان الترتيب بحسب @timestamp الذي تعتمد عليه استعلامات السجلات.

الـ Shards: وحدة التوسع ومصدر معظم مشكلات الإنتاج

الـ Shard هو إندكس Lucene مفرد. لكل Index عدد ثابت من الـ Primary Shards يُحدَّد عند الإنشاء — لا يمكن تغييره بدون إعادة فهرسة. لكل Shard عدد قابل للتهيئة من الـ Replica Shards. القواعد العملية المستخدمة في نشرات Elasticsearch الكبيرة هي:

  • استهدف 20–50 غيغابايت لكل Shard. الـ Shards خارج هذا النطاق مشكلة: إذا كانت صغيرة جدًا، فإن عبء تنسيق آلاف الـ Shards يُدهور كمون الاستعلام؛ وإذا كانت كبيرة جدًا، تتعثر عمليات دمج القطاعات، ويطول التعافي بعد فشل العقدة، ويرتفع ضغط الذاكرة.
  • Replica واحد على الأقل في الإنتاج. وجود صفر Replicas يعني أن أي فشل لعقدة يُسبب فقدانًا للبيانات وحالة احمرار للكلاستر.
  • إجمالي عدد الـ Shards يُحدد استخدام الـ Heap. Elasticsearch يحتفظ ببيانات وصفية لكل Shard في الـ Heap. في بيئات الحجم الكبير، كل Shard يستهلك بضعة كيلوبايتات في كل عقدة. كلاستر يحوي 100,000 Shard سيُصاب بنفاد الذاكرة حتى على عقد بـ 64 غيغابايت.
التكاثر المفرط للـ Shards هو السبب الأول لفشل كلاسترات Elasticsearch في الإنتاج. عندما تكتب Fluentd أو Logstash Index جديدًا لكل خدمة في كل يوم، سينتج عن ذلك آلاف الـ Shards خلال أسابيع. استخدم دائمًا ILM مع شروط التدوير لمنع الانتشار غير المنضبط للـ Shards.

لفحص توزيع الـ Shards الآن:

# التحقق من أعداد وأحجام الـ Shards للكلاستر GET /_cat/shards?v&h=index,shard,prirep,state,docs,store,node&s=store:desc # إيجاد أكبر الـ Shards (مفيد لاكتشاف الـ Shards الضخمة) GET /_cat/shards?v&h=index,shard,prirep,store,node&s=store:desc&size=20 # رؤية الـ Shards غير الموزّعة وسبب عدم توزيعها GET /_cluster/allocation/explain

الـ Mappings: مخطط البيانات عند الكتابة

يُوصف Elasticsearch أحيانًا بأنه "بدون مخطط"، لكن هذا مضلل. ما يفعله فعليًا هو الـ Dynamic Mapping — أول مستند يصل يُحدد أنواع الحقول لكل المستندات اللاحقة. هذا خطير في خطوط معالجة السجلات. حقل يصل كـ long في سجلات خدمة واحدة ولكن كـ keyword في خدمة أخرى سيُولّد تعارضًا في الـ Mapping، مما يتسبب في رفض أحد المستندين بخطأ 400 وفقده بصمت اعتمادًا على إعداد الاستيعاب.

النمط المعتمد في الإنتاج هو تعريف Index Template صريح مع Component Templates تُثبّت الحقول المعروفة وتُهيّئ افتراضات منطقية للباقي. استخدم dynamic: strict للحقول التي تريد فيها تطبيقًا صارمًا، أو dynamic: true مع كتلة dynamic_templates تستقبل الحقول غير المتوقعة وتُعيّنها كـ keyword بدلًا من السماح لـ Elasticsearch باكتشاف نوع رقمي أو تاريخ قد يتعارض لاحقًا.

# إنشاء Component Template لحقول الطابع الزمني ومستوى السجل PUT /_component_template/logs-base-mappings { "template": { "mappings": { "dynamic_templates": [ { "strings_as_keyword": { "match_mapping_type": "string", "mapping": { "type": "keyword", "ignore_above": 1024 } } } ], "properties": { "@timestamp": { "type": "date" }, "log.level": { "type": "keyword" }, "message": { "type": "text", "norms": false }, "host.name": { "type": "keyword" }, "service.name": { "type": "keyword" }, "trace.id": { "type": "keyword" }, "http.response.status_code": { "type": "short" } } }, "settings": { "number_of_shards": 2, "number_of_replicas": 1, "index.codec": "best_compression", "index.refresh_interval": "5s" } } } # إنشاء Index Template وربطه بـ Data Stream PUT /_index_template/logs-app { "index_patterns": ["logs-app-*"], "data_stream": {}, "composed_of": ["logs-base-mappings"], "priority": 200 }
اضبط norms: false على حقل message. الـ Norms تخزّن بيانات تطبيع طول المستند لكل حقل لأغراض ترتيب مدى الصلة — لا فائدة منها في استعلامات السجلات حيث يهمك المطابقة الدقيقة لا ترتيب BM25. تعطيلها يوفر نحو بايت واحد لكل مستند لكل حقل، وهو ما يتراكم ليصل إلى غيغابايتات في الكلاسترات ذات الحجم الكبير.

إدارة دورة حياة الـ Index (ILM)

ILM هو محرك السياسات الذي ينقل الـ Indices عبر مراحل محددة — hot → warm → cold → frozen → delete — كلما تقدم عمرها. في كلاسترات السجلات هذا ليس خيارًا: بدون ILM، تمتلئ الأقراص وستجد نفسك تحذف الـ Indices يدويًا في الثالثة صباحًا.

المحركات الرئيسية للانتقال هي شروط التدوير (Rollover Conditions) في المرحلة الساخنة: عندما يصل Index إلى حد أقصى للعمر أو الحجم أو عدد المستندات، يُنشئ ILM Index جديدًا للكتابة ويصبح القديم للقراءة فقط ويبدأ انتقاله نحو الدفء. الحدود النموذجية في شركة متوسطة الحجم: تدوير عند max_age: 1d أو max_size: 40gb، انتقال للـ Warm بعد يومين (دمج قسري إلى قطعة واحدة + تقليص الـ Shards)، انتقال للـ Cold بعد 7 أيام (تحميل كـ Searchable Snapshot على التخزين السحابي)، حذف بعد 30–90 يومًا (نافذة الامتثال التنظيمي).

Elasticsearch ILM Phase Transitions HOT Primary shards Active writes Rollover WARM Force-merge Shrink shards Age / size COLD Searchable snapshot Object storage Retention DELETE Policy expiry Compliance window اليوم 0 يوم 1-2 يوم 7 يوم 30-90 مراحل انتقال ILM لـ Data Stream السجلات
ينقل ILM الـ Indices من المرحلة الساخنة (كتابة نشطة) عبر الدافئة (مدمجة، قراءة فقط) والباردة (لقطة على التخزين السحابي) وصولًا للحذف، مما يجعل تكاليف التخزين متوقعة.
# تعريف سياسة ILM للإنتاج لسجلات التطبيقات PUT /_ilm/policy/logs-app-policy { "policy": { "phases": { "hot": { "min_age": "0ms", "actions": { "rollover": { "max_age": "1d", "max_primary_shard_size": "40gb" }, "set_priority": { "priority": 100 } } }, "warm": { "min_age": "2d", "actions": { "set_priority": { "priority": 50 }, "readonly": {}, "forcemerge": { "max_num_segments": 1 }, "shrink": { "number_of_shards": 1 } } }, "cold": { "min_age": "7d", "actions": { "set_priority": { "priority": 0 }, "searchable_snapshot": { "snapshot_repository": "s3-log-archive" } } }, "delete": { "min_age": "30d", "actions": { "delete": {} } } } } }

قراءة صحة الكلاستر

يعرض Elasticsearch صحة الكلاستر كـ أخضر / أصفر / أحمر. الأحمر يعني أن Shard أساسيًا واحدًا على الأقل غير موزّع — بعض البيانات غير متاحة والكتابات على ذلك الـ Shard مرفوضة. الأصفر يعني أن جميع الـ Primary Shards موزّعة لكن Replica Shard واحدًا على الأقل غير موزّع — الكلاستر يعمل بشكل كامل لكنه بدون تكرار لتلك الـ Shards. الأخضر يعني أن كل شيء موزّع وبصحة جيدة.

في كلاستر سجلات الإنتاج، حالة صفراء مستمرة خلال إعادة تشغيل متدرجة متوقعة ومقبولة. الأحمر هو دائمًا حادثة. أسرع مسار للفرز هو:

  1. تحقق من GET /_cluster/health?pretty — انظر إلى unassigned_shards وactive_shards_percent_as_number.
  2. نفّذ GET /_cluster/allocation/explain — سيخبرك Elasticsearch بالضبط لماذا الـ Shard غير موزّع (حد القرص، لا عقد مؤهلة، إلخ).
  3. إذا كانت حدود القرص هي السبب، الحدود الافتراضية هي 85% (منخفض — إيقاف توزيع الـ Replicas)، 90% (مرتفع — نقل الـ Shards بعيدًا)، 95% (مرحلة الفيضان — جميع الـ Indices تصبح للقراءة فقط). تحقق بـ GET /_cat/nodes?v&h=name,diskUsed,diskAvail,diskTotal,diskPercent.
راقب jvm.mem.heap_used_percent على كل عقدة. فوق 85% استخدام للـ Heap، يبدأ مجمع القمامة (GC) في الـ JVM بالتسبب في توقفات لعدة ثوانٍ. هذا هو السبب الثاني الأكثر شيوعًا لحوادث Elasticsearch في الإنتاج بعد تكاثر الـ Shards. اضبط تنبيهًا عند 75% Heap، وتنبيهًا حرجًا عند 85%.

عادات المشغّل الاحترافي

  • اختبر دائمًا سياسة ILM جديدة على Data Stream غير إنتاجي قبل تطبيقها على Indices الإنتاج — إجراء shrink مدمّر ولا يمكن التراجع عنه.
  • حدد أولوية Index Template الخاص بك (استخدم قيمًا مثل 200) حتى لا تتجاوزها القوالب المدمجة في Elastic ذات الأولوية 100 بشكل غير متوقع.
  • استخدم GET /_data_stream/logs-app-* لرؤية أي Backing Indices موجودة وأيها هو Index الكتابة الحالي — هذا أول ما تتحقق منه عند توقف وصول السجلات.
  • تجنب _forcemerge على الـ Indices الساخنة. إنها عملية كثيفة الاستخدام للـ I/O وتتنافس مع الفهرسة النشطة ويمكن أن تزعزع استقرار العقدة تحت الحمل.