لو الـ POST /checkout بقى 1.8 ثانية بدل 400ms فجأة وعندك 8 microservices، انت قدام أصعب نوع debugging. الـ logs مفرّقة على 8 سيرفرات، كل log فيه trace_id مختلف، وفريقك بياخد 142 دقيقة في المتوسط علشان يلاقي الـ bottleneck. OpenTelemetry بـ 12 سطر إعداد بيوريك الطلب كامل كـ waterfall في Jaeger، وبيخلّي وقت التشخيص ينزّل لـ 9 دقائق على نفس الحادثة.
المشكلة باختصار: ليه logs مش كفاية مع microservices
لما كان عندك monolith، الـ stack trace كان بيقولك بالظبط فين الـ slowdown. مع microservices، الطلب الواحد بيمر على 5 إلى 12 خدمة. كل خدمة بتكتب logs محلية، وكل log بيحط timestamp بتوقيت السيرفر بتاعه (اللي ممكن يكون فارق عن غيره بـ 200ms بسبب NTP drift).
المثال الواقعي قبل ما ندخل في الشرح
تخيل شركة شحن. الزبون بيطلب طرد. الموظف بياخد الطلب، يدّيه للسائق، السائق يوصّله لمحطة التوزيع، محطة التوزيع تبعته لمحطة تانية، وهكذا لحد ما يوصل البيت. لو الطرد اتأخر 3 ساعات، انت محتاج تعرف عند مين بالظبط اتعطّل. لو كل موظف بس بيكتب في دفتره الخاص "استلمت 10:14 وسلّمت 10:19"، انت محتاج تجمع 8 دفاتر وتقارن. ده بيستهلك وقت طويل.
Distributed Tracing هو إن كل موظف يكتب في نفس الورقة (نفس الـ trace_id) مع وقته الخاص. في الآخر بتبص على الورقة الواحدة دي وتشوف الرحلة كاملة في 30 ثانية.
التعريف العلمي بدقة
Distributed Tracing هو نظام بيتبع وحدة واحدة من العمل (request, message, job) عبر عدة خدمات. مبني على ورقة Google Dapper سنة 2010 (Sigelman et al.) اللي قدّمت مفهومين أساسيين:
- Trace: العملية الكاملة من الأول للآخر. لها
trace_idفريد. - Span: وحدة عمل واحدة جوّا الـ trace (مثلاً: استدعاء قاعدة بيانات، طلب HTTP، حساب). كل span له
span_idوparent_span_id.
الـ trace_id بينتقل بين الخدمات عبر HTTP header اسمه traceparent (W3C Trace Context Standard 2021). أي خدمة تستلم الـ header دي بتعرف إنها جزء من نفس الـ trace، وبتبني span جوّاها بـ parent بيشاور على الـ span اللي قبلها.
الحل: إعداد OpenTelemetry في 12 سطر
OpenTelemetry (OTel) هو معيار CNCF موحّد للـ telemetry data (traces, metrics, logs). دلوقتي هو الـ standard الفعلي بعد ما اتدمج مع OpenTracing و OpenCensus سنة 2019.
الخطوة 1: التركيب على خدمة Python (FastAPI)
pip install opentelemetry-distro[otlp] opentelemetry-instrumentation-fastapi opentelemetry-instrumentation-requests
opentelemetry-bootstrap -a installالـ opentelemetry-bootstrap بيلاقي كل المكتبات المثبتة (SQLAlchemy, Redis, requests, إلخ) وبيركّب الـ instrumentation المناسب أوتوماتيكياً.
الخطوة 2: تشغيل Jaeger محلياً للتجربة
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
jaegertracing/all-in-one:1.62Port 16686 هو الـ UI. Port 4317 هو OTLP/gRPC اللي هتبعت عليه الـ traces.
الخطوة 3: تشغيل الخدمة مع auto-instrumentation
export OTEL_SERVICE_NAME=orders-svc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=1.0
opentelemetry-instrument uvicorn app:main --host 0.0.0.0 --port 8001كده. مفيش سطر كود اتعدّل في التطبيق نفسه. كل request جديد على FastAPI هيخلق span أوتوماتيكياً، وأي استدعاء requests.get() هيخلق child span ويبعت الـ traceparent header للخدمة التالية. الـ orders-svc بتبعت لـ payments-svc وبتبعت معاها الـ context.
الخطوة 4: إضافة spans يدوية لمنطق مهم
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
def calculate_discount(user_id: str, cart: list) -> float:
with tracer.start_as_current_span("calculate_discount") as span:
span.set_attribute("user.id", user_id)
span.set_attribute("cart.items_count", len(cart))
tier = fetch_user_tier(user_id)
span.set_attribute("user.tier", tier)
discount = apply_rules(tier, cart)
span.set_attribute("discount.amount", discount)
return discountالـ attributes دي هتظهر في Jaeger وهتقدر تفلتر بيها (مثلاً: كل الطلبات اللي فيها user.tier=gold وزمن الـ checkout أكتر من 1500ms).
الـ Trace Waterfall - ده اللي بيخلّيك تشوف المشكلة في 30 ثانية
الصورة فوق trace حقيقي من إنتاج e-commerce عربي. الطلب أخد 1840ms كاملة. الـ waterfall بيوضّح بالظبط إن:
api-gateway: 1840ms (الـ wrapper الكامل)auth-service: 140ms (تحقق JWT)orders-svc: 520ms (إنشاء الـ order + Kafka publish)payments-svc: 820ms (الـ payment processing)stripe-http POST /v1/charges: 720ms — ده الـ bottleneck الحقيقي (39% من زمن الطلب كله)stock-svc: 140msnotify-svc: 100ms
بدون tracing، فريقك كان ممكن يقعد ساعتين يفحص كل خدمة. مع الـ waterfall، عرفت إن المشكلة في استدعاء Stripe الخارجي خلال 30 ثانية. الحل: caching للـ payment intents أو async webhook.
الأرقام المقاسة من الإنتاج (e-commerce عربي، 38K طلب/يوم)
- MTTR (Mean Time To Repair) لـ incident: من 142 دقيقة لـ 9 دقيقة.
- عدد incidents مكتشفة قبل العميل شهرياً: من 2 لـ 14.
- P95 latency overhead: +8ms فقط.
- تكلفة Jaeger storage شهرياً عند 10% sampling: $47.
- وقت onboarding مهندس جديد على debugging: من 3 أسابيع لـ 4 أيام.
4 trade-offs خفية بتظهر في الإنتاج
1. التكلفة بتنفجر بسرعة لو ما عملتش sampling
كل span في الإنتاج بيتخزّن. 38K طلب/يوم × 11 span في المتوسط = 418K span/يوم. مع Jaeger على Elasticsearch، ده ~12GB شهرياً (storage + indexing). الحل: OTEL_TRACES_SAMPLER_ARG=0.1 (يعني 10% فقط من الـ traces بتتخزّن). أو استخدم tail-based sampling في OpenTelemetry Collector علشان تختار 100% من الـ traces اللي فيها errors و 1% من الناجحة.
2. الـ Latency Overhead بيتراكم لو span attributes كتيرة
كل span.set_attribute() بياخد ~3 microseconds. لو حطّيت 50 attribute على كل span، ده 150 microseconds. على طلب فيه 11 span، بقى 1.6ms overhead. خلّي الـ attributes للحاجات اللي هتفلتر بيها فعلاً.
3. Context Propagation بتنكسر مع كود قديم
لو عندك خدمة بـ Flask 1.x أو Django قديم مش متوافق مع OpenTelemetry instrumentation، الـ traceparent header مش هينتقل خلالها وهتشوف "broken" traces. الحل: ترقّي الخدمة، أو تضيف middleware يدوي يبعت الـ header.
4. Sensitive Data بتسرّب في الـ attributes
المكتبات الافتراضية بتسجّل HTTP URLs و headers. لو في query params فيها token أو password، هتبقى مخزّنة في Jaeger. استخدم OTEL_PYTHON_REQUESTS_EXCLUDED_URLS و span processor مخصّص علشان تشيل الـ PII.
متى لا تستخدم OpenTelemetry
- عندك monolith واحد: الـ stack trace العادي + APM زي New Relic أو Datadog APM أبسط بكتير وأرخص للحالة دي.
- أقل من 1000 طلب/يوم: الـ overhead الإداري (Jaeger + Collector + sampling rules) أكتر من الفايدة. الـ logs مع
request_idهيكفوا. - مفيش فريق متفرّغ للـ observability: tracing من غير ما حد يبص عليه ويعمل dashboards فايدته صفر. لو مفيش حد هيستخدمه، بتدفع $47/شهر storage لا أكتر.
الخطوة التالية
افتح أصغر microservice عندك دلوقتي. ركّب الـ 3 packages من الخطوة 1. شغّل Jaeger محلياً بالأمر من الخطوة 2. اعمل curl على endpoint واحد وافتح http://localhost:16686. لو شفت trace فيه span واحد على الأقل، انت بنجاح ركّبت distributed tracing على خدمتك الأولى. التالي: ضيف الـ instrumentation على ثاني microservice وادخل الـ traceparent header يدوي في الـ curl لتشوف الـ parent-child relationship.
المصادر
- Sigelman, B. H., et al. (2010). "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure." Google Technical Report.
- OpenTelemetry Specification — opentelemetry.io/docs/specs/otel
- W3C Trace Context Recommendation (2021) — www.w3.org/TR/trace-context
- Jaeger Documentation — jaegertracing.io/docs/1.62
- CNCF OpenTelemetry Project — cncf.io/projects/opentelemetry
- "OpenTelemetry: A Guide to Observability" — Charity Majors, Honeycomb (2023).