לדלג לתוכן

רפליקציה במסד נתונים מוסברת - איך שומרים עותקים בלי לאבד את הראש

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

הרעיון הבסיסי - עותק אחד ראשי, כמה עותקים משניים

בדפוס הנפוץ ביותר, יש שרת אחד ראשי (primary או master) שמקבל את כל פעולות הכתיבה, וכמה שרתי עותק (replicas) שמקבלים ממנו את השינויים ומעדכנים את עצמם בהתאם. כל פעם שנכתב נתון חדש בראשי, הוא משוכפל אוטומטית לכל העותקים.

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

סינכרוני מול אסינכרוני - הפשרה שחשוב להכיר

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

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

הבעיה שממנה נובעות רוב הטעויות - עקביות קריאה

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

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

רפליקציה זה לא sharding, וחשוב לא לבלבל ביניהם

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

מה קורה כשהראשי נופל

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

איפה לומדים לתכנן את זה נכון

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

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

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

לסיכום

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