استراتيجيات أخذ العينات
استراتيجيات أخذ العينات
لا يستطيع نظام إنتاج يعالج 100,000 طلب في الثانية أن يسجل كل span لكل طلب. قد يولد trace واحد لاستدعاء نموذجي بين الخدمات المصغرة 30-50 span؛ وبدقة كاملة يعني ذلك 3-5 ملايين سجل span في الثانية، قبل أن تحتسب النطاق الترددي للاستيعاب وعبء المعالج لعمليات التسلسل والتصدير على كل pod تطبيق. أخذ العينات هو الطريقة التي تجعل التتبع الموزع مجدياً اقتصادياً على نطاق واسع دون فقدان الـ traces المهمة فعلاً.
التوتر الجوهري في أخذ العينات هو هذا: تريد تقليل الحجم، لكن الـ traces التي تحتاجها أكثر — البطيئة، والتي تحتوي على أخطاء، والشاذة النادرة إحصائياً — هي بالضبط تلك التي لا تستطيع التضحية بها. الحصول على هذا التوازن الصحيح يفصل المؤسسات التي تستخرج قيمة هندسية حقيقية من بنيتها التحتية للتتبع عن تلك التي تخزن نسبة عشوائية من حركة مرورها وتسميها "قابلية الملاحظة".
أخذ العينات من الرأس (Head-Based Sampling)
أخذ العينات من الرأس يتخذ قرار الاحتفاظ أو الإسقاط في بداية الـ trace تماماً — عند أول span، قبل حدوث أي معالجة في المراحل اللاحقة. يُشفَّر القرار في حقل sampled ضمن ترويسة W3C Trace Context ويُنقل إلى كل خدمة في سلسلة الاستدعاء. كل خدمة تحترم هذا الحقل: إذا أُخذت عينة من الـ span الجذر، تُسجَّل كل فروعه؛ وإذا لم تُؤخذ، تتخطى كل خدمة الأداة لذلك الطلب تماماً.
الشكل الأكثر شيوعاً هو أخذ العينات الاحتمالي (المعدل): أخذ عينات من 1% أو 5% من جميع الطلبات الواردة عشوائياً. هذا رخيص بشكل تافه — مقارنة رقم عشوائي واحد لكل طلب — وينتج عرضاً إحصائياً تمثيلياً لحركة مرورك.
العيب المميت: سيُسقط محدد العينات بنسبة 1% نسبة 99% من أخطاء 500 ونسبة 99% من ارتفاعات زمن الاستجابة p99.9. عند 100k طلب في الثانية مع معدل خطأ 0.01% (100 خطأ/ثانية)، ستشهد إحصائياً trace خطأ واحداً فقط في الثانية.
أخذ العينات من الذيل (Tail-Based Sampling)
أخذ العينات من الذيل يخزن trace كاملاً في الذاكرة، ينتظر وصول جميع الـ spans، يقيّم الـ trace الكامل وفق سياسة، ثم يقرر الاحتفاظ به أو إسقاطه. لأن القرار يحدث بعد معرفة النتيجة، يمكنك تطبيق سياسات تلتقط فعلاً ما يهم:
- أخذ عينات دائم للأخطاء — أي trace يحتوي على span بـ
status.code = ERRORيُحتفظ به بنسبة 100%. - أخذ عينات بناءً على عتبة زمن الاستجابة — احتفظ بكل الـ traces التي يتجاوز إجمالي مدتها عتبة محددة.
- تحديد المعدل لكل مسار — احتفظ بـ N trace كحد أقصى في الثانية لكل endpoint.
- سياسات مركبة — ادمج قواعد متعددة: دائمة للأخطاء، مبنية على زمن الاستجابة للبطيئة، احتمالية لما تبقى.
التكلفة معمارية: أخذ العينات من الذيل يتطلب طبقة تجميع ذات حالة (stateful). يجب توجيه جميع الـ spans لـ traceId معين إلى نفس instance المجمع — لا يمكن توزيعها عبر مجمعات عديمة الحالة. يُطبَّق هذا عبر التوجيه بمعرف الـ trace: موازن تحميل أمام طبقة مجمعك يجزئ traceId إلى شظية collector محددة.
ضبط أخذ العينات من الذيل في OTel Collector
معالج tailsampling في OpenTelemetry Collector ينفذ هذا النمط. فيما يلي إعداد تمثيلي للإنتاج: الاحتفاظ دائماً بالأخطاء والـ traces الأبطأ من ثانية واحدة، وتحديد معدل الـ traces الصحية بـ 10 في الثانية، واستخدام نافذة تخزين مؤقت لمدة 30 ثانية للـ spans المتأخرة.
decision_wait على ضعف p99 لأطول استدعاء بين خدماتك على الأقل. إذا كان استعلام قاعدة البيانات يستغرق أحياناً 20 ثانية، تضمن نافذة 30 ثانية وصول الـ span قبل اتخاذ القرار. راقب مقياس otelcol_processor_tail_sampling_late_span_goes_to_new_decision بانتظام.Load-Balancing Exporter: التوجيه بمعرف الـ Trace
لأن أخذ العينات من الذيل يتطلب وجود جميع الـ spans لـ trace ما على شظية واحدة، تحتاج إلى طبقة موازنة تحميل تُوجِّه بـ traceId لا بالاتصال أو Round-Robin. loadbalancingexporter في OTel Collector يفعل هذا بالضبط — يجلس في طبقة "router" تستقبل الـ spans من جميع الخدمات وتُعيدها إلى مجموعة ثابتة من instances collector عبر التجزئة المتسقة على معرف الـ trace.
أخذ العينات على نطاق واسع: أنماط فشل الإنتاج
عدة أنماط فشل تؤلم الفرق التي اختبرت أخذ العينات من الذيل على أحمال صغيرة فقط:
- فيضان المخزن المؤقت: إذا كان
num_tracesصغيراً جداً وتوافدت ذروة حركة مرور، يُخلي المجمع الـ traces الأقدم قبل اتخاذ القرار — يسقط بصمت الـ traces التي قد تكشف السبب الجذري. راقبotelcol_processor_tail_sampling_sampling_decision_timer_firedوdropped_spans. - النقاط الساخنة في الشظايا: التجزئة المتسقة توزع معرفات الـ trace بالتساوي من الناحية النظرية، لكن خدمة تولد عدداً عالياً جداً من الـ spans لكل trace يمكن أن تُثقل شظية واحدة.
- ضغط الذاكرة: كل span مخزن يشغل heap على المجمع. احرص على تحجيم pods المجمع بشكل مناسب.
- إعادة تشغيل المجمع تُفقد القرارات الجارية: إعادة التشغيل المتدحرجة لـ pods الشظية في منتصف نافذة القرار تُنتج "دماغاً منقسماً" — بعض الـ spans للـ trace ذاته يذهب إلى الـ pod القديم وبعضها إلى الجديد.
الاستراتيجية الهجينة: الرأس والذيل معاً في الممارسة
المؤسسات الهندسية من الدرجة الأولى لا تختار أحدهما فقط — بل تُطبق كليهما بالتراتب. نمط إنتاج شائع على نطاق واسع:
- أخذ عينات من الرأس على جانب العميل في SDK: إسقاط endpoints فحص الصحة (
/healthz،/readyz) كلياً بنسبة 100% — هذه تولد حجماً هائلاً من الـ spans دون أي قيمة تشخيصية. استخدمParentBasedsampler حتى تحترم الخدمات اللاحقة قرار الخدمة الأعلى. - أخذ عينات احتمالي من الرأس في مجمع طبقة Router بنسبة 20-50% — هذا يقلل البيانات التي يجب على طبقة الـ sampler تخزينها مؤقتاً.
- تقييم سياسة أخذ العينات من الذيل في طبقة الـ sampler: احتفظ بـ 100% من الأخطاء والـ traces البطيئة من الـ 20-50% المصفاة مسبقاً، وطبّق تحديد المعدل على الباقي.
النتيجة: حجم الـ trace الإجمالي المرسل إلى Backend قد يكون 0.5-2% من حجم الطلبات الخام، لكن ضمن هذه النسبة الصغيرة لديك تغطية شبه 100% لجميع الأخطاء وحالات الشذوذ في زمن الاستجابة.