הגדרת אבולוציית סכימה עבור מסדי נתונים NoSQL

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

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

אבולוציית סכמה ב-NoSQL: איפה הפרודקשן נשבר

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

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

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

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

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

מה נותן שינוי מבוקר במבנה הרשומות

תאימות נבדקת לפני ההחלה

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

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

החזרת שינוי לאחור — דקות במקום שעות

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

כשלים בקריאה עוברים לסוג מבוקר

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

הנתונים נכתבים מחדש בעצלתיים, לפי פנייה

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

עריכות ידניות עוברות לתקנון, והשחרור מואץ

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

גישות לאבולוציית סכמה: השוואת אפשרויות

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

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

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

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

שלבי הטמעה: מביקורת ועד מסירה

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

  1. מלאי גרסאות רשומות. סופרים איזה שיעור מהמסמכים מתאים לאיזו צורה: היכן סוג השדה התפצל, היכן נשארו שדות משחרורים שהוחזרו לאחור, היכן הקינון שונה. מקבלים מפה של מצב הנתונים בפועל, ולא הנחות לפי הקוד.
  2. סכמת תאימות ותוכנית שינוי מבנה. מחלקים את העריכות לתאימות לאחור ולשוברות: שדה אופציונלי השירות שורד מיד, שינוי שם או שינוי סוג — רק בשני שלבים, דרך תקופת קריאה של שתי האפשרויות.
  3. הגדרת כללי בדיקה. מפעילים את הוולידטור במצב רך: הוא כותב אי-התאמות ליומן ולא חוסם כתיבה. אחרי כמה ימים רואים מה בדיוק מפר את הכללים החדשים, והתיקונים בקוד נעשים לפי המצב בשטח.
  4. הרצת ניסוי על עותק נתונים. פורסים תמונת מצב של מסד העבודה בסביבה מבודדת ומריצים את הטרנספורמציה יחד עם החזרה לאחור: זמן המעבר, שיעור הרשומות שלא הומרו אוטומטית, התנהגות השירות על נתונים מעורבים.
  5. הפעלה הדרגתית של כללים. מעבירים בדיקות ממצב רך לקפדני לפי אוספים, החל מהאוספים בעלי העומס הנמוך. שליטה — שיעור הרשומות שעוברות בדיקה ותדירות שגיאות קריאה בכל שלב.
  6. מסירת תקנון. מוסרים מסמך: איך מוסיפים שדה, מה נחשב לשינוי שובר, איך מעצבים מעבר דו-שלבי, מי מפעיל החזרה לאחור, אילו בדיקות עומדות בבנייה. מכאן והלאה הצוות משנה את הסכמה בעצמו.

מארז האספקה: כללים, תקנונים, תיעוד

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

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

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

מקרה בוחן: שינוי מבנה בלי השבתת שירות

מה היה

שירות ההזמנות שמר מסמכים במאגר NoSQL MongoDB בלי שדה גרסת סכמה: האפליקציה קראה אותם ישירות וציפתה בדיוק לאותה קבוצת שדות שהיא כתבה פעם. ברגע שהוסיפו למבנה בלוק מקונן של כתובת משלוח, חלק מהמטפלים התחילו ליפול בפירוק — שיעור כשלי קריאת המסמכים עלה ל-4–6% מהבקשות.

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

מה שינו

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

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

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

מה לעשות אם המאגר כבר בפרודקשן עם גרסאות שונות

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

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

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

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

אימות, החזרות לאחור ותאימות: שאלות נפוצות

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

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

מה יקרה למסמכים ישנים במסד?

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

אפשר להחזיר שינוי מבנה לאחור אם הנתונים כבר נכתבו חלקית בפורמט החדש?

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

היכן נגמרת האחריות של המאגר ומתחילה האחריות של האפליקציה?

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

איך לוודא תאימות גרסאות לפני פריסה?

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

נדון במשימה ונחזור עם הערכה ותוכנית

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

בתמורה נשלח מסמך ממוקד, ולא אוסף של שיטות כלליות:

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

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