מסד נתונים בהיקף גדול - מה צריך לדעת¶
בכל שיחה על מדרגיות, בסוף מגיעים לאותה נקודת חיכוך - מסד הנתונים. אפשר להוסיף כמה שרתי אפליקציה שרוצים, אבל אם כולם פונים לאותו מסד נתונים בודד, בשלב מסוים הוא יהפוך לצוואר הבקבוק של המערכת כולה. אז מה עושים כשמסד נתונים אחד כבר לא מספיק?
קודם כל - כמה רחוק אפשר להגיע עם מסד נתונים אחד¶
לפני שרצים להוסיף מורכבות, כדאי לדעת שמסד נתונים בודד, על חומרה טובה עם אינדקסים נכונים ושאילתות כתובות היטב, מסוגל לשרת כמות עצומה של תעבורה - הרבה יותר ממה שרוב האפליקציות בפועל מייצרות. הרבה בעיות "מדרגיות" שמפתחים נתקלים בהן הן בעצם בעיות של שאילתות לא יעילות או חוסר אינדקסים, לא בעיה אמיתית של קנה מידה. הצעד הראשון תמיד צריך להיות אופטימיזציה של מה שיש, לא הוספת שכבות.
הצעד הראשון האמיתי - שכפול לקריאה¶
כשבאמת מגיעים לגבול, הצעד הראשון והכי נפוץ הוא שכפול (replication) - יצירת עותקים נוספים של מסד הנתונים שמסתנכרנים אוטומטית מהמקור. במודל הנפוץ ביותר, יש שרת ראשי (primary) אחד שמקבל את כל הכתיבות, וכמה שרתי עותק לקריאה (read replicas) שמקבלים את כל הקריאות.
זה עובד מצוין כי ברוב האפליקציות יש הרבה יותר קריאות מכתיבות - חושבים על אתר מסחר, לכל הזמנה יש עשרות צפיות במוצרים. פיזור הקריאות בין כמה עותקים מפחית עומס משמעותי מהשרת הראשי, שיכול להתמקד בכתיבות בלבד. החיסרון - יש עיכוב קטן (replication lag) בין הרגע שנתון נכתב לראשי לרגע שהוא מופיע בעותקים, כך שאם קוראים מיד אחרי כתיבה מאותו עותק, אולי עוד לא רואים את השינוי.
כשגם שכפול לא מספיק - פיצול נתונים¶
יש מקרים שבהם הבעיה היא לא כמות הקריאות אלא כמות הכתיבות, או פשוט כמות הנתונים עצמה שגדלה מעבר למה שמכונה אחת יכולה להחזיק ביעילות. כאן נכנס פיצול נתונים (sharding) - חלוקת הנתונים בין כמה מסדי נתונים נפרדים לגמרי, כשכל אחד מחזיק רק חלק מהנתונים.
למשל, אפשר לפצל לפי מזהה משתמש - משתמשים עם ID בין 1 ל-1,000,000 בשרד אחד, בין 1,000,001 ל-2,000,000 בשרד שני, וכן הלאה. הבחירה של מפתח הפיצול (shard key) קריטית - היא קובעת אם הפיצול יהיה מאוזן, ואיזה שאילתות יישארו פשוטות ואיזה יהפכו למסובכות. שאילתה שמבקשת נתונים ממשתמש בודד קלה - יודעים בדיוק לאיזה שרד לפנות. שאילתה שצריכה לצרף נתונים ממשתמשים במספר שרדים שונים הופכת מסובכת ויקרה משמעותית.
SQL מול NoSQL - זו לא שאלה של מודרני מול מיושן¶
יש תפיסה מוטעית שכשמגיעים להיקף גדול, חייבים לעבור ל-NoSQL. זה לא נכון. מסדי נתונים יחסיים (SQL) כמו Postgres מתפקדים מצוין בהיקפים עצומים - חברות ענק מריצות אותם עם מיליארדי רשומות. השאלה האמיתית היא לא היקף, אלא צורת הנתונים והצרכים.
מסדי NoSQL כמו MongoDB או Cassandra מתאימים כשהנתונים לא דורשים קשרים מורכבים בין טבלאות, כשצריך גמישות סכימה גבוהה, או כשמדובר בכתיבות בקצב עצום שדורשות מבנה מבוזר מלכתחילה. אבל הם מוותרים על יכולות שמסדי SQL נותנים בחינם - עסקאות (transactions) מלאות, שלמות רפרנציאלית, ושאילתות מורכבות עם JOIN. הבחירה צריכה להיגזר מהצורך העסקי, לא מטרנד.
קאשינג - הכלי הכי זול לחיסכון בעומס על מסד הנתונים¶
לפני שמתעסקים בפיצול נתונים מורכב, שכבת קאש כמו Redis יכולה לחסוך המון עומס בעלות מאמץ נמוכה יחסית. שאילתות שחוזרות על עצמן הרבה - למשל, פרטי מוצר פופולרי שנצפה אלפי פעמים ביום - אפשר לשמור בקאש בזיכרון מהיר, ולפנות למסד הנתונים רק כשהמידע בקאש לא קיים או פג תוקף. זה מקטין דרמטית את מספר הפניות בפועל למסד הנתונים.
הנושא הזה, יחד עם מדרגיות ואיזון עומס, הוא בדיוק מה שמרכיב את היכולת האמיתית לעצב מערכות בהיקף גדול, ואנחנו עוברים עליו לעומק עם דוגמאות מספריות אמיתיות בקורס ארכיטקטורת תוכנה.
מתלבטים בין שכפול, פיצול, או פשוט אופטימיזציה של השאילתות הקיימות שלכם? שאלו בדיסקורד, לרוב זה חוסך שבועות של ניסוי וטעייה.
לסיכום¶
מסד נתונים בהיקף גדול לא אומר בהכרח מסד נתונים מסובך. הצעד הראשון תמיד צריך להיות אופטימיזציה של מה שקיים - אינדקסים ושאילתות טובות יותר. אחר כך שכפול לקריאה, שפותר את רוב הבעיות בפועל. פיצול נתונים הוא כלי כבד ומורכב שראוי לשמור לרגע שבו באמת הגעתם לגבולות של הפתרונות הפשוטים יותר. ובכל שלב, קאשינג טוב יכול לחסוך לכם המון כאב בעלות נמוכה יחסית.