تصميم أنظمة الخدمات الخلفية

أنماط التوسع والبنية التحتية للبيانات

5 دقيقة للقراءة

كل مقابلة تصميم أنظمة تصل في النهاية إلى السؤال: "كيف يتوسع هذا لـ 10 أضعاف أو 100 ضعف حركة المرور؟" يغطي هذا الدرس أنماط التوسع واستراتيجيات قواعد البيانات والبنية التحتية المتخصصة للبيانات التي تحتاجها للإجابة بثقة.

التوسع الأفقي مقابل الرأسي

الجانبالرأسي (توسيع لأعلى)الأفقي (توسيع للخارج)
الطريقةآلة أكبر (معالج أقوى، ذاكرة أكثر، قرص أكبر)آلات أكثر
الحدسقف العتاد (أكبر مثيل متاح)غير محدود عمليًا
التعقيدبسيط (بدون تغييرات في الكود)معقد (حالة موزعة، تنسيق)
منحنى التكلفةأسي (2x معالج ≠ 2x تكلفة)خطي (2x خوادم ≈ 2x تكلفة)
التوقفيتطلب إعادة تشغيل لتغيير الحجمبدون توقف مع موزع الأحمال
الأفضل لـقواعد البيانات (قبل التجزئة)، الخدمات الصغيرةخوادم API عديمة الحالة، النسخ المتماثلة للقراءة

قاعدة عامة للمقابلة: ابدأ رأسيًا للبساطة، ثم انتقل أفقيًا عند الوصول للحدود. الخدمات عديمة الحالة (خوادم API) تتوسع أفقيًا بسهولة. الخدمات ذات الحالة (قواعد البيانات) تحتاج تخطيطًا دقيقًا.

النسخ المتماثلة لقراءة قواعد البيانات

أبسط نمط توسع لأعباء العمل كثيفة القراءة. خادم أساسي (رئيسي) واحد يعالج كل الكتابات، ونسخ متماثلة متعددة تعالج القراءات.

                    ┌──────────────┐
                    │   الأساسي   │
     الكتابات ────▶│  (الرئيسي)  │
                    └──────┬───────┘
                           │ النسخ المتماثل
              ┌────────────┼────────────┐
              ▼            ▼            ▼
       ┌──────────┐ ┌──────────┐ ┌──────────┐
       │ نسخة 1  │ │ نسخة 2  │ │ نسخة 3  │
       └──────────┘ └──────────┘ └──────────┘
              ▲            ▲            ▲
              └────────────┼────────────┘
                  القراءات (موزعة الأحمال)

تأخر النسخ المتماثل

النسخ المتماثلة متسقة نهائيًا. التأخر النموذجي 10-100 مللي ثانية، لكن قد يرتفع تحت حمل كتابة ثقيل. تعامل مع هذا في تطبيقك:

def get_user_profile(user_id: str, just_updated: bool = False) -> dict:
    if just_updated:
        # القراءة من الأساسي لضمان رؤية آخر كتابة
        return primary_db.query("SELECT * FROM users WHERE id = %s", user_id)
    else:
        # القراءات العادية تذهب للنسخة المتماثلة
        return replica_db.query("SELECT * FROM users WHERE id = %s", user_id)

سجل الكتابة المسبقة (WAL)

WAL هو الآلية التي تستخدمها قواعد البيانات لضمان المتانة واسترداد الأعطال. فهمه يظهر معرفة عميقة بقواعد البيانات في المقابلات.

كيف يعمل WAL:

  1. قبل تعديل صفحة بيانات، تكتب قاعدة البيانات التغيير في ملف سجل تسلسلي (WAL)
  2. كتابة WAL تُفرغ إلى القرص (fsync)
  3. فقط بعد ذلك تُعدل صفحة البيانات الفعلية في الذاكرة
  4. صفحات البيانات تُفرغ إلى القرص لاحقًا على دفعات (نقاط التفتيش)

إذا تعطلت قاعدة البيانات:

  • التغييرات غير المؤكدة في الذاكرة تُفقد، لكن WAL على القرص
  • عند إعادة التشغيل، تعيد قاعدة البيانات تشغيل WAL لإعادة بناء الحالة
  • لا تُفقد أبدًا أي بيانات مؤكدة (WAL مُفرغ)

استخدامات WAL في PostgreSQL: استرداد الأعطال، النسخ المتماثل المتدفق للنسخ، الاسترداد في نقطة زمنية (PITR)، والنسخ المتماثل المنطقي (Debezium CDC).

استراتيجيات التجزئة (Sharding)

عندما لا يستطيع خادم قاعدة بيانات واحد التعامل مع حجم الكتابة أو حجم البيانات، تقسم البيانات عبر خوادم متعددة (أجزاء). هذا أحد أعقد القرارات في تصميم الأنظمة.

مقارنة الاستراتيجيات

الاستراتيجيةكيف تعملالمزاياالعيوب
قائمة على النطاقتجزئة بنطاق المفتاح (مثلاً A-M على الجزء 1، N-Z على الجزء 2)استعلامات نطاق بسيطة، سهلة الفهمأقسام ساخنة (بعض النطاقات أكثر نشاطًا)
قائمة على التجزئةshard = hash(key) % num_shardsتوزيع متساوٍاستعلامات النطاق تتعطل (يجب التشتت والتجميع)
قائمة على الدليلجدول بحث يربط كل مفتاح بالجزء الخاص بهإعادة تعيين مرنةالبحث يضيف زمنًا، الدليل نقطة فشل واحدة

مثال التجزئة القائمة على Hash

import hashlib

NUM_SHARDS = 8

def get_shard(user_id: str) -> int:
    """تحديد أي جزء يحتوي على بيانات هذا المستخدم."""
    hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
    return hash_value % NUM_SHARDS

def query_user(user_id: str) -> dict:
    shard_id = get_shard(user_id)
    connection = shard_connections[shard_id]
    return connection.query("SELECT * FROM users WHERE id = %s", user_id)

اكتشاف الأقسام الساخنة والتخفيف

بعض المفاتيح تتلقى حركة مرور غير متناسبة (ملف شخصية مشهور، منشور فيروسي). استراتيجيات التخفيف:

  1. التجزئة المتسقة مع العقد الافتراضية — توزع الحمل بشكل أكثر تساويًا عبر الخوادم الفعلية
  2. تمليح المفاتيح الساخنة — إضافة لاحقة عشوائية (celebrity_123_0، celebrity_123_1، ... celebrity_123_9) والتشتيت عبر 10 أجزاء، ثم تجميع القراءات
  3. جزء مخصص للبيانات الساخنة — نقل المفاتيح الساخنة المكتشفة إلى جزء معزول وأقوى
  4. طبقة تخزين مؤقت — تخزين المفاتيح الساخنة في Redis حتى لا تصل لقاعدة البيانات أبدًا

تقسيم البيانات متعدد المستأجرين

تطبيقات SaaS تخدم مستأجرين (عملاء) متعددين. استراتيجية العزل تعتمد على الحجم والامتثال والتكلفة:

الاستراتيجيةالوصفالعزلالتكلفةالأفضل لـ
DB مشتركة، مخطط مشترككل المستأجرين في نفس الجداول، عمود tenant_idالأقلالأرخصمستأجرون صغار، شركات ناشئة
DB مشتركة، مخططات منفصلةكل مستأجر يحصل على مخططه الخاص في DB واحدةمتوسطمنخفضمستأجرون متوسطون، بيانات معتدلة
مخطط مخصص لكل مستأجرمخطط منفصل، خادم مشتركجيدمتوسطالصناعات المنظمة
DB مخصصة لكل مستأجركل مستأجر يحصل على خادم قاعدة بيانات خاصالأعلىالأغلىالمؤسسات، الامتثال الصارم

⚠ Prices change frequently. The values above are for illustration only and may be out of date. Always verify current pricing directly with the provider before making cost decisions: Anthropic · OpenAI · Google Gemini · Google Vertex AI · AWS Bedrock · Azure OpenAI · Mistral · Cohere · Together AI · DeepSeek · Groq · Cursor · GitHub Copilot · Windsurf.

-- نهج المخطط المشترك: كل استعلام يتضمن tenant_id
-- استخدم Row Level Security (RLS) في PostgreSQL للتطبيق

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON orders
    USING (tenant_id = current_setting('app.current_tenant')::uuid);

-- التطبيق يعيّن سياق المستأجر لكل طلب
SET app.current_tenant = 'tenant-abc-123';
SELECT * FROM orders;  -- يرى فقط طلبات tenant-abc-123

تخزين الكائنات الثنائية الكبيرة

الكائنات الثنائية الكبيرة (الصور، الفيديوهات، المستندات) يجب ألا تُخزن أبدًا في قاعدة البيانات العلائقية. استخدم تخزين الكائنات.

الخدمةالمزودالميزات الرئيسية
S3AWSمتانة 11 تسعات، قواعد دورة الحياة، إشعارات الأحداث
Cloud StorageGCPطبقات Nearline/Coldline، اتساق قوي
Azure BlobAzureطبقات Hot/Cool/Archive، تخزين غير قابل للتغيير

نمط الروابط المُوقّعة مسبقًا

import boto3

def generate_upload_url(file_key: str, content_type: str) -> str:
    """إنشاء رابط مُوقّع مسبقًا لرفع مباشر من العميل إلى S3."""
    s3 = boto3.client("s3")
    url = s3.generate_presigned_url(
        "put_object",
        Params={
            "Bucket": "my-app-uploads",
            "Key": file_key,
            "ContentType": content_type,
        },
        ExpiresIn=300,  # 5 دقائق
    )
    return url

# التدفق:
# 1. العميل يطلب رابط رفع من خادم API
# 2. خادم API يُنشئ رابطًا مُوقّعًا مسبقًا ويرجعه
# 3. العميل يرفع مباشرة إلى S3 (يتجاوز خادم API)
# 4. S3 يُطلق إشعار حدث ← معالجة/صورة مصغرة/CDN

هذا النمط يبعد الملفات الكبيرة عن خوادم API، مما يمنع اختناقات عرض النطاق وضغط الذاكرة.

البنية التحتية للبحث

عندما يكون SQL LIKE '%search_term%' بطيئًا جدًا (وهو دائمًا بطيء على نطاق واسع لأنه لا يستطيع استخدام الفهارس)، تحتاج محرك بحث مخصصًا.

المفاهيم الأساسية لـ Elasticsearch

المفهومالوصف
الفهرس المقلوبيربط كل كلمة فريدة بقائمة المستندات التي تحتويها — بحث O(1)
التعيين (Mapping)تعريف المخطط: أي الحقول text (قابلة للبحث الكامل) مقابل keyword (مطابقة تامة)
المُحلل (Analyzer)مُرمّز + مرشحات: "Running quickly" ← ["run", "quick"] (تجذير + تصغير)
تسجيل الصلةTF-IDF أو BM25: المستندات ذات المصطلحات النادرة المطابقة تحصل على درجة أعلى
شبه فوريالمستندات قابلة للبحث خلال ~1 ثانية من الفهرسة (فترة التحديث)

متى تستخدم Elasticsearch مقابل SQL

استخدم Elasticsearch عندماابقَ مع SQL عندما
بحث نصي كامل عبر حقول متعددةبحث دقيق بالمفتاح الأساسي
مطابقة ضبابية، مرادفات، تجذيراستعلامات WHERE column = value بسيطة
بحث مُصنّف (تصفية حسب الفئة + السعر + التقييم)البيانات تتسع في الذاكرة، أقل من مليون سجل
اقتراحات الإكمال التلقائيالاتساق القوي مطلوب
تجميع وتحليل السجلاتلا ميزانية لبنية تحتية إضافية

بيانات السلاسل الزمنية

المقاييس، بيانات أجهزة IoT، سجلات التطبيقات، والتداول المالي كلها تشترك في النمط: كثيفة الكتابة، إلحاق فقط، يُستعلم عنها بنطاق زمني.

قاعدة البياناتالنوعالميزة الرئيسية
InfluxDBمصممة خصيصًا للسلاسل الزمنيةلغة استعلام InfluxQL/Flux، خفض العينات مدمج
TimescaleDBامتداد PostgreSQLدعم SQL كامل، جداول فائقة تُقسّم تلقائيًا بالوقت
ClickHouseتحليلات عمودية التوجهاستعلامات تجميع سريعة للغاية

الاحتفاظ وخفض العينات

البيانات الخام بدقة ثانية واحدة تستهلك تخزينًا هائلًا. استخدم احتفاظًا متدرجًا:

البيانات الخام (دقة 1 ثانية):      احتفظ 7 أيام
تجميعات 5 دقائق:                   احتفظ 30 يومًا
تجميعات ساعة:                      احتفظ سنة واحدة
تجميعات يوم:                       احتفظ للأبد
-- TimescaleDB: إنشاء تجميع مستمر لملخصات 5 دقائق
CREATE MATERIALIZED VIEW metrics_5min
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('5 minutes', timestamp) AS bucket,
    sensor_id,
    AVG(value) AS avg_value,
    MAX(value) AS max_value,
    MIN(value) AS min_value,
    COUNT(*) AS sample_count
FROM raw_metrics
GROUP BY bucket, sensor_id;

-- إضافة سياسة احتفاظ لحذف البيانات الخام بعد 7 أيام
SELECT add_retention_policy('raw_metrics', INTERVAL '7 days');

تصميم خط أنابيب ETL

خطوط أنابيب ETL (استخراج، تحويل، تحميل) تنقل البيانات من قواعد البيانات التشغيلية إلى مستودعات البيانات للتحليلات.

┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  قاعدة      │───▶│  استخراج    │───▶│   تحويل     │───▶│   تحميل     │
│ البيانات    │    │  (Debezium   │    │  (Spark /    │    │(BigQuery /   │
│ (PostgreSQL) │    │   CDC)       │    │   dbt)       │    │ Snowflake /  │
└──────────────┘    └──────────────┘    └──────────────┘    │ Redshift)    │
                                                            └──────────────┘

CDC (التقاط تغييرات البيانات) مع Debezium

بدلاً من التصديرات الدورية على دفعات (التي تفقد البيانات بين التشغيلات وتُرهق قاعدة البيانات المصدر)، يلتقط CDC كل INSERT و UPDATE و DELETE فور حدوثها بقراءة WAL لقاعدة البيانات.

WAL قاعدة البيانات ← موصّل Debezium ← موضوع Kafka ← المستهلك ← مستودع البيانات

الفوائد:
- شبه فوري (ثوانٍ، وليس ساعات)
- بدون حمل على قاعدة البيانات المصدر (يقرأ WAL، وليس الجداول)
- يلتقط الحذف (التصدير الدوري يفقدها)
- يحافظ على الترتيب الدقيق للتغييرات

طبقة التحويل

الأداةالنوعالأفضل لـ
dbtتحويلات SQL أولاًالمحللون الذين يعرفون SQL، تحويلات تصريحية
Apache Sparkحوسبة موزعةبيانات واسعة النطاق (TB+)، تحويلات معقدة
Apache Flinkمعالجة تدفقيةتحويلات فورية، تجميعات نافذية

نصيحة للمقابلة: عند السؤال عن خطوط أنابيب البيانات، اذكر CDC بدلاً من ETL الدوري لإظهار تفكير عصري. أبرز المقايضة: CDC أكثر تعقيدًا في الإعداد لكنه يوفر بيانات أحدث وحملاً أقل على قاعدة البيانات المصدر.

لقد أكملت جميع الدروس الثلاثة في هذه الوحدة. خذ الاختبار لاختبار فهمك لأُطر تصميم الأنظمة والمسائل الكلاسيكية وأنماط التوسع. :::

اختبار

اختبار الوحدة 4: تصميم أنظمة الخدمات الخلفية

خذ الاختبار
نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.