0Pricing
Coding Interview Prep · درس

إطار مقابلة تصميم الأنظمة

تعرّف إلى إطار RADIO ذي الخطوات الخمس (Requirements وAPI وData وInfrastructure وOptimise)، وتدرّب على تطبيقه على أداة اختصار عناوين URL.

إطار مقابلة تصميم الأنظمة درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

لماذا يهم تصميم الأنظمة في المقابلات

تختبر مقابلات تصميم الأنظمة قدرتك على التفكير على نطاق واسع — كيف ستصمم Twitter أو YouTube أو خدمة لاختصار عناوين URL لمليارات المستخدمين؟ بخلاف مسائل البرمجة التي لها إجابة صحيحة واحدة، فإن تصميم الأنظمة مفتوح النهاية؛ إذ يجب أن تجري المفاضلات وتبرّرها. وتخصّص الأدوار العليا وأدوار staff مستوىً يتراوح بين 30 و45 دقيقة لهذه الجولة وحدها.

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

# System design is not about a single correct answer.
# Interviewers look for:
eval_criteria = [
    'Ability to clarify requirements before designing',
    'Back-of-envelope capacity estimation',
    'High-level architecture with clear components',
    'Data modelling and storage choice',
    'Handling scalability (10x, 100x load)',
    'Trade-off discussion (consistency vs availability, etc.)',
    'Communication: talking through decisions as you make them',
]
for c in eval_criteria:
    print('-', c)

نظرة عامة على إطار RADIO

يوفّر إطار RADIO بنية قابلة للتكرار من خمس خطوات لأي مقابلة لتصميم الأنظمة:

  • R — المتطلبات: الوظيفية وغير الوظيفية
  • A — تصميم API: ما العمليات التي يوفّرها النظام؟
  • D — نموذج البيانات: ما البيانات التي تُخزَّن، وكيف؟
  • I — البنية التحتية: المكوّنات عالية المستوى، مثل الخوادم وقوائم الانتظار وذاكرات التخزين المؤقت
  • O — التحسين: الاختناقات، والتخزين المؤقت، والتقسيم، والنسخ المتماثل

انتقل دائمًا بين هذه الخطوات بالترتيب، لكن عُد إلى الخطوات السابقة ونقّحها عندما تظهر رؤى جديدة. خصّص وقتًا متقاربًا لكل مرحلة. لا تبدأ مطلقًا برسم الصناديق مباشرةً قبل توضيح المتطلبات.

RADIO = {
    'R': 'Requirements — What must the system do? What scale?',
    'A': 'API         — Define endpoints/operations the system exposes',
    'D': 'Data Model  — Entities, schemas, storage types',
    'I': 'Infra       — High-level architecture: servers, queues, caches, CDN',
    'O': 'Optimise    — Identify and address bottlenecks, trade-offs',
}
for step, desc in RADIO.items():
    print(f'[{step}] {desc}')

print('\nTiming guide for a 45-min interview:')
print('  R: 5 min | A: 5 min | D: 10 min | I: 15 min | O: 10 min')

الخطوة R: توضيح المتطلبات

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

  • وظيفية: اختصار عنوان URL، وإعادة التوجيه إلى عنوان URL الأصلي، مع دعم اختياري للأسماء المستعارة المخصّصة وانتهاء الصلاحية
  • غير وظيفية: كم عدد عناوين URL يوميًا؟ هل يغلب على النظام القراءة أم الكتابة؟ ما متطلب التوافر، 99.9% أم 99.99%؟ وما الزمن المستغرق المقبول؟

يوضح التصريح بالافتراضات صراحةً نضجك. وغالبًا ما يقدّم المحاورون مواصفات غامضة عمدًا ليروا ما إذا كنت تطرح الأسئلة المناسبة. إن تخصيص دقيقتين للتوضيح يجنبك تصميم النظام الخطأ.

# Requirements questions for URL Shortener:
functional = [
    'Shorten a given URL to a 7-character alias',
    'Redirect short URL to original URL',
    'Allow custom aliases (optional)',
    'URL expiry (optional)',
    'Analytics: click count per URL (optional)',
]
non_functional = [
    '100 million new URLs per day (write: ~1160/sec)',
    '10:1 read:write ratio => 11,600 redirects/sec',
    'Redirects must be < 100ms p99 latency',
    '99.99% availability (< 1 hr downtime/year)',
    'URLs must be globally accessible',
]
print('Functional:')
for f in functional: print(' -', f)
print('\nNon-functional:')
for nf in non_functional: print(' -', nf)

الخطوة R: التقدير التقريبي

بعد تحديد المتطلبات، قدّر السعة. يوضح ذلك قدرتك على التفكير في النطاق قبل اقتراح الحلول. ومن الأرقام الأساسية التي ينبغي اشتقاقها: الطلبات في الثانية (RPS)، ومساحة التخزين المطلوبة يوميًا أو سنويًا، وعرض النطاق الترددي، والذاكرة اللازمة للتخزين المؤقت.

استخدم أرقامًا تقريبية وقرّب بحرية. يهتم المحاورون برتبة المقدار، لا بالأرقام الدقيقة. مثال لخدمة اختصار عناوين URL: ‏100M عملية كتابة/يوم ÷ 86400 ≈ 1160 عملية كتابة/ثانية. ‏10B عملية قراءة/يوم ÷ 86400 ≈ 115K عملية قراءة/ثانية. حجم كل سجل URL نحو 500 بايت: ‏100M × 500B = 50 GB/يوم، و18 TB/سنة.

# Back-of-envelope for URL Shortener
writes_per_day = 100_000_000        # 100 million URLs/day
read_write_ratio = 100              # 100:1 read/write
reads_per_day = writes_per_day * read_write_ratio
bytes_per_url = 500                 # url string + metadata
years_to_store = 5

print('=== Capacity Estimation ===')
print(f'Writes/sec:  {writes_per_day / 86400:.0f}')
print(f'Reads/sec:   {reads_per_day / 86400:,.0f}')
print(f'Storage/day: {writes_per_day * bytes_per_url / 1e9:.1f} GB')
print(f'Storage total ({years_to_store}y): {writes_per_day * bytes_per_url * 365 * years_to_store / 1e12:.1f} TB')

cache_hit_rate = 0.80
hot_urls = reads_per_day * (1 - cache_hit_rate)
print(f'\n80% cache hit rate: {cache_hit_rate*100}% of reads from cache')
print(f'DB reads/sec: {hot_urls / 86400:,.0f}')

الخطوة A: تصميم API

حدّد نطاق API، أي العمليات التي يوفّرها النظام للعملاء والخدمات الداخلية. حدّد بوضوح طريقة HTTP، ومسار نقطة النهاية، ومعاملات الطلب، وتنسيق الاستجابة. وهذا يرسّخ بقية التصميم، إذ إن كل شيء آخر موجود لتنفيذ واجهات API هذه.

بالنسبة إلى خدمة اختصار عناوين URL، فإن واجهتي API الأساسيتين هما: (1) POST /shorten لإنشاء عنوان URL مختصر، و(2) GET /{alias} لإعادة التوجيه. وتشمل الخيارات الإضافية: DELETE /{alias} للحذف، وGET /{alias}/stats للتحليلات. حدّد رموز الاستجابة، مثل 201 Created و301 Redirect و404 Not Found.

# API Design for URL Shortener
apis = [
    {
        'method': 'POST',
        'path': '/api/v1/shorten',
        'request': '{"long_url": "https://...", "alias": "optional", "expires_at": "optional"}',
        'response': '201 Created: {"short_url": "https://short.ly/abc1234", "alias": "abc1234"}',
    },
    {
        'method': 'GET',
        'path': '/{alias}',
        'request': 'No body',
        'response': '301 Redirect to long_url (or 404 Not Found)',
    },
    {
        'method': 'GET',
        'path': '/api/v1/{alias}/stats',
        'request': 'Optional: date range query params',
        'response': '200 OK: {"clicks": 42000, "unique_visitors": 15000}',
    },
]
for api in apis:
    print(f'{api["method"]} {api["path"]}')
    print(f'  Request:  {api["request"]}')
    print(f'  Response: {api["response"]}')
    print()

الخطوة D: نموذج البيانات

يحدّد نموذج البيانات ما تخزّنه وكيفية تخزينه. حدّد الكيانات الأساسية وسماتها. بالنسبة إلى خدمة اختصار عناوين URL: جدول urls يحتوي على alias، وهو المفتاح الأساسي، وlong_url، وcreated_at، وexpires_at، وuser_id. ويمكن إضافة جدول clicks اختياري للتحليلات.

يُعد اختيار نوع التخزين المناسب أمرًا بالغ الأهمية: قاعدة بيانات علائقية للبيانات المنظّمة ذات الاستعلامات المعقدة؛ ومخزن مفتاح-قيمة، مثل Redis وDynamoDB، لعمليات البحث عن alias بزمن O(1) على نطاق واسع؛ ومخزن كائنات، مثل S3، للبيانات الثنائية الكبيرة. وبالنسبة إلى خدمة اختصار عناوين URL، يُعد مخزن مفتاح-قيمة يعتمد على alias مثاليًا للقراءات، مع قاعدة بيانات علائقية للكتابات والإدارة.

# Data model for URL Shortener

# Core table (PostgreSQL)
urls_schema = '''
CREATE TABLE urls (
    alias       VARCHAR(16) PRIMARY KEY,   -- e.g., 'abc1234'
    long_url    TEXT NOT NULL,
    user_id     UUID REFERENCES users(id),
    created_at  TIMESTAMP DEFAULT NOW(),
    expires_at  TIMESTAMP,
    click_count BIGINT DEFAULT 0
);
CREATE INDEX ON urls(user_id);
'''

# Cache layer (Redis) for hot reads
redis_schema = '''
alias  =>  long_url       # O(1) GET on cache hit
TTL = 24 hours (or until expiry)
Cache eviction: LRU
'''

print('PostgreSQL schema:')
print(urls_schema)
print('Redis cache:')
print(redis_schema)
print('Storage split: Redis for hot reads (~80%), PostgreSQL for writes and cold reads')

الخطوة I: البنية التحتية عالية المستوى

ارسم تصورًا للبنية التحتية عالية المستوى: ما الخوادم التي تتولى كل مسؤولية، وكيف تتدفق البيانات بين المكوّنات، وما الخدمات الخارجية المستخدمة. بالنسبة إلى خدمة اختصار عناوين URL على نطاق واسع:

  • موازن التحميل: يوزّع حركة المرور على نسخ خدمة الكتابة وخدمة القراءة
  • خدمة الكتابة: تنشئ alias، وتتحقق من تفرده، وتكتب في قاعدة البيانات، وتبطل صلاحية ذاكرة التخزين المؤقت
  • خدمة القراءة وإعادة التوجيه: تتحقق أولًا من ذاكرة Redis المؤقتة، ثم تعود إلى قاعدة البيانات عند فقدان القيمة المخزنة مؤقتًا
  • قاعدة البيانات العلائقية: مصدر الحقيقة، مع نسخ متماثلة للقراءة
  • عنقود Redis: يخزّن تعيينات عناوين URL كثيرة الاستخدام مؤقتًا لإتاحة قراءات بزمن أقل من ميلي ثانية
# ASCII architecture sketch
architecture = '''
         Clients
            |
       [Load Balancer]
       /             \\
  [Write API]    [Read/Redirect API]
      |                  |  |
  [Alias Gen]       [Redis Cache]
      |                  |
  [PostgreSQL]---->[Read Replicas]

Alias Generation:
- Base62 encoding of auto-incrementing ID
- 62^7 = 3.5 trillion unique URLs (enough for 5 years at 100M/day)
- OR random 7-char Base62 with collision check
'''
print(architecture)
print('Key design decisions:')
print('  - Read/Write split: separate services for scalability')
print('  - Cache-aside pattern: read from Redis, fallback to DB')
print('  - 301 vs 302 redirect: 301 cached by browser (less load), 302 always hits server (analytics)')

الخطوة O: التحسين ومعالجة الاختناقات

تعالج مرحلة التحسين الاختناقات وتوسّع النظام. وبالنسبة إلى خدمة اختصار عناوين URL، تشمل المخاوف الأساسية: زمن إعادة التوجيه، وذلك بوضع Redis قريبًا من المستخدمين عبر CDN أو ذاكرات التخزين المؤقت الطرفية؛ وتفرّد alias على نطاق واسع، وذلك باستخدام خادم تذاكر مركزي أو التجزئة مع كشف التصادمات؛ واختناق الكتابة في قاعدة البيانات، وذلك عبر الكتابات الدفعية أو الكتابات غير المتزامنة باستخدام قائمة انتظار.

ناقش المفاضلات صراحةً: تقلّل عمليات إعادة التوجيه 301 حمل الخادم، لكنها تفقد دقة التحليلات؛ أما عمليات إعادة التوجيه 302 فيمكن تتبّعها، لكنها تضيف زمنًا مستغرقًا. يقلّل التخزين المؤقت ذو مدة الانتهاء الطويلة حمل قاعدة البيانات، لكنه يعرّضك لخطر عناوين URL القديمة. إن إظهار فهمك لهذه الموازنة يدل على تفكير بمستوى متقدم.

# Key optimisation discussion points
optimisations = {
    'Read latency': [
        'Redis cluster in multiple regions (CDN-edge caching)',
        '301 redirect for non-tracked URLs (client caches)',
        '302 redirect when click analytics are needed',
    ],
    'Write throughput': [
        'Async writes: accept request, enqueue to Kafka, batch-commit to DB',
        'Ticket server (centralised ID generator) to avoid UUID collision',
        'Or: hash(user_id + timestamp + random) with retry on collision',
    ],
    'Availability': [
        'Multi-AZ PostgreSQL with automatic failover',
        'Redis Sentinel or Redis Cluster for HA cache',
        'Health checks + circuit breaker on each service',
    ],
    'Storage': [
        'Partition urls table by hash(alias) for horizontal scaling',
        'Archive expired URLs to cold storage (S3)',
    ],
}
for area, points in optimisations.items():
    print(f'{area}:')
    for p in points: print(f'  - {p}')
    print()

إنشاء alias: ترميز Base62

من التفاصيل التقنية الأساسية في خدمات اختصار عناوين URL كيفية إنشاء aliases قصيرة وفريدة. النهج القياسي هو استخدام مُعرّف عدد صحيح متزايد تلقائيًا من قاعدة البيانات، ثم ترميزه باستخدام Base62، الذي يتكوّن من الأرقام 0-9 والحروف a-z وA-Z. ويمكن لسلسلة Base62 مكوّنة من 7 محارف تمثيل ‏62^7 ≈ 3.5 تريليون عنوان URL فريد، وهو عدد يكفي لعقود بمعدل 100M يوميًا.

هذا النهج خالٍ من التصادمات، لأن كل مُعرّف فريد، وينتج سلاسل قصيرة وآمنة للاستخدام في عناوين URL. ويكون تعيين ID→alias حتميًا وقابلًا للعكس. أما المفاضلة فهي أن المعرّفات المتسلسلة تنشئ aliases يمكن التنبؤ بها، وهو مصدر قلق أمني. أعد ترتيب أبجدية Base62 أو استخدم إزاحة للعداد لتقليل إمكانية التنبؤ.

import string

BASE62_CHARS = string.digits + string.ascii_lowercase + string.ascii_uppercase
BASE = 62

def encode_base62(num):
    if num == 0:
        return BASE62_CHARS[0]
    result = ''
    while num:
        result = BASE62_CHARS[num % BASE] + result
        num //= BASE
    return result

def decode_base62(s):
    result = 0
    for c in s:
        result = result * BASE + BASE62_CHARS.index(c)
    return result

# Generate aliases for IDs 1, 100, 1000, 10^9
for id_ in [1, 100, 1000, 1_000_000, 10**9, 3_521_614_606207]:
    alias = encode_base62(id_)
    decoded = decode_base62(alias)
    print(f'ID {id_:20,} => alias "{alias}" (len={len(alias)}) => decoded={decoded}')

تطبيق RADIO على تصميم موجز Twitter

لنطبّق RADIO بإيجاز على تصميم موجز أخبار شبيه بـ Twitter لنوضح قابلية تعميم الإطار:

  • R: ينشر المستخدمون التغريدات، ويتابعون الآخرين، ويرون موجزًا لتغريدات الأشخاص الذين يتابعونهم. النطاق: 300M مستخدم، و500M تغريدة/يوم، ويجب أن يُحمّل الموجز في أقل من <2s.
  • A: POST /tweets، GET /feed، GET /timeline/{user_id}
  • D: جدول tweets ‏(id, user_id, content, created_at)؛ وجدول follows ‏(follower_id, followee_id)؛ وذاكرة مؤقتة للموجز لكل مستخدم
  • I: تكتب خدمة Fan-out التغريدات الجديدة مسبقًا في موجزات المتابعين؛ وتُستخدم Cassandra لجداول المتابعة والتغريدات الكثيفة الكتابة؛ ويُستخدم Redis لذاكرات الموجز المؤقتة
# Fan-out on Write vs Fan-out on Read trade-off
fan_out_strategies = {
    'Fan-out on Write (Push)': {
        'How': 'When user A posts, immediately write to all followers feeds',
        'Pro': 'O(1) feed read — feed is precomputed',
        'Con': 'Celebrities with 10M followers => 10M writes per post; very slow write',
        'Best for': 'Users with few followers (regular users)',
    },
    'Fan-out on Read (Pull)': {
        'How': 'When user reads feed, query followees tweets and merge',
        'Pro': 'Writes are fast (one DB write per tweet)',
        'Con': 'Feed read is slow: must query all N followees',
        'Best for': 'Celebrity accounts (few reads per post)',
    },
    'Hybrid': {
        'How': 'Push for regular users, pull for celebrities',
        'Pro': 'Balances read and write cost',
        'Con': 'More complex implementation',
        'Best for': 'Production systems like Twitter',
    },
}
for strategy, details in fan_out_strategies.items():
    print(f'{strategy}:')
    for k, v in details.items(): print(f'  {k}: {v}')
    print()

الأخطاء الشائعة في مقابلات تصميم الأنظمة

تجنّبوا هذه الأخطاء المتكررة التي تتسبب في تعثر مقابلات تصميم الأنظمة:

  • القفز إلى الحلول: رسم المربعات قبل توضيح المتطلبات يدل على عادات هندسية ضعيفة
  • عدم إجراء التقديرات: التصميم دون معرفة الحجم هو مجرد تخمين
  • الإفراط في الهندسة: تصميم النظام لمليار مستخدم عندما ينص السؤال على 10,000 يهدر وقت المقابلة
  • عدم مناقشة المفاضلات: لكل اختيار مزايا وعيوب؛ وعدم ذكرها يوحي بفهم سطحي
  • الصمت: يحتاج المحاورون إلى سماع طريقة تفكيركم؛ اشرحوا قراراتكم أثناء اتخاذها
# Checklist: before you stop talking, verify you covered:
checklist = [
    '[ ] Asked clarifying questions about scale and constraints',
    '[ ] Made capacity estimates (RPS, storage, bandwidth)',
    '[ ] Defined the API surface clearly',
    '[ ] Described the data model and storage choices',
    '[ ] Drew a high-level architecture with named components',
    '[ ] Identified the main bottleneck and proposed a solution',
    '[ ] Discussed at least one trade-off explicitly',
    '[ ] Verified the design meets the stated requirements',
]
for item in checklist:
    print(item)

اختبار سريع

اختبروا فهمكم لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.

مراجعة الدرس

في هذا الدرس تعلمتم أن إطار RADIO ينظم مقابلات تصميم الأنظمة ضمن Requirements وAPI وData Model وInfrastructure وOptimise، وأن توضحوا دائمًا المتطلبات الوظيفية وغير الوظيفية وتجروا تقديرات السعة قبل اقتراح تصميم، وأن تناقشوا المفاضلات صراحةً — فلكل قرار تصميم مزايا وعيوب يتوقع المحاورون منكم توضيحها. بعد ذلك سنستكشف كيفية الاختيار بين محركات تخزين SQL وNoSQL بناءً على أنماط الوصول والاتساق والحجم.

الأسئلة الشائعة

هل درس «إطار مقابلة تصميم الأنظمة» مجاني؟

نعم — نص درس «إطار مقابلة تصميم الأنظمة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «إطار مقابلة تصميم الأنظمة»؟

تعرّف إلى إطار RADIO ذي الخطوات الخمس (Requirements وAPI وData وInfrastructure وOptimise)، وتدرّب على تطبيقه على أداة اختصار عناوين URL. تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟

لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «إطار مقابلة تصميم الأنظمة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟

نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. إطار مقابلة تصميم الأنظمة
  2. تخزين البيانات القابل للتوسّع: SQL مقابل NoSQL
  3. التخزين المؤقت وشبكات CDN وموازنة الحمل
  4. تصميم محدِّد معدل الطلبات وتصميم موجز Twitter
← العودة إلى Coding Interview Prep