الواجهات الخلفية: Jaeger و Tempo
الواجهات الخلفية: Jaeger و Tempo
بعد أن يتلقى OpenTelemetry Collector الـ spans من الخدمات المُجهَّزة، يجب أن تُخزَّن في مكان يدعم التخزين والاستعلام الفعّالَين. يهيمن نظامان على بيئات الإنتاج في عام 2025: Jaeger (المشروع الناضج الحائز على شهادة CNCF) و Grafana Tempo (البديل الحديث القائم على السحابة والمُحسَّن من حيث التكلفة). اختيار الواجهة الخلفية الخاطئة — أو تهيئة الصحيحة بشكل خاطئ — يُفضي إلى تكاليف تخزين متفاقمة أو traces تختفي تماماً في اللحظة التي تحتاج فيها إلى تتبع عطل حرج. يتناول هذا الدرس كلا النظامَين بعمق: البنية التحتية، ومحركات التخزين، وآليات الاستعلام، وكيفية ربط الـ traces بالسجلات والمقاييس لتحقيق الرصد الشامل الموحَّد.
Jaeger: البنية والتخزين
أطلقت Uber مشروع Jaeger مفتوح المصدر عام 2017. كانت بنيته الأصلية تعتمد ثنائيات منفصلة لـ collector و query و agent، غير أن عمليات النشر الحديثة تجمع هذه المكونات في ملف ثنائي واحد all-in-one (للتطوير) أو زوج collector + query مدعومَين بمخزن خارجي (للإنتاج). لا يزال البروتوكول الأصلي لـ Jaeger هو Thrift عبر UDP، إلا أنه منذ الإصدار 1.35 بات يدعم OTLP gRPC/HTTP بشكل أصيل، ما يتيح لـ OTel Collector الإرسال المباشر دون الحاجة إلى Jaeger agent القديم.
تتوفر للتخزين ثلاثة خيارات مهمة في الإنتاج:
- Elasticsearch / OpenSearch — الخيار الافتراضي في معظم الشركات. تُخزَّن الـ traces كوثائق JSON مُفهرَسة بـ
traceIDوserviceNameوstartTime. يُعالَج الاحتفاظ بالبيانات عبر ILM (إدارة دورة حياة المؤشرات). العيب: يُضخِّم الـ ES index التخزينَ بشكل كبير عند معدلات نقل مرتفعة (>50 ألف span في الثانية). - Cassandra — الواجهة الأصلية لـ Uber. أداء كتابة ممتاز؛ الاحتفاظ بالبيانات عبر TTL أمر يسير. أثقل تشغيلياً من ES لمعظم الفرق.
- Badger (مدمج) — قرص محلي، للتطوير فقط. لا تستخدمه مطلقاً في الإنتاج.
تعرض خدمة Jaeger Query واجهة مستخدم على المنفذ 16686 وواجهة gRPC على المنفذ 16685. تتيح الواجهة البحث بالخدمة والعملية والوسوم ونطاق المدة والنافذة الزمنية. الاستعلام الداخلي هو بحث مُفهرَس بالوسوم في الواجهة الخلفية، لا مسح نصي شامل.
jaeger-span-YYYY-MM-DD). عند تجاوز 10 ملايين span يومياً، عيِّن --es.use-aliases=true و --es.rollover-on-create=true حتى يتحكم ILM في التدوير بدلاً من الحدود التقويمية. هذا يتفادى "انفجار الـ shards صباح الاثنين" حيث ينمو مؤشر عطلة نهاية الأسبوع بلا قيد.
Grafana Tempo: البنية والتخزين
صُمِّم Tempo بهدف واحد: جعل تخزين الـ traces رخيصاً كتخزين الكائنات. يكتب الـ spans مباشرةً إلى S3 أو GCS أو Azure Blob على شكل كتل Parquet، مع فهرس صغير في الذاكرة / القرص المحلي يحتوي فقط على traceID → موقع الكتلة. هذا الفهرس هو سبب اختلاف نموذج استعلام Tempo جذرياً عن Jaeger: يجب أن تعرف traceID لاسترداد trace. يبدو هذا محدوداً، لكنه تصميم مقصود — يُحيل Tempo البحث في خصائص الـ span إلى مقاييس Prometheus واستعلامات السجلات، مما يُبقي تكاليف التخزين أقل بمرتبة من Jaeger المدعوم بـ ES.
منذ Tempo 2.0، يتيح لغة الاستعلام TraceQL البحثَ المعتمد على الخصائص دون معرفة traceID مسبقاً، مدعومةً بفهرس عمودي جديد يُسمى Tag Value Index. هذا يُغلق معظم الفجوة في تجربة المستخدم مقارنةً بـ Jaeger.
الاستعلام عن الـ Traces: TraceQL مقابل واجهة Jaeger
نموذج استعلام واجهة Jaeger بسيط: اختر خدمة، اختر عملية، حدد نطاقاً زمنياً ومُرشِّح وسوم، انقر Find Traces. تُترجِم الواجهة الخلفية هذا إلى استعلام متعدد الأطراف في ES على مؤشر الوسوم. هذا سريع للتصحيح العارض، لكنه يُرجع فقط الـ traces التي تحتوي على الـ span المطابق — لا توجد لغة للتعبير عن شروط متعددة الـ span كـ "الـ span الخاص بـ db.query استغرق أكثر من 500 مللي ثانية والـ span الأصلي HTTP أرجع 200".
تسد TraceQL هذه الفجوة بصياخ pipeline مستوحاة من LogQL:
ربط الـ Traces بالسجلات
أكثر سير عمل التصحيح قيمةً في الأنظمة الموزعة هو trace → span → سطور سجل مترابطة. يعمل هذا لأن OTel يُشيع traceID و spanID في كل سياق، ويُدرِجهما مكتبة التسجيل في كل سجل. في Grafana، يُحوِّل derived field في مصدر بيانات Loki حقل السجل الخام إلى رابط قابل للنقر يفتح الـ trace المطابق في Tempo:
على مستوى التطبيق، يجب أن يُرسل المُسجِّل trace_id و span_id كحقول منظمة. مع opentelemetry-instrumentation-logging الخاص بـ OpenTelemetry (Python) أو OTel Log Bridge API (Java/Go)، يحدث هذا تلقائياً عند استخدام سياق OTel. إن كنت تستخدم مُسجِّلاً مخصصاً، استخرج المعرِّفات يدوياً:
ربط الـ Traces بالمقاييس
تولِّد ميزة service graph في Tempo مقاييس Prometheus (معدل الطلبات، معدل الأخطاء، مدرَّجات المدة) من علاقات الـ span — تحديداً من الـ root spans وأبنائها. تُعرِّضها على شكل traces_service_graph_request_total و traces_service_graph_request_failed_total و traces_service_graph_duration_seconds. ربط هذه المقاييس بعرض خريطة الخدمة في Grafana يمنحك رسماً بيانياً حياً لتبعيات نظامك بالكامل مع مقاييس RED مُستمَدة من الـ traces فقط — دون أي استخدام Prometheus على كل خدمة.
الاختيار بين Jaeger وTempo
في عمليات النشر الجديدة عام 2025، يُعدُّ Tempo الخيار الافتراضي في معظم الشركات التي تعمل على Kubernetes مع مكدسات Grafana القائمة — تُلغي الواجهة الخلفية لتخزين الكائنات العبء التشغيلي لتشغيل Elasticsearch أو Cassandra، والتكامل الأصيل مع Grafana يجعل ربط الـ traces/logs/metrics سلساً. يظل Jaeger الخيار الصحيح حين تمتلك مؤسستك بنية Elasticsearch ضخمة قائمة لأغراض أخرى (تكلفة تشغيل مشتركة)، أو حين تحتاج إلى واجهة Jaeger الناضجة لفرق غير مألوفة بـ Grafana، أو حين تستلزم معدلات كتابة Cassandra عند نطاق واسع جداً (>100 ألف span/ثانية لكل عقدة).
كلا الواجهتَين تقبلان OTLP بشكل أصيل، لذا طبقة الأجهزة تبقى متطابقة بغض النظر عن اختيارك — ويمكنك تشغيل كليهما بالتوازي أثناء الهجرة دون المساس بكود التطبيق.