انتقل للمحتوى
ObservabilityBackendReliabilityArchitectureLeadership

ليه الـ observability بتيجي قبل الـ optimization

6 يوليو 2026مدة القراءة: 8 دقايقعبدالرحمن سعيد

من غير observability، قرارات تحسين الأداء بتعتمد على التخمين. قبل ما تزوّدوا workers، أو تعيدوا كتابة services، أو تغيّروا قاعدة البيانات، لازم تعرفوا الوقت والأخطاء وضغط الـ queue والتكلفة جايين منين فعلًا.

كتير بنبدأ نراجع الأداء بعد ما المشكلة توصل للمستخدم. الـ API يبقى بطيء، أو المهام تتراكم في queue، أو الـ CPU عالي، أو مستخدم بيشتكي. وأول رد فعل تلقائي بيكون الـ optimization: نزوّد workers، أو نعمل cache لحاجة، أو نعيد كتابة hot path، أو ننقل شغل لـ Go، أو نعمل scale لقاعدة البيانات. أحيانًا ده بينفع. لكن في أغلب الأحيان بينقل الـ bottleneck لمكان تاني، عشان محدش قاس النظام قبل ما يغيّره.

في شغلي على أنظمة الـ production، برجع لنفس النقطة: لازم نفهم اللي بيحصل قبل ما نحسّن الأداء. من غير قياسات واضحة، ممكن نصرف وقت في تعديل جزء مش هو سبب المشكلة أصلًا.

لو مش عارفين تشرحوا الـ latency والأخطاء وضغط الـ queue والتكلفة جايين منين، يبقى لسه مش وقت الـ optimization. ده وقت القياس.

فخ الـ optimization

الخطير في الـ optimization بتاع الـ backend إن كل حل لوحده بيبان منطقي. نضيف caching بالـ Redis. نزوّد الـ concurrency في Celery. نضيف indexes. نقسم service. ننقل الشغل اللي بيحتاج CPU كتير لـ Go. نستخدم Elasticsearch. نضيف read replicas. كل حل من دول ممكن يفيد، لو بيعالج سبب البطء الفعلي.

لو قاعدة البيانات بطيئة عشان فيه indexes ناقصة، زيادة الـ workers في Celery بتزوّد الضغط على قاعدة البيانات. لو الـ queue متكدّسة عشان API خارجي بيعمل timeout، زيادة الـ workers ممكن تخلّينا نوصل للـ rate limits أسرع. ولو الـ p95 latency عالية بسبب permission check واحد مكلّف، إعادة كتابة الـ endpoint كله مجهود ضايع.

اللي لازم الـ observability تجاوب عليه

الـ backend اللي شغال في الـ production مش محتاج كل لوحات التحكم الممكنة من أول يوم. محتاج قياسات تجاوب على الأسئلة اللي هنختار الحل على أساسها.

  • الـ latency: وقت الـ request بيروح فين فعلًا — كود التطبيق، قاعدة البيانات، الـ cache، الشبكة، API خارجي، الـ serialization؟
  • الأخطاء: أنهي failures بتوصل للمستخدم، أو ينفع نعمل لها retry، أو جاية من برّه، أو ليها علاقة بالـ validation، أو سببها الكود بتاعنا إحنا؟
  • ضغط الـ queue: الـ tasks مستنية عشان الـ workers واصلين لأقصى طاقتهم، ولا عشان الـ tasks نفسها بطيئة، ولا عشان الـ retries بتزيد بسرعة، ولا عشان الـ downstream services بتفشل؟
  • الـ throughput: النظام يقدر يعالج كام request أو job أو record أو event قبل ما أداؤه يبدأ يقل؟
  • التكلفة: أنهي workload هو المسؤول عن تكلفة الـ compute، أو قاعدة البيانات، أو الـ cache، أو التخزين، أو الـ APIs بتاعة طرف تالت؟

نبدأ بأربعة مؤشرات

في أغلب أنظمة الـ backend، عايز أشوف أربعة مؤشرات قبل ما أثق في أي خطة optimization: الـ latency بتاعة الـ requests، ونسبة الأخطاء، وطول الـ queue، والـ saturation. دي حاجات بسيطة، بس مع بعض بتوضّح إذا كان النظام بطيء، ولا فيه عطل، ولا عليه حمل أكبر من طاقته، ولا مستني حاجة تانية.

text
Minimum useful production view

HTTP:
  - request count by endpoint
  - p50 / p95 / p99 latency by endpoint
  - 4xx / 5xx error rate

Database:
  - slow queries
  - connection pool usage
  - lock wait time

Queue:
  - queue depth
  - task runtime
  - retry count
  - failure reason

Infrastructure:
  - CPU / memory
  - container restarts
  - external API latency

مثال: ضغط الـ queue في Celery

تخيّلوا queue في Celery بدأت تتأخر كل ما حجم البيانات الواردة يزيد فجأة. أول حل ممكن نفكر فيه إننا نزوّد الـ concurrency بتاعة الـ workers. ده ممكن يكون صح، بس بشرط إن الـ workers يكونوا فعلًا CPU-bound أو مستنيين slots فاضية. لكن لو الـ tasks بطيئة عشان كل job بيعمل تلات عمليات write على قاعدة البيانات واستدعاء لـ API خارجي، زيادة الـ workers ممكن تخلّي النظام أسوأ.

بدل ما نبدأ بسؤال «إزاي نخلّي Celery أسرع؟»، نسأل «وقت الـ task بيروح فين؟». الإجابة بتحدد هنبدأ الـ debugging منين.

python
# Useful task instrumentation pattern
import time
import logging

logger = logging.getLogger(__name__)

def timed_step(name, fn):
    start = time.perf_counter()
    try:
        return fn()
    finally:
        duration_ms = round((time.perf_counter() - start) * 1000, 2)
        logger.info("task_step_completed", extra={
            "step": name,
            "duration_ms": duration_ms,
        })

def process_event(event):
    validated = timed_step("validate", lambda: validate_event(event))
    enriched = timed_step("enrich", lambda: enrich_event(validated))
    saved = timed_step("store", lambda: store_event(enriched))
    timed_step("notify", lambda: maybe_notify(saved))

أول ما البيانات دي تبقى عندكم، ممكن الـ optimization يطلع مختلف جدًا عن أول تخمين. يمكن الـ validation مش مكلّف، والـ enrichment بطيء، والتخزين تمام، والإشعارات بتعمل timeout. في الحالة دي، الحل مش workers أكتر. ممكن يكون timeout، أو circuit breaker، أو queue منفصلة، أو إننا ننقل شغل الإشعارات لـ pipeline أولويته أقل.

القياس بيوضّح المفاضلات

جزء كبير من شغل الـ Staff Engineer هو توضيح المفاضلات ومساعدة الفريق يختار. نضيف caching بالـ Redis ولا نصلّح الـ query؟ نزوّد workers ولا نقلّل وقت تنفيذ الـ task؟ نقسم service ولا نفضل على الـ monolith؟ ننقل hot path لـ Go ولا نحسّن كود الـ Python؟

القياس بيدّي الفريق أساس يقارن بيه الحلول دي، بدل ما الاختيار يعتمد على التفضيلات الشخصية.

  • لو أغلب الـ p95 latency جاية من query واحدة على قاعدة البيانات، يبقى نصلّح الـ query قبل ما نضيف infrastructure.
  • لو الـ p99 latency جاية من APIs خارجية، يبقى نضيف timeouts، و retries، و fallbacks، وحالات بتظهر للمستخدم.
  • لو طول الـ queue بيزيد بس استهلاك الـ CPU بتاع الـ workers قليل، يبقى نحقّق في الانتظار على الـ downstream وفي الـ retry storms.
  • لو الـ CPU واصل لأقصى طاقته والـ algorithm هو اللي واخد أغلب وقت التنفيذ، ساعتها تغيير اللغة أو الـ runtime ممكن يبقى له مبرر.

الترتيب اللي بفضّله

لما النظام يبقى تحت ضغط، ده الترتيب اللي عادةً بكون عايز الفريق يمشي عليه.

  • نقيس المسار: الـ request، وقاعدة البيانات، والـ cache، والـ queue، والـ dependency الخارجية.
  • نحدد الـ bottleneck بالأدلة، مش بالإحساس.
  • نطبّق أصغر حل يعالج الـ bottleneck.
  • نقيس تاني بعد التغيير.
  • ساعتها بس نفكر في تغييرات أكبر في الـ architecture.

الـ optimization مش بيخلص لما الكود يتغيّر. بيخلص لما القياسات تتحسّن والفريق يفهم ليه.

الـ observability كمان أداة قيادة

الـ observability الكويسة بتغيّر سلوك الفريق. بتقلّل اللوم وقت الـ incidents، عشان الفريق يقدر يبص على الأدلة. بتساعد فرق المنتج تفهم المفاضلات، عشان المهندسين يقدروا يشرحوا تكلفة أي ميزة. وبتساعد المهندسين الـ junior يتعلموا التفكير بعقلية الـ production، عشان يقدروا يشوفوا الكود بيتصرف إزاي برّه اللابتوب بتاعهم.

عشان كده أنا شايف إن الـ observability مكانها في نقاشات الـ architecture، مش في مهام الـ DevOps بس. الـ logs والـ metrics والـ traces ولوحات التحكم والتنبيهات والـ runbooks جزء من نموذج تشغيل المنتج. لو الفريق مش قادر يشغّل النظام، يبقى الـ architecture ناقصة.

اللي هضيفه من بدري

في أي نظام backend، هجهّز القياسات دي بدري باستخدام الأدوات المناسبة للـ stack، حتى قبل ما يزيد حجم الاستخدام.

  • الـ structured logs، وفيها الـ request IDs والـ task IDs.
  • الـ metrics بتاعة الـ latency والأخطاء على مستوى كل endpoint.
  • رؤية للـ slow queries، والـ metrics بتاعة الـ connection pool في قاعدة البيانات.
  • طول الـ queue في Celery، ووقت تنفيذ الـ tasks، والـ retries، وأسباب الفشل.
  • الـ latency بتاعة الـ APIs الخارجية، وعدد الـ timeouts، وعدد الـ retries.
  • قالب مختصر لتوثيق الـ incident: التأثير، والسبب الجذري، والإصلاح، والإجراء الوقائي.

الهدف الحقيقي

القياسات لازم تساعد الفريق يفهم النظام. لما حاجة تبطّأ أو تفشل أو تكلفتها تزيد، المفروض نعرف نراجع أنهي بيانات وإيه السؤال اللي محتاجين نجاوب عليه.

تجهيز الـ observability من بدري بيخلّي تحسين الأداء مبني على بيانات. ووقت الأعطال، البيانات دي بتساعدنا نحدد السبب ونختار تعديل نقدر نقيس أثره، من غير تعقيد ملوش لازمة.

كتبه

عبدالرحمن سعيد

قائد فريق برمجيات | مهندس برمجيات أول متخصص في الـ Backend