Message Queues ו-Event-Driven Architecture

📚 System Design ⏱️ 13 דק׳ 🎓 מתקדם ✓ חינם לגמרי
Message Queues ו-Event-Driven Architecture

למה מערכות גדולות לא מדברות ישירות זו עם זו

AppsFlyer, חברת ה-attribution הישראלית, מעבדת 80 מיליארד events ביום. אם כל service הייתה קוראת ישירות לservice אחרת, API call אחד לשרת עמוס, timeout, קריסה, כל השרשרת הייתה נופלת. הפתרון שנבחר: Kafka. במקום API calls ישירים, כל service מפרסמת event לtopic, וכל מי שמעניין לו, קורא בקצב שלו, באופן עצמאי.

זה הרעיון של Event-Driven Architecture: decoupling. Service A לא יודעת שService B קיימת. היא רק מפרסמת "קרה משהו". Service B מקשיבה ומגיבה. אפשר להוסיף Service C מחר, בלי לשנות שורת קוד ב-A.

Kafka, RabbitMQ, SQS, ההבדלים שחשובים בפועל

קריטריוןKafkaRabbitMQAWS SQS
מודלLog-based, pullQueue-based, pushQueue-based, pull
Retentionימים/שבועות, replayעד שנצרךעד 14 ימים
Throughputמיליוני events/שנייהעשרות אלפי msg/שנייהמאות אלפי msg/שנייה
RoutingTopics + partitionsExchanges מתוחכמיםSNS fanout נדרש
Multi-consumerכל Consumer Group קורא הכלExchange + bindingsSNS + SQS
הכי מתאים לEvent sourcing, analytics, replayBackground jobs, complex routingServerless, AWS stack פשוט

עדכון חשוב, Kafka 4.0 (מרץ 2025): ZooKeeper הוסר לחלוטין. מי שמשתמש ב-Kafka 3.x עם ZooKeeper חייב לעבור ל-KRaft לפני שידרגו ל-4.0. ב-KRaft, recovery time מהיר פי 10. בנוסף, KIP-932 (Early Access) מביא Queue semantics ישירות לKafka, הבדל המסורתי בינה לRabbitMQ מיטשטש.

Consumer Groups, המנגנון שמאפשר scale ו-isolation

Consumer Group הוא אחד המושגים שהכי חשוב להבין ב-Kafka. הגדרה פשוטה: קבוצת consumers שחולקת ביניהם את ה-partitions. כל partition נצרכת בדיוק על-ידי consumer אחד בתוך ה-group. זה אומר שתי דברים:

דוגמה ישראלית: Wolt. כשorder מתקבל, topic אחד OrderPlaced מוזן על-ידי producer אחד. Consumer groups מקבילות: notifications-service (SMS ללקוח), dispatch-service (ניתוב לרידר), analytics-service (דשבורד real-time), fraud-service (בדיקת חריגות). כל אחת עצמאית, כל אחת בקצב שלה.

הסבר לי על Kafka
Design a Kafka topology for Wolt-style Israeli food delivery (50K orders/day, peak 5K orders/hour). Events: OrderPlaced, DriverAssigned, OrderDelivered, OrderCancelled. Consumers: notifications, dispatch, analytics, billing. Specify: topic structure, partition count and key per topic, consumer group assignments, retention policy. Critical question: if fraud-detection consumer lags 10 minutes behind, does it block the notifications consumer?

Delivery Semantics, כמה פעמים message אחת תגיע?

זו הבחירה שחייבים לקבל בכל system event-driven:

ב-payment processing, at-least-once פירושו סכנה של חיוב כפול. הפתרון: idempotency. כל event נושאת idempotency key (למשל orderId+paymentAttempt). Consumer בודק לפני עיבוד. הטריק: אל תשתמשו ב-SELECT ואז INSERT, זו race condition. השתמשו ב-INSERT INTO processed_events (event_id) VALUES ($1) ON CONFLICT DO NOTHING, atomic, thread-safe.

הבעיות שפוגשים בproduction

Poison Pill Messages, message שגורמת ל-consumer לקרוש בכל פעם שמנסים לעבד אותה. בלי Dead Letter Queue (DLQ), consumer נתקע לנצח. כל message אחריה חסומה. DLQ לוקח messages שנכשלו N פעמים ומסיר אותן מה-main queue לqueue נפרדת לחקירה. זה לא optional, זה חובה בכל production system.

Ordering Assumptions לא נכונות, Kafka מבטיח ordering בתוך partition בלבד. אם צריך ordering של events לאותו user (OrderPlaced לפני PaymentProcessed לפני OrderShipped), כל events של אותו userId חייבים לנותב לאותה partition. partition key = userId. בלי זה, PaymentProcessed יכול להגיע לפני OrderPlaced.

Consumer Lag Monitoring חסר, Consumer lag הוא הפרש בין ה-offset האחרון ב-topic לoffset שה-consumer צרך. כשlag גדל, consumers לא מדביקים את ה-producers. זה ה-metric הקריטי. system שנראה בריא אבל עם consumer lag גדל הוא כבר בבעיה. הגדירו alert ב-Prometheus/Grafana על consumer lag מעל סף מוגדר.

Outbox Pattern, הפתרון לבעיה שכולם עוברים

בעיה קלאסית: שמרתם record ב-PostgreSQL, ניסיתם לשלוח event לKafka, הרשת נפלה. עכשיו ה-DB מעודכן אבל הevent לא יצאה. dual-write problem.

Outbox Pattern פותר זאת: כתבו ל-business table ול-outbox table באותה DB transaction. Process נפרד (Debezium CDC) קורא מה-outbox ושולח לKafka. אם Kafka לא זמינה, events מחכות ב-outbox. עם שתי ה-writes ב-transaction אחת, אי אפשר לקבל מצב שה-DB מעודכן בלי event, או event בלי DB update.

Debezium 2.5+ (2025) הוא ה-industry standard לconfiguration כזו. אבל שימו לב: "Stop overusing the outbox pattern", מוסיף complexity. אם ה-system שלכם לא מגיע ל-scale שבו dual-write בעיה אמיתית, אולי לא שווה את ה-overhead.

dispatch-service נחסמת גם כן עד שnotification-service מתאושש
dispatch-service ממשיכה לרוץ בלי הפרעה, כל consumer group עצמאית
Kafka מוחקת events ישנות ו-dispatch-service מפסידה אותן
שני ה-groups עוברים consumer group rebalance
גורם לlatency גבוהה יותר מ-INSERT ישיר
SQL server לא תומך בSELECT לפני INSERT
שני consumers יכולים לעבור את ה-SELECT בו-זמנית לפני שאחד מהם עשה INSERT
PostgreSQL לא תומך בסדר הזה של פקודות
הוספת partition semantics לראשונה
הכנסת Consumer Groups כfeature חדש
הסרה מלאה של ZooKeeper, Kafka רצה עכשיו על KRaft בלבד
הוספת exactly-once delivery semantics לראשונה

איך Claude עוזר לתכנן event-driven systems

Claude מצטיין בשאלות ארכיטקטורה שיש להן trade-offs. כשהוא מסביר שאלת Kafka, הוא לא רק נותן תשובה, הוא מסביר למה ומה ה-alternatives. פרומפטים שעובדים:

נקודות מפתח לסיכום

רוצה ללמוד עם מעקב התקדמות, קוויזים ותעודה?

כל 130 השיעורים פתוחים בחינם, כולל נגן אינטראקטיבי, שמירת התקדמות ותעודה דיגיטלית בסיום.