איך להתכונן לראיון System Design
ב-2019, Slack עבר 100 מיליון הודעות ביום. ב-2020 — עם COVID — הם עלו ל-1.5 מיליארד הודעות ביום בשיא. מי שתכנן את הארכיטקטורה ב-2019 צריך היה לחשוב: "מה קורה אם התנועה מכפילה את עצמה פי 15 בחודש?" מי שלא חשב — היה נלחץ ב-March 2020.
ראיון System Design בודק בדיוק את היכולת הזו: לחשוב על מערכת שלמה, לזהות את אבני הבניין, ולהבין איפה דברים יישברו לפני שהם נשברים.
מה בודקים — ולמה אין תשובה "נכונה"
בניגוד לראיון Algorithms שיש בו תשובה מיטבית, ב-System Design אין תשובה אחת נכונה. מראיין טוב לא מחפש שתגידו "Kafka". הוא מחפש:
יכולת לפרק בעיה מורכבת — לקחת "תכנן Twitter" ולשבור אותה לcomponents ניתנים לניהול.
מודעות ל-tradeoffs — "אם נשתמש ב-NoSQL, מרוויחים X אבל מפסידים Y". לא רק לדעת מה לבחור — לדעת מה המחיר.
thinking at scale — "מה קורה כש-1 מיליון users מתחברים בו-זמנית?" לא לתכנן לכמות users הנוכחית — לתכנן לעשר פעמים ממנה.
communication — לחשוב בקול. המראיין לא יכול לקרוא מחשבות. אם לא מסבירים למה בוחרים מה שבוחרים — הוא לא יכול לתת לכם credit.
ה-Framework: 4 שלבים לכל שאלה
שלב 1: Requirements (5-10 דקות)
לפני שציירים כלום — שואלים. זה לא אות חולשה, זה אות בשלות.
שאלות חובה:
├── Scale: "כמה users? כמה requests/שנייה? כמה data?"
├── Availability: "מה ה-SLA? 99.9% = 9 שעות downtime/שנה. 99.99% = 1 שעה"
├── Consistency: "האם read-after-write חשוב? האם eventual consistency OK?"
├── Latency: "מה זמן תגובה מקסימלי נסבל?"
└── Scope: "מה ה-features הקריטיים לתכנן עכשיו?"דוגמה: "תכנן URL Shortener."
שאלות חיוניות: כמה URLs מקצרים ביום? כמה redirects? האם URL ניתן לשינוי אחרי יצירה? האם צריך analytics? כמה זמן שומרים URL? האם צריך vanity URLs (custom slugs)?
שלב 2: High-Level Design (10-15 דקות)
ציירו את הblocks הגדולים. לא לרדת לפרטים עדיין.
שלב 3: Deep Dive (15-20 דקות)
בחרו 1-2 components ותצללו לעומק. מה schema הDB? מה אסטרטגיית הcaching? איך מייצרים short URLs?
שלב 4: Bottlenecks ו-Failure Modes (5 דקות)
"איפה המערכת הזו תיפול?" DB write bottleneck? Cache invalidation? Single point of failure?
נושאי ליבה שחוזרים בכל ראיון
Scaling
Vertical Scaling — שרת חזק יותר. פשוט, אבל יש ceiling. השרת הכי חזק בעולם עדיין מוגבל.
Horizontal Scaling — יותר servers מאחורי Load Balancer. דורש שה-app יהיה stateless — כל request יכול להגיע לכל server.
Database Sharding — פיצול ה-DB לכמה מכונות לפי key. ה-challenge: איך מחלקים? לפי user_id? לפי geography? כל גישה עם tradeoffs שונים.
| שיטת Sharding | יתרון | חיסרון |
|---|---|---|
| Range-based (user 1-1M → shard 1) | פשוט לממש | hot spots: user IDs חדשים כולם ב-shard אחד |
| Hash-based (hash(user_id) % N) | פיזור אחיד | קשה להוסיף shards — rehashing |
| Directory-based (lookup table) | גמיש | ה-directory עצמו bottleneck |
| Geographic (EU → shard EU) | data locality | לא אחיד אם יש market גדול |
Caching
Caching הוא הכלי הראשון שמשתמשים בו לבעיות read performance. הרעיון: תוצאות יקרות (DB queries, API calls) נשמרות בmemory מהיר (Redis, Memcached) לזמן מוגבל.
אסטרטגיות invalidation — הבעיה הקשה:
- TTL (Time To Live) — data פג תוקף אחרי X זמן. פשוט, אבל data יכול להיות stale עד X.
- Cache-aside — app מנהל cache ידנית: בדוק cache → miss → קרא DB → כתוב לcache.
- Write-through — כל write לDB → write לcache במקביל. תמיד fresh, אבל write overhead.
- Cache invalidation on update — כשDB מתעדכן, מחקו את ה-cache key. פשוט בעיקרון, מורכב כשיש כמה servers.
Phil Karlton אמר: "There are only two hard things in Computer Science: cache invalidation and naming things." זה לא בדיחה — cache invalidation bugs גרמו ל-incidents בכל חברה גדולה.
Database Design — SQL vs NoSQL
השאלה לא "SQL טוב יותר מNoSQL". השאלה היא: "מה ה-access patterns שלי?" SQL מצוין עם joins מורכבים, ACID transactions, ושאילתות שלא ידועות מראש. NoSQL מצוין עם scale-out אופקי, schemaless data, ו-access patterns פשוטים ויציבים.
| קריטריון | SQL (PostgreSQL, MySQL) | NoSQL (MongoDB, DynamoDB, Cassandra) |
|---|---|---|
| Schema | קפיד, migration מוגדר | גמיש, schemaless |
| Transactions | ACID מלא | מוגבל, בד"כ per-document |
| Scale | vertical (בעיקר), read replicas | horizontal, sharding built-in |
| Joins | native, efficient | לא נתמך או יקר |
| Query flexibility | גבוהה — SQL שרירותי | נמוכה — צריך לדעת queries מראש |
| Consistency | strong consistency | configurable (eventual↔strong) |
הכלל הפשוט: אם אתם יודעים את access patterns מראש ולא צריכים joins — NoSQL. אם אתם צריכים flexibility בquerying — SQL.
Message Queues
Kafka, RabbitMQ, AWS SQS — מה הם ולמה חשובים?
כשservice A צריך לקרוא לservice B, ויש שתי אפשרויות:
Synchronous (HTTP call): A מחכה לתשובה מB. אם B איטי — A איטי. אם B נפל — A נכשל.
Asynchronous via Queue: A שולח message לqueue וממשיך. B קורא מה-queue כשהוא מוכן. אם B נפל — message נשאר ב-queue עד שB חוזר.
שימושים קלאסיים ל-Message Queue:
Order completed → [Queue] → Email Service
→ Inventory Service
→ Analytics Service
→ Fulfillment Service
כל אחד קורא בקצב שלו, נכשל בלי להוריד אחרים.Kafka מצוין לevent streaming בvolume גבוה (millions/sec). RabbitMQ מצוין לtask queues עם routing מורכב. SQS — fully managed, פחות features, אבל אפסי תחזוקה.
CAP Theorem
בsystem distributed בלתי אפשרי לקבל את שלושתם בו-זמנית:
C — Consistency: כל read מחזיר את הwrite האחרון (או error). A — Availability: כל request מקבל תשובה (לא error). P — Partition Tolerance: המערכת ממשיכה לעבוד גם כש-nodes מנותקים.
בpractice, Partition Tolerance הוא הכרחי בכל distributed system (רשת תמיד יכולה להיכשל). אז הבחירה האמיתית היא CP vs AP:
- CP: כשיש partition — מחזיר error (לא ריספונסיבי) במקום תשובה שאולי stale. בנקאות, transactions.
- AP: כשיש partition — מחזיר תשובה שאולי לא עדכנית. Social feeds, shopping cart.
URL Shortener — walkthrough מלא
"תכנן URL Shortener כמו bit.ly"
Requirements שנקבל:
- 100M URLs חדשים ביום (1200 writes/sec)
- 10B redirects ביום (115K reads/sec — read-heavy)
- URLs נשמרים 5 שנים
- זמן redirect < 50ms ב-p99
- 99.99% availability
Estimation: 100M URLs/day × 365 × 5 = 182.5B URLs. ממוצע URL: ~500 bytes → כ-91TB storage.
Short Code Generation — אחת הבעיות הנפוצות:
import hashlib
import base62
def generate_short_code(long_url: str, user_id: str) -> str:
# הוסיפו timestamp למניעת collisions
input_str = f"{long_url}:{user_id}:{time.time()}"
# MD5 hash של ה-input
hash_bytes = hashlib.md5(input_str.encode()).digest()
# המרה ל-base62 (a-z, A-Z, 0-9) — 8 תווים = 62^8 = 218 טריליון אפשרויות
short_code = base62.encodebytes(hash_bytes)[:8]
return short_codeCaching Strategy — read-heavy בvery much:
80/20 rule: 20% מה-URLs מייצרים 80% מה-traffic. Cache את ה-20% החם ב-Redis. 115K reads/sec — רוב מהcache.
async def redirect(short_code: str) -> str:
# Cache-aside pattern
cached = await redis.get(f"url:{short_code}")
if cached:
return cached
# Cache miss — לDB
url = await db.get_url(short_code)
if not url:
raise NotFoundException()
# Cache for 24h (hot URLs stay hot)
await redis.setex(f"url:{short_code}", 86400, url.original_url)
return url.original_urlהטעויות הנפוצות בראיון
לא לשאול שאלות: "תכנן Twitter" יכול להיות 50M users או 500M users. עם consistency מחמיר או eventual. עם Reels/Stories או בלי. בלי לשאול — אתם מתכננים מערכת שאולי לא מתאימה לרצון המראיין.
לקפוץ לפתרון לפני שהבנתם: "נשתמש ב-Kafka" — למה? Kafka מה-place הנכון כאן? מה הוא פותר? מי שפותח עם answers לפני שמבין את הbusiness constraints — מראה pattern חשיבה שגוי.
לדבר רק על happy path: המראיין רוצה לראות שאתם חושבים על failures. מה קורה כש-DB ראשי נפל? כשCache flush? כשthird-party API לא עונה? כשרשת מפוצלת?
לא לדבר בקול: המראיין לא יכול לתת לכם credit על מחשבות שלא אמרתם. גם אם ה-intuition שלכם נכון — אם לא אמרתם אותו, הוא לא נמדד.
תרגול אפקטיבי: קחו שאלה (Design WhatsApp, Design YouTube, Design Rate Limiter), שבו 45 דקות, ותעברו על כל 4 שלבים בקול. לא בראש — בקול. הבדל עצום בין "אני יודע לתכנן את זה" לבין "אני יודע להסביר לאחרים איך לתכנן את זה."
חידון
מה זה 'Thundering Herd' ואיך מונעים אותו?