איך מעצבים אפליקציית צ'אט כמו וואטסאפ - מקרה בוחן¶
עיצוב אפליקציית צ'אט הוא אחד השאלות הכי נפוצות בראיונות system design, ולא בכדי - היא נראית פשוטה על פני השטח, אבל מסתירה כמה בעיות מעניינות ברגע שמכניסים לתמונה מיליוני משתמשים, מכשירים מרובים, וחיבורים שנופלים כל הזמן. בואו נפרק אותה שלב אחרי שלב.
שלב 1 - למה HTTP רגיל לא מספיק¶
בקשת HTTP רגילה עובדת לפי מודל בקשה-תשובה - הקליינט שולח בקשה, השרת עונה, והחיבור נסגר. זה מצוין לטעינת דף, אבל לא מתאים לצ'אט, כי השרת צריך ליזום שליחת הודעה לקליינט ברגע שהיא מגיעה, בלי שהקליינט יבקש אותה. הפתרון המקובל הוא WebSocket - חיבור דו-כיווני שנשאר פתוח לאורך זמן, ומאפשר גם לשרת וגם לקליינט לשלוח הודעות בכל רגע בלי לפתוח חיבור חדש בכל פעם.
שלב 2 - ניתוב הודעה בין שני משתמשים¶
כשמשתמש א' שולח הודעה למשתמש ב', השרת צריך לדעת לאיזה שרת בדיוק ב' מחובר - כי במערכת גדולה יש הרבה שרתי WebSocket במקביל, ולא כל המשתמשים מחוברים לאותו אחד. הפתרון הנפוץ הוא שכבת ניתוב - מיפוי בזיכרון מהיר כמו Redis בין מזהה משתמש לשרת שהוא מחובר אליו כרגע. כשהודעה מגיעה, השרת המקבל בודק את המיפוי, ומעביר את ההודעה לשרת הנכון דרך תור הודעות פנימי, וזה מעביר אותה הלאה לקליינט.
שלב 3 - מה קורה כשהנמען לא מחובר¶
זו בדיוק הנקודה שבה אפליקציית צ'אט אמיתית נבדלת מדוגמה תיאורטית. אם משתמש ב' לא מחובר, לא ניתן להעביר לו הודעה בזמן אמת - אז ההודעה חייבת להישמר במסד נתונים כדי שתופיע כשהוא יתחבר מחדש. זה אומר שכל הודעה עוברת שני מסלולים במקביל - ניסיון מסירה מיידית דרך WebSocket אם הנמען מחובר, ושמירה קבועה במסד הנתונים בכל מקרה. כשהמשתמש חוזר להתחבר, האפליקציה שולפת את כל ההודעות שלא נמסרו עדיין, לפי חותמת זמן.
שלב 4 - סדר הודעות וכמה מכשירים¶
משתמשים היום מתחברים מכמה מכשירים בו זמנית - טלפון, מחשב, טאבלט - וכולם צריכים לראות את אותו סדר הודעות בדיוק. זה מוסיף מורכבות אמיתית - אי אפשר פשוט לסמוך על שעון המערכת של כל שרת, כי שעונים בין שרתים שונים לא בהכרח מסונכרנים לגמרי. פתרון נפוץ הוא להשתמש במונה סידורי (sequence number) שגדל עבור כל שיחה בנפרד, כך שכל הודעה מקבלת מספר סידורי חד משמעי, וכל המכשירים מציגים את ההודעות לפי המספר הזה ולא לפי שעון מקומי.
שלב 5 - אישורי מסירה וקריאה¶
הסימונים המוכרים - וי אחד נשלח, שני וי נמסר, וי כחול נקרא - הם בעצם אירועים נוספים שעוברים באותו ערוץ בדיוק כמו ההודעות עצמן. כל שינוי מצב יוצר אירוע קטן שנשלח חזרה לשולח, ולרוב גם הוא נשמר במסד הנתונים כדי שיישאר עקבי גם אם השולח מתנתק ומתחבר מחדש מאוחר יותר.
שלב 6 - קבוצות והצפנה¶
צ'אט קבוצתי מוסיף בעיה של קנה מידה - הודעה אחת בקבוצה עם אלף חברים צריכה להגיע לכולם, לא רק לאדם אחד. בפועל זה פועל דומה לבעיית הפאנאאוט שדיברנו עליה במקרה הבוחן על פיד חברתי - ההודעה מופצת לכל חברי הקבוצה, לרוב באמצעות תור עבודה אסינכרוני, כדי שלא לחסום את השולח בזמן שהמערכת מעדכנת אלף תיבות בו זמנית. הצפנה מקצה לקצה, כשהיא קיימת, מוסיפה עוד שכבה - השרת בכלל לא רואה את תוכן ההודעה, רק מנתב אותה מוצפנת, מה שאומר שכל הלוגיקה של סדר וחיפוש צריכה לעבוד בלי להסתמך על תוכן ההודעה עצמו.
זה מקרה בוחן שממחיש יפה איך כמה נושאים שדיברנו עליהם בעבר - תקשורת בזמן אמת, תורי הודעות, ומדרגיות - מתחברים לפתרון אחד שלם. בקורס ארכיטקטורת תוכנה שלנו עוברים על מקרי בוחן דומים ובונים יחד את החשיבה הזאת צעד אחר צעד.
מתלבטים איך הייתם פותרים חלק מסוים בבעיה הזו? הצטרפו לקהילה בדיסקורד ותביאו את הרעיון שלכם לדיון.
לסיכום¶
אפליקציית צ'אט נראית פשוטה, אבל מסתירה כמה בעיות אמיתיות של עיצוב מערכות - תקשורת בזמן אמת עם WebSocket, ניתוב הודעות בין שרתים מרובים, טיפול במשתמשים שלא מחוברים, וסדר עקבי בין כמה מכשירים. כל אחת מהבעיות האלה פותרת את עצמה בנפרד, אבל הערך האמיתי בראיון הוא להראות שאתם יודעים לחבר את כולן לפתרון אחד קוהרנטי.