لماذا التتبع الموزع؟
لماذا التتبع الموزع؟
لديك مقاييس Prometheus. لديك سجلات منظمة في Loki أو Elasticsearch. لوحات Grafana خضراء، وأهداف مستوى الخدمة ضمن الميزانية، ومع ذلك — يقضي مهندسوك الكبار ساعات في جلسات ما بعد الحوادث يتتبعون ارتفاعاً في زمن الاستجابة بمقدار 400ms أثر على 0.3% من الطلبات الثلاثاء الماضي بعد الظهر. المقاييس أظهرت اضطراباً طفيفاً. السجلات أظهرت بعض استعلامات قاعدة البيانات البطيئة. لكن لم يستطع أحد الإجابة على السؤال البسيط: أي خدمة تسببت فعلاً في التباطؤ، ولماذا أصابت تلك الطلبات تحديداً؟
هذه هي المشكلة التي بُني التتبع الموزع لحلها. إنه ليس بديلاً عن المقاييس أو السجلات — بل هو الركيزة الثالثة التي تتيح لك طرح فئة مختلفة جوهرياً من الأسئلة: ماذا حدث لهذا الطلب المحدد أثناء رحلته عبر نظامك؟
مشكلة إسناد زمن الاستجابة
في بنية الخدمات المصغرة، طلب واحد موجه للمستخدم يتشعب إلى عشرات الاستدعاءات للخدمات الخارجية. طلب الدفع قد يمر بـ API gateway، وخدمة مصادقة، وخدمة سلة التسوق، وخدمة التسعير، وخدمة الدفع، وخدمة كشف الاحتيال، وخدمة الطلبات — كل منها يستدعي قواعد بياناته الخاصة أو الكاش أو واجهات API خارجية. إجمالي زمن الاستجابة الذي يختبره المستخدم هو مجموع كل هذه الوقفات، بالإضافة إلى وقت الشبكة بينها.
المقاييس تعطيك إجماليات: "زمن p99 لخدمة الدفع هو 380ms." لكن أي 380ms؟ هل هي 200ms في خدمة الدفع، و100ms في فحص الاحتيال، و80ms في كل شيء آخر؟ أم 350ms في خدمة السلة عند إخفاق الكاش؟ هذان مشكلتان مختلفتان تماماً بحلول مختلفة تماماً، والمقياس الإجمالي لا يخبرك بأيهما تواجه.
السجلات تخبرك بما حدث داخل كل خدمة، لكن ربط قصة طلب واحد عبر عشر خدمات من عشرة تدفقات سجلات منفصلة — كل منها بانحراف طابع زمني خاص، وتنسيق سجل خاص، ومعدل أخذ عينات خاص — أمر مؤلم فعلاً على نطاق واسع. يتطلب من إنسان ربط الإدخالات يدوياً بمعرف الطلب، غالباً عبر شاشات واجهة مستخدم مختلفة أو خطوط أوامر grep.
التتبعات مقابل المقاييس مقابل السجلات
معرفة متى تستخدم كل نوع من أنواع الإشارات هي مهارة أساسية لمهندس SRE. إنها ليست قابلة للتبادل — لكل منها نقطة قوة مميزة وملف تكلفة مميز.
المقاييس هي قياسات رقمية مجمّعة مسبقاً تُأخذ عينات منها أو تُحسب عبر نوافذ زمنية. رخيصة التخزين (العداد بضعة بايتات)، سريعة الاستعلام (قواعد بيانات السلاسل الزمنية مُحسَّنة لاستعلامات النطاق)، وممتازة للتنبيه على الأحوال المعروفة. ضعفها هو انخفاض القيم الكاردينالية: عداد Prometheus له تسميات، لكن لا يمكنك إضافة تسمية user_id لعداد يُطلق ملايين المرات في الثانية دون تفجير قيمك الكاردينالية وتدمير أداء الجمع. المقاييس تجيب: هل ثمة خطأ ما، وما مدى انتشاره؟
السجلات هي سجلات أحداث غير قابلة للتغيير تُصدر في نقاط اعتباطية في الكود. تحمل سياقاً غير محدود — أي زوج مفتاح/قيمة تريد إضافته. السجلات المنظمة الحديثة (سطور JSON، logfmt) جعلت السجلات قابلة للاستعلام، وأدوات مثل Loki أو Datadog Logs تتيح الفلترة والتجميع عبر حقول ذات قيم كاردينالية عالية. ضعفها هو التكلفة والترابط: عند معدلات طلبات عالية، تخزين وفهرسة كل سطر سجل مكلف، وربط السجلات من خدمات متعددة لطلب واحد يتطلب معرفاً مشتركاً صريحاً.
التتبعات هي سجلات مرتبطة سببياً لرحلة الطلب. التتبع هو شجرة من الامتدادات، حيث يمثل كل امتداد وحدة عمل في خدمة واحدة. الامتدادات تحمل بيانات توقيت وحالة وبيانات وصفية وروابط لامتداداتها الأبوية. التتبعات تتفوق في إسناد زمن الاستجابة (أي خدمة استغرقت كم)، ورسم خرائط التبعيات (أي الخدمات تستدعي أي)، وتصحيح الأخطاء على مستوى الطلب. ضعفها هو التكلفة: تخزين كل امتداد لكل طلب عند إنتاجية عالية مكلف، لهذا استراتيجيات أخذ العينات (التي يغطيها الدرس 7) ضرورية.
تشريح التتبع: مخطط الشلال
يُصوَّر التتبع كـ مخطط شلال (يُسمى أيضاً مخطط اللهب أو مخطط غانت). المحور الأفقي هو الزمن. كل صف هو امتداد واحد — وحدة عمل في خدمة واحدة. الامتدادات متداخلة لإظهار السببية: إن أطلق الامتداد أ الامتداد ب، يكون ب منتذراً تحت أ، ونطاق زمنه يقع ضمن نطاق أ.
كل امتداد يحمل مجموعة قياسية من الحقول:
- trace_id — المعرف الـ 128 بت المشترك بين كل امتداد في نفس التتبع.
- span_id — معرف الـ 64 بت الفريد لهذا الامتداد.
- parent_span_id — span_id الامتداد الذي أطلق هذا (فارغ للامتداد الجذر).
- name — اسم عملية مقروء للإنسان، مثل
cart.GetItemsأوdb.query. - start_time وend_time — طوابع زمنية بدقة النانوثانية.
- status — OK أو ERROR أو UNSET.
- attributes — أزواج مفتاح/قيمة بسياق اعتباطي:
http.method،db.statement،user.id،cart.item_count. - events — تعليقات توضيحية مُوقَّتة ضمن عمر الامتداد (مثل "إخفاق الكاش"، "محاولة إعادة رقم 2").
لماذا لا تستطيع المقاييس والسجلات وحدها فعل ذلك
إليك السيناريو الذي أقنع كل شركة تقنية كبرى بالاستثمار الجاد في التتبع الموزع. لديك انحدار في زمن p99 — الدفع انتقل من 120ms إلى 400ms بين عشية وضحاها. المقاييس تُظهر الانحدار بوضوح. تعرف أنه حقيقي. ماذا الآن؟
مع المقاييس وحدها: تتحقق من لوحات الخدمات الخارجية واحدة تلو الأخرى. Auth تبدو بخير. Cart تبدو بخير. Fraud تبدو بخير — p99 فيها 280ms، ارتفع من 95ms، لكنك تلاحظ هذا فقط بعد 25 دقيقة من البحث في اللوحات، ولا تزال لا تعرف إن كانت fraud هي المُسبِّب أم ضحية لتباطؤ قاعدة البيانات.
مع السجلات وحدها: تبحث grep عن الطلبات ذات زمن الاستجابة العالي، تجد معرف تتبع في السجلات، ثم تفتح أربع واجهات بحث سجلات مختلفة لإيجاد كل سطور السجل بذلك المعرف، وتحسب الفجوات الزمنية بينها يدوياً. ممكن، لكن يستغرق 45 دقيقة ويتطلب أن كل خدمة سجلت بالفعل معرف التتبع (وهو ما لا يحدث في أغلب الأحيان).
مع التتبعات: تفتح واجهة التتبع، تُفلتر بـ service=checkout AND duration>200ms، تنقر على تتبع واحد، وترى الشلال فوراً. امتداد fraud-check أحمر وعرضه 275ms. تنقر عليه، تقرأ سماته: fraud.provider=acme-fraud-api، http.url=https://api.acmefraud.com/v2/check. تتحقق من صفحة حالة مزود الاحتيال — كان لديهم تدهور بدأ الساعة 23:47 الليلة الماضية. إجمالي وقت التحقيق: 4 دقائق.
نشر السياق: الغراء الذي يربط كل شيء
لكي يعمل التتبع عبر حدود الخدمات، يجب أن كل خدمة تمرر سياق التتبع للخدمة التالية. حين تستدعي الخدمة أ الخدمة ب عبر HTTP، تحقن معرف التتبع ومعرف الامتداد في ترويسات HTTP. حين تستدعيها عبر gRPC، تحقنهما في بيانات gRPC الوصفية. حين تنشر رسالة في Kafka، تحقنهما في ترويسات الرسالة. الخدمة ب تستخرج السياق عند الاستلام، تنشئ امتداداً جديداً ابناً، وتكمل التتبع.
تنسيق الترويسة القياسي (المحدد بمواصفة W3C Trace Context، المعتمد من OpenTelemetry) هو:
نشر السياق هو ما يحوّل مجموعة امتدادات منفصلة لكل خدمة إلى تتبع موزع متماسك. بدونه لديك قياسات محلية منفصلة. بوجوده لديك رسم بياني سببي كامل لرحلة الطلب عبر نظامك بأكمله.
الحالة الإنتاجية: حين يُسدد التتبع تكلفته
للتتبع الموزع تكاليف حقيقية: عمل التجهيز، بنية تحتية للجامع، تخزين للامتدادات. عند إنتاجية عالية (أكثر من 10,000 طلب في الثانية)، تخزين كل امتداد بدون تخطيط مُكلف. لكن حساب العائد على الاستثمار واضح لأي منظمة تُشغّل خدمات مصغرة على نطاق واسع.
تأمل: انحدار في زمن p99 يُدهور معدل إتمام الدفع بنسبة 2% لشركة تعالج 10 ملايين دولار يومياً. كل ساعة يستمر فيها الانحدار تُكلف نحو 83 ألف دولار في إيرادات ضائعة. إن قلّصت التتبعات وقت التشخيص من 3 ساعات (ربط السجلات يدوياً) إلى 10 دقائق (شلال التتبع)، ذلك يوفر ساعتين و50 دقيقة لكل حادث — نحو 233 ألف دولار لكل حادث. تكلفة تشغيل مجموعة Jaeger أو Tempo مع أخذ عينات رأسية بنسبة 10% بضعة آلاف دولار شهرياً. الحساب ليس متقارباً.
هذا هو السبب في انتقال التتبع الموزع من نموذج أولي للبحث في Google (Dapper، 2010) إلى متطلب أساسي لأي شركة تُشغّل خدمات مصغرة على نطاق واسع. وهو أيضاً سبب إنشاء OpenTelemetry — المعيار المحايد للبائعين لإصدار التتبعات والمقاييس والسجلات — لمنع الاحتكار البائعي لحزم SDK التتبع الاحتكارية. الدروس الأخرى في هذا الدرس التعليمي تبني منظومة OpenTelemetry الكاملة، من التجهيز إلى الـ backends إلى أخذ العينات إلى سير عمل التصحيح في الإنتاج.