לדלג לתוכן

מתכננים מקצר קישורים בקנה מידה - הרבה יותר מ-CRUD פשוט

מקצר קישורים נשמע כמו הפרויקט הכי פשוט שאפשר לבנות - טבלה אחת שממפה קוד קצר לכתובת ארוכה, ושתי פעולות: יצירה וקריאה. וזה נכון, אם מדובר בעשרה משתמשים. אבל ברגע שמכניסים את המילה "בקנה מידה" - כלומר מיליוני קישורים ומיליארדי קליקים - כל החלטה קטנה הופכת משמעותית. זה בדיוק מה שהופך את הפרויקט הזה לתרגיל תכנון כל כך פופולרי בראיונות עבודה, וגם כל כך שימושי ללמוד לבד.

למה זו מערכת שנשלטת על ידי קריאה (read-heavy)

הצעד הראשון בכל תכנון הוא להבין את דפוס התעבורה. במקצר קישורים, כל קישור נוצר פעם אחת אבל נלחץ עליו הרבה פעמים - אולי אלפי לחיצות על אותו קוד קצר. זה אומר שהמערכת היא read-heavy במובהק, ולכן כל אופטימיזציה צריכה להתמקד קודם כל בהאצת הקריאה, לא הכתיבה. זו נקודת מוצא שמשנה כמעט כל החלטה הבאה.

שכבת מטמון - למה בלעדיה המערכת לא תחזיק מעמד

אם כל קליקה על קישור מצריכה שאילתה למסד הנתונים, המערכת תיפול תחת עומס גבוה - גם אם מדובר בשאילתה מהירה יחסית. הפתרון הנפוץ הוא שכבת מטמון (למשל Redis) שיושבת בין השרת למסד הנתונים ומחזיקה את הקישורים הפופולריים ביותר בזיכרון. ברוב המערכות האמיתיות, אחוז קטן מהקישורים אחראי לרוב הענק של הקליקות - וזה בדיוק מה שהופך מטמון ליעיל כל כך: מספיק לשמור את "החם" בזיכרון מהיר, והשאר יכול להישאר במסד הנתונים האיטי יותר.

אינדקס נכון על מסד הנתונים

השאילתה הכי נפוצה במערכת היא "תן לי את הכתובת המקורית עבור הקוד הקצר X". זו שאילתה שחייבת להיות מהירה ברמת מילישניות בודדות, ולכן הקוד הקצר צריך להיות מאונדקס - ברוב המקרים אפילו כמפתח ראשי (primary key), ולא רק שדה רגיל בטבלה. זו נקודה שנשמעת טריוויאלית, אבל היא בדיוק סוג ההחלטה שמפרידה מערכת שסובלת עומס למערכת שקורסת תחתיו.

יצירת קודים ייחודיים בקנה מידה

כאן נמצאת הבעיה המעניינת ביותר בפרויקט. איך מייצרים קוד קצר ייחודי (למשל שש תווים) כשיש מיליוני קישורים שכבר קיימים, ובלי שרת אחד צריך "לחכות" לכל השאר?

יש כמה גישות נפוצות:

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

מונה גלובלי מומר ל-Base62 - שומרים מספר סידורי עולה (1, 2, 3...) וממירים אותו לייצוג בבסיס 62 (אותיות וספרות), מה שנותן קוד קצר וייחודי מובנה. הבעיה - מונה מרכזי יחיד יכול להפוך לצוואר בקבוק כשיש הרבה שרתים כותבים בו-זמנית.

הקצאת טווחים מראש - כל שרת מקבל מראש "טווח" של מזהים (למשל 1,000 מספרים) שהוא יכול להשתמש בהם בלי לתאם עם אף שרת אחר בכל בקשה. זו הגישה שמשתמשים בה מערכות אמיתיות בקנה מידה גדול, כי היא מבטלת את צוואר הבקבוק לגמרי.

שיקולי סקיילינג נוספים

כשהמערכת גדלה עוד יותר, מסד הנתונים עצמו יכול להתחלק (sharding) לפי טווח קודים או hash של הקוד, כך שאף מכונה בודדת לא מחזיקה את כל הנתונים. גם שכבת האפליקציה עצמה בדרך כלל stateless - כלומר כל בקשה יכולה להגיע לכל שרת, בלי תלות בזיכרון מקומי - מה שמאפשר להוסיף שרתים נוספים בקלות תחת עומס.

למה שווה לתרגל את זה גם בלי לבנות בפועל

תרגילי "תכננו X בקנה מידה" נפוצים כל כך בראיונות עבודה בדיוק כי הם בודקים חשיבה, לא ידע פרטני. אין תשובה "נכונה" יחידה - יש טרייד-אופים, ומה שמעניין את המראיין הוא איך אתם מנמקים בחירה. תרגול המערכת הזו לבד, גם בלי לכתוב שורת קוד, בונה בדיוק את יכולת ההנמקה הזאת.

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

רוצים להתווכח על גישת ה-Base62 מול הקצאת טווחים? יש לנו קהילה שאוהבת בדיוק דיונים כאלה.

הצטרפו לקהילה בדיסקורד