מיגרציות מסד נתונים בפרודקשן: Expand-Contract בלי Downtime
מאת צוות מדיה דיל · 26.06.2026 · טכנולוגיה · 5 דק׳
Migrations הפיכות, CREATE INDEX CONCURRENTLY, אסטרטגיית Expand-Contract, Backfill ב-batches, Flyway ו-Prisma Migrate, replication lag ב-read replicas.
הרצת ALTER TABLE שמוסיף עמודת NOT NULL על טבלה עם עשרות מיליוני שורות בפרודקשן יכולה לנעול את הטבלה לדקות ארוכות ולהפיל את השירות כולו, גם אם ה-migration "עבד מצוין" בסביבת הפיתוח על טבלה ריקה. ניהול מיגרציות בפרודקשן הוא לא רק "להריץ סקריפט SQL" — זו משמעת של שינויים הפיכים, מתוזמנים נכון ביחס לפריסת הקוד, שלא עוצרים את המערכת בזמן שהם רצים. משמעת הזו נבנית מכמה עקרונות פשוטים שחוזרים על עצמם בכל migration רציני, בלי קשר לכלי הספציפי שמריץ אותם.
מיגרציות הפיכות כברירת מחדל
כל migration צריך שיהיה לו up ו-down מוגדרים במפורש: הפעולה שמבצעת את השינוי, והפעולה ההפוכה שמחזירה למצב הקודם. גם אם בפועל נדיר להריץ rollback על migration שכבר רץ מול נתונים אמיתיים (כי down עלול לאבד נתונים שנכתבו בינתיים), עצם הכתיבה של הפעולה ההפוכה מכריחה לחשוב על המשמעות המלאה של השינוי מראש, ונותנת מסלול מילוט אמיתי כשמשהו משתבש בסביבת staging לפני שמגיעים לפרודקשן.
הבעיה שlock מלא פותר אותה
ALTER TABLE שמוסיף עמודה, משנה טיפוס, או בונה אינדקס בדרך הרגילה נועל את הטבלה ל-writes (ולפעמים גם reads) עד שהפעולה מסתיימת. על טבלה גדולה זה יכול לקחת דקות שבהן כל בקשת כתיבה נתקעת בתור וממתינה, מה שבפרודקשן שקול להשבתה. הפתרון הוא להשתמש בפעולות concurrent כשהמסד תומך בהן — CREATE INDEX CONCURRENTLY ב-Postgres בונה את האינדקס ברקע בלי לנעול כתיבות, במחיר של זמן ריצה ארוך יותר ואפשרות שהבנייה תיכשל ותצטרך ניקוי ידני.
אסטרטגיית Expand-Contract
שינוי סכמה שדורש קוד ישן וחדש לרוץ בו זמנית (deploy הדרגתי, blue-green) לא יכול להיות אטומי — הפריסה עצמה לוקחת זמן. Expand-Contract פותר את זה בשלושה שלבים נפרדים: שלב Expand מוסיף את המבנה החדש בלי למחוק את הישן (עמודה חדשה לצד הישנה, עדיין nullable), שלב מעבר שבו הקוד כותב לשניהם וקורא מהחדש, ושלב Contract שמסיר את המבנה הישן רק אחרי שכל instance של הקוד עודכן. כל שלב הוא deploy נפרד ובר-ביטול, כך שאף פעם אין רגע שבו הסכמה לא תואמת לקוד שרץ, ואם משהו משתבש באמצע אפשר לעצור בין שלב לשלב בלי לחייב שינוי סכמה נוסף כדי לחזור למצב יציב.
Backfill בלי לנעול את הטבלה
מילוי עמודה חדשה בנתונים היסטוריים חייב לרוץ ב-batches קטנים עם sleep בין ריצה לריצה, לא כ-UPDATE יחיד על כל הטבלה — UPDATE גדול פותח טרנזקציה ארוכה שמנפחת את ה-WAL, נועלת שורות למשך זמן ארוך, ומקשה על VACUUM לפנות מקום. סקריפט backfill שרץ ב-chunks של אלפי שורות עם המתנה קצרה בין chunk ל-chunk משאיר לטרנזקציות אחרות מקום לרוץ, ואפשר לעצור אותו ולחדש אם צריך.
כלים: Flyway, Prisma Migrate ואחרים
הכלי בפועל פחות קריטי מהמשמעת, אבל Flyway ו-Prisma Migrate (וכלים דומים כמו Alembic ו-Liquibase) פותרים בעיה משותפת: לשמור היסטוריית migrations ממוספרת שרצה בסדר קבוע על כל סביבה, לזהות אילו migrations כבר רצו על כל מסד נתונים, ולמנוע הרצה כפולה. הם גם מייצרים תיעוד אוטומטי של אבולוציית הסכמה — מי שמצטרף לפרויקט יכול לקרוא את סדר ה-migrations ולהבין איך המבנה הגיע למצבו הנוכחי, בלי לנחש מה קרה בפרודקשן.
שילוב עם Read Replicas ו-Partitioning
בארכיטקטורה עם read replicas, migration שמשנה סכמה חייב להביא בחשבון replication lag — קוד חדש שרץ מיד אחרי ה-migration עלול לקרוא מ-replica שעוד לא קיבל את השינוי. באופן דומה, migration על טבלה שעברה partitioning צריך לרוץ בנפרד על כל partition או להשתמש בתמיכה native שהמסד מציע לשינוי סכמה ברמת הטבלה האב.
מתכננים שינוי סכמה בפרודקשן בלי downtime? נשמח לעזור לכם בוואטסאפ.
תגיות: Database Migrations · Expand Contract · Flyway · Prisma Migrate · Zero Downtime · Schema Change