Tail Sampling بالعربي: قلل تكلفة الـ Tracing من غير ما تعمي نفسك
مستوى القارئ: متوسط
هتخرج من المقال بإعداد عملي يخلي OpenTelemetry Collector يحتفظ بالـ traces المهمة، بدل ما يبعث كل الضوضاء لنظام التخزين.
المشكلة باختصار
الطريقة الشائعة إنك تشغل tracing وتبعت كل span إلى Tempo أو Jaeger أو أي backend. الطريقة دي بتفشل لما حجم الطلبات يكبر. هتشوف كل حاجة، لكن هتدفع في storage وnetwork وquery latency، وبعد شهر هتبدأ تقلل retention بدل ما تصلح التصميم.
الافتراض إن عندك خدمة API بتستقبل حوالي 5 ملايين request يوميًا، وكل request ينتج trace متوسطه 12 span. لو احتفظت بكل شيء، أنت بتتعامل مع 60 مليون span يوميًا. حتى لو كل span بعد الضغط حجمه 500 بايت فقط، ده حوالي 30GB بيانات خام يوميًا قبل metadata والفهارس.
الفكرة: قرار العينة بعد ما الـ trace يكتمل
ركز في الفرق. Head sampling بياخد القرار في بداية الطلب. يعني ممكن يرمي request فشل في آخر خطوة لأنه كان اختار من البداية إنه لا يحتفظ به. Tail sampling يستنى شوية، يجمع spans الخاصة بنفس trace_id، ثم يقرر.
مثال بسيط: عندك طلب checkout بدأ طبيعي، وبعد 1.8 ثانية فشل في payment provider. لو استخدمت sampling عشوائي 5% من البداية، غالبًا الطلب ده هيختفي. مع tail sampling تقدر تقول: احتفظ بكل ERROR، واحتفظ بكل trace أبطأ من 2000ms، وخد 5% فقط من النجاح العادي.
علميًا، الـ OpenTelemetry Collector مبني من receivers وprocessors وexporters داخل pipelines. الـ processor يقدر يغير أو يفلتر البيانات قبل التصدير، والـ tail_sampling processor في توزيعة contrib يقيّم traces بناءً على policies مثل status_code وlatency وprobabilistic.
إعداد عملي قابل للنسخ
استخدم الإعداد التالي كبداية. هو يستقبل traces عبر OTLP، يحمي الذاكرة، يطبق tail sampling، ثم يرسل النتائج إلى Tempo. عدّل endpoint حسب بيئتك.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 5s
limit_mib: 1024
spike_limit_mib: 256
tail_sampling:
decision_wait: 20s
num_traces: 50000
expected_new_traces_per_sec: 2500
decision_cache:
sampled_cache_size: 100000
non_sampled_cache_size: 100000
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: keep-slow-traces
type: latency
latency:
threshold_ms: 2000
- name: sample-success-noise
type: probabilistic
probabilistic:
sampling_percentage: 5
batch:
timeout: 5s
send_batch_size: 1024
exporters:
otlp/tempo:
endpoint: tempo:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, batch]
exporters: [otlp/tempo]
الترتيب مهم. ضع processors اللي تضيف سياق مهم قبل tail_sampling. بعد tail_sampling استخدم batch عشان التصدير يبقى أقل تكلفة. لو عندك k8sattributes أو resource processor، غالبًا مكانهم قبل tail_sampling.
الأرقام المتوقعة والـ trade-off
لو 2% من الطلبات errors أو أبطأ من 2 ثانية، ومعاهم 5% من باقي النجاح، هتحتفظ تقريبًا بـ 6.9% من traces بدل 100%. في مثال 60 مليون span يوميًا، ده ينزل الحجم النظري من 30GB إلى حوالي 2.1GB يوميًا. الرقم تقديري، لكنه كافي لتوضيح الاتجاه.
الـ trade-off هنا واضح. بتكسب تكلفة تخزين أقل وqueries أسرع وتحقيقات أوضح وقت الأعطال. بتخسر ذاكرة داخل الـ Collector، لأن tail_sampling لازم يحتفظ بـ traces مؤقتًا لحد decision_wait. كمان لو spans لنفس trace وصلت لأكثر من Collector instance بدون load balancing مناسب، القرار ممكن يبقى ناقص.
راقب مؤشرات الـ Collector نفسها. لو decision_wait قريب من زمن وصول spans المتأخرة، ارفعه تدريجيًا من 20s إلى 30s. ولو الذاكرة بتضغط، قلل num_traces أو زوّد موارد الـ Collector. أفضل طريقة هنا إنك تقيس قبل وبعد، مش تغير الأرقام بالمزاج.
متى لا تستخدم هذه الطريقة
لا تستخدم tail sampling كحل أول لو حجم الـ traffic صغير والتكلفة مش مشكلة. Head sampling أبسط وأقل استهلاكًا للذاكرة. لا تستخدمه أيضًا لو التحقيقات عندك تحتاج 100% من traces لأسباب امتثال أو forensics. وفي بيئات latency شديدة الحساسية، اختبر Collector منفصل قبل الإنتاج لأن القرار بيتأخر لحد اكتمال الـ trace أو انتهاء decision_wait.
مصادر راجعتها
- OpenTelemetry Collector Configuration: يوضح بنية receivers وprocessors وexporters وservice pipelines.
- OpenTelemetry Collector Processors: يذكر tail sampling وmemory limiter كـ processors مدعومة.
- Tail Sampling Processor README: يوضح policies مثل latency وstatus_code وprobabilistic، والتنبيه الخاص بتجميع spans لنفس trace.
- OpenTelemetry tail sampling sample configuration: مثال رسمي حديث لفكرة الاحتفاظ بالأخطاء والطلبات البطيئة حسب criticality.
الخطوة التالية
الخطوة التالية: شغّل الإعداد على staging لمدة ساعة، وقارن عدد traces المصدّرة قبل وبعد. لو الأخطاء والطلبات الأبطأ من 2 ثانية ظاهرة بوضوح، انقل نفس السياسة للإنتاج مع مراقبة ذاكرة الـ Collector.