הגירה של סכמת מסד נתונים ללא זמן השבתה: תבנית Blue-Green
הגורם השכיח ביותר לזמן השבתה במהלך פריסות הוא שינויי סכמה. פעולת ALTER TABLE על טבלה עם 200 מיליון שורות ב-PostgreSQL מחזיקה AccessExclusiveLock למשך מספר דקות. במהלך זמן זה, שאילתות כתיבה מצטברות בתור, מאגר החיבורים מתמלא, והיישום מחזיר שגיאות 500. עבור מסחר אלקטרוני בעומס גבוה, זמן השבתה כזה יכול לעלות מיליונים. אנו מתמחים בהגירות ללא זמן השבתה ומשתמשים ב-Expand-Contract—התבנית האמינה היחידה שהוכחה על טבלאות הגדולות מ-1 TB. היא מאפשרת שינויי סכמה ללא עצירת היישום. במסחר אלקטרוני, כל דקת השבתה יכולה לעלות עד $5,000, ולכן קריטי להימנע מנעילות.
הגירת מסד נתונים Blue-green אינה שני מסדי נתונים נפרדים עם סנכרון. במציאות, זה אומר הרחבת הסכמה כך ששתי גרסאות היישום—הישנה והחדשה—יכולות לעבוד במקביל, תוך קריאה ממסד נתונים יחיד. אנו מציעים מחזור מלא: ביקורת, תכנון, יישום ובדיקות. קבלו ייעוץ על הגירה ללא זמן השבתה — צרו קשר כדי להעריך את הפרויקט שלכם.
למה Expand-Contract היא התבנית האמינה היחידה
העיקרון הבסיסי של הגירת סכמה ללא זמן השבתה נקרא Expand-Contract (או Parallel Change). הוא מורכב משלושה שלבים, שכל אחד מהם נפרס בנפרד.
שלב Expand – הרחבת הסכמה מבלי לשבור קוד ישן:
-- Добавляем новый столбец как nullable (!)
ALTER TABLE orders ADD COLUMN payment_method VARCHAR(64);
-- Или переименование через добавление нового столбца
ALTER TABLE users ADD COLUMN first_name VARCHAR(255);
ALTER TABLE users ADD COLUMN last_name VARCHAR(255);
-- Индекс создаём CONCURRENTLY — без блокировки таблицы
CREATE INDEX CONCURRENTLY idx_orders_payment_method ON orders(payment_method);לאחר מכן, פרסו גרסת יישום v2 שכותבת גם לעמודות הישנות וגם לחדשות במקביל. הגרסה הישנה v1 ממשיכה לעבוד—היא לא יודעת על העמודות החדשות, אבל זה בסדר כי הן nullable.
שלב Migration – מילוי נתונים ברקע:
-- Заполняем новые столбцы батчами, не блокируя таблицу
DO $$
DECLARE
batch_size INT := 10000;
last_id BIGINT := 0;
max_id BIGINT;
BEGIN
SELECT MAX(id) INTO max_id FROM users;
WHILE last_id < max_id LOOP
UPDATE users
SET first_name = split_part(name, ' ', 1),
last_name = trim(substr(name, strpos(name, ' ')))
WHERE id > last_id AND id <= last_id + batch_size AND first_name IS NULL;
last_id := last_id + batch_size;
PERFORM pg_sleep(0.1); -- небольшая пауза между батчами
END LOOP;
END $$;שלב Contract – הסרת הישן לאחר מעבר מלא:
-- Только после того, как v1 полностью выведена из эксплуатации
ALTER TABLE users DROP COLUMN name;
ALTER TABLE orders ALTER COLUMN payment_method SET NOT NULL; איפה זה נשבר: טעויות נפוצות ופתרונות
הטעות השכיחה ביותר היא הוספת -- Добавляем новый столбец как nullable (!) ALTER TABLE orders ADD COLUMN payment_method VARCHAR(64); -- Или переименование через добавление нового столбца ALTER TABLE users ADD COLUMN first_name VARCHAR(255); ALTER TABLE users ADD COLUMN last_name VARCHAR(255); -- Индекс создаём CONCURRENTLY — без блокировки таблицы CREATE INDEX CONCURRENTLY idx_orders_payment_method ON orders(payment_method); ללא -- Заполняем новые столбцы батчами, не блокируя таблицу DO $$ DECLARE batch_size INT := 10000; last_id BIGINT := 0; max_id BIGINT; BEGIN SELECT MAX(id) INTO max_id FROM users; WHILE last_id < max_id LOOP UPDATE users SET first_name = split_part(name, ' ', 1), last_name = trim(substr(name, strpos(name, ' '))) WHERE id > last_id AND id <= last_id + batch_size AND first_name IS NULL; last_id := last_id + batch_size; PERFORM pg_sleep(0.1); -- небольшая пауза между батчами END LOOP; END $$; בשלב Expand. PostgreSQL לפני גרסה 11 היה כותב מחדש את כל הטבלה. עם PostgreSQL 11+, -- Только после того, как v1 полностью выведена из эксплуатации ALTER TABLE users DROP COLUMN name; ALTER TABLE orders ALTER COLUMN payment_method SET NOT NULL; עובד באופן מיידי (הערך נשמר בקטלוג המערכת), אבל רק אם DEFAULT הוא סטטי, לא קריאת פונקציה.
המלכוד השני הוא אינדקסים ייחודיים. NOT NULL יכול להיכשל עם שגיאת DEFAULT אם הנתונים כבר מכילים כפילויות. אז האינדקס נשאר במצב ADD COLUMN NOT NULL DEFAULT 'value' – יש למחוק אותו, לנקות את הנתונים, ולנסות ליצור אותו שוב.
הבעיה השלישית היא מפתחות זרים. CREATE UNIQUE INDEX CONCURRENTLY ב-PostgreSQL לוקח duplicate key על שתי הטבלאות וסורק לחלוטין את טבלת הילדים. על טבלאות גדולות, זה לוקח דקות. הפתרון הוא ליצור FK עם INVALID, ולאחר מכן לאמת בנפרד:
-- Шаг 1: добавить FK без проверки существующих строк (быстро)
ALTER TABLE orders ADD CONSTRAINT fk_orders_users FOREIGN KEY (user_id) REFERENCES users(id) NOT VALID;
-- Шаг 2: валидация в фоне (не блокирует DML, только берёт ShareUpdateExclusiveLock)
ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_users; איך להימנע מנעילות בעת הוספת מפתחות זרים
השתמשו בגישה דו-שלבית: תחילה ALTER TABLE ADD CONSTRAINT FOREIGN KEY, ולאחר מכן אימות נפרד. זה מקטין את זמן הנעילה מדקות למילישניות. האימות סורק את הטבלה אך אינו מפריע לפעולות רגילות – רק ShareRowExclusiveLock קצר לעדכון המילון.
אוטומציה של בדיקות ב-CI/CD
בצנרת לפני הפריסה, הריצו ניתוח סטטי של הגירות. הכלי squawk (עבור PostgreSQL) בודק קבצי SQL עבור תבניות מסוכנות:
---
# .github/workflows/db-migration-check.yml
- name: Check migrations safety
run: |
squawk db/migrations/*.sql \
--exclude=prefer-text-field \
--pg-version=14.0
Squawk מזהה NOT VALID, -- Шаг 1: добавить FK без проверки существующих строк (быстро) ALTER TABLE orders ADD CONSTRAINT fk_orders_users FOREIGN KEY (user_id) REFERENCES users(id) NOT VALID; -- Шаг 2: валидация в фоне (не блокирует DML, только берёт ShareUpdateExclusiveLock) ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_users; , NOT VALID, ופעולות אחרות הדורשות ShareUpdateExclusiveLock. בהשוואה לבדיקה ידנית, Squawk מוצא 90% מהבעיות פי 10 מהר יותר.
לבדיקות אוטומטיות של ההגירה עצמה, הקימו עותק של מסד הנתונים היצרני (באמצעות pg_dump/restore או שכפול לוגי) והריצו את ההגירה שם לפני היישום בייצור.
רשימת בדיקה להגירה בטוחה
- [ ] בדקו שאין NOT NULL ללא DEFAULT ב-Expand
- [ ] ודאו שאינדקסים ייחודיים נוצרים CONCURRENTLY ואין כפילויות
- [ ] עבור FK השתמשו ב-NOT VALID + אימות נפרד
- [ ] הריצו squawk לפני הפריסה
- [ ] בדקו על עותק של הייצור
- [ ] הכינו סקריפטים לחזרה לאחור
| בדיקה | למה זה מסוכן | איך squawk מזהה |
|---|---|---|
| ADD COLUMN NOT NULL ללא DEFAULT | נועל את הטבלה לכל משך הזמן | ✅ |
| DROP INDEX ללא CONCURRENTLY | נועל את הטבלה | ✅ |
| ALTER TABLE RENAME | דורש AccessExclusiveLock | ✅ |
| ADD FOREIGN KEY ללא NOT VALID | סורק את הטבלה תחת ShareRowExclusiveLock | ⚠️ (דורש ניתוח נוסף) |
מה העבודה שלנו כוללת
בהזמנת הגירת blue-green, אנו מספקים:
- ביקורת סכמה נוכחית – זיהוי צווארי בקבוק, תלויות, גדלי טבלאות.
- תוכנית הגירה – פירוק לשלבי Expand-Contract עם הערכות זמן.
- יישום ההגירה – כתיבת סקריפטים SQL, פרוצדורות מילוי, סקריפטים לחזרה לאחור.
- בדיקות – הרצה בסביבת staging עם נתונים בקנה מידה של ייצור.
- תיעוד – תיאור התהליך, runbook למפעילים, תבנית postmortem.
- תמיכה לאחר פריסה – ניטור שגיאות, מעקב אחר מדדי מסד נתונים.
תהליך היישום
| שלב | תוכן | משך |
|---|---|---|
| ביקורת סכמה | ניתוח טבלאות נוכחיות, גדלים, סוגי שינויים | יום אחד |
| תכנון | פירוק שינויים לשלבי Expand-Contract | 1-2 ימים |
| יישום | כתיבת הגירות, סקריפטים למילוי | 2-5 ימים |
| בדיקות | הרצה ב-staging עם נפח נתונים של ייצור | 1-2 ימים |
| פריסה | פריסה מדורגת לפי שלבים | לפי לוח זמנים |
עבור שינויים פשוטים (הוספת עמודה nullable, אינדקס חדש) – 2-3 ימים כולל בדיקות. עבור מבנה מחדש מורכב של טבלאות עם עשרות מיליוני שורות – עד 2-3 שבועות כולל מילוי וניטור.
אנו נעריך את הפרויקט שלכם תוך יום עסקים אחד. צרו קשר – נמצא תוכנית הגירה אופטימלית עם ערובה ללא זמן השבתה. הניסיון שלנו: עשרות הגירות PostgreSQL מוצלחות בפרויקטים בעומס גבוה. פנו אלינו כדי לדון במשימה שלכם.
לפי תיעוד PostgreSQL, ALTER TABLE עם NOT VALID ממזער נעילות (ראו ALTER TABLE בתיעוד PostgreSQL).







