שלום, חבר!
היום, בואו נדבר על מהן ההעברות במסד נתונים ועל חשיבותן. בעולם של היום, זה לא מפתיע שכל שינוי במסד הנתונים צריך להתבצע בזהירות ובהתאם לתהליך ספציפי. מומלץ ששלבים אלו ייכללו בצינור CI/CD שלנו כך שהכול ירוץ באופן אוטומטי.
זהו סדר היום:
- מה הבעיה?
- איך אנחנו מתקן אותה?
- דוגמה פשוטה
- דוגמה מורכבת יותר
- המלצות
- תוצאות
- מסקנות
מה הבעיה?
אם הקבוצה שלך לא עסקה עדיין בהעברות במסד הנתונים ואתה לא בטוח למה הן נחוצות, בוא נסדר את זה. אם אתה כבר מכיר את היסודות, תרגיש חופשי לדלג קדימה.
האתגר העיקרי
כאשר אנו מבצעים שינויים "מתוכננים" ו"חלקים" במסד הנתונים, עלינו לשמור על זמינות השירות ולעמוד בדרישות SLA (כך שהמשתמשים לא יסבלו מזמן ריחוק או עיכוב). דמיינו שאתם רוצים לשנות סוג עמוד בטבלה עם 5 מיליון משתמשים. אם תעשו זאת "לפנים" (למשל, פשוט רץ ALTER TABLE
בלי הכנה מראש), הטבלה עשויה להינעל לזמן משמעותי — והמשתמשים שלך יישארו בלי שירות.
כדי למנוע כאבי ראש כאלה, עקוב אחרי כללי שתי הכללים:
- החלק את ההעברות בדרך שלא תנעל את הטבלה (או לפחות תמזער נעילות).
- אם יש צורך לשנות את סוג העמוד, נוח יותר לפעמים ליצור עמוד חדש עם הסוג הנכון תחילה ואז למחוק את הישן לאחר מכן.
בעיה נוספת: בקרת גרסאות וחזרות אחורה
לפעמים יש צורך לבצע חזרה לגרסה קודמת של המיגרציה.
לעשות זאת באופן ידני – להיכנס לבסיס הנתונים הייצורי ולשחק עם הנתונים – הינו לא רק מסוכן, אלא גם כנראה בלתי אפשרי אם אין לך גישה ישירה. זהו המקום שבו כלי מיגרציה מיוחדים מועילים. הם מאפשרים לך ליישם שינויים בצורה נקייה ולשחזר אותם כאשר נדרש.
כיצד נתקן זאת? להשתמש בכלים הנכונים
לכל שפה וסביבה יש כלים מיוחדים למיגרציה:
- ל-Java, Liquibase או Flyway הם נפוצים.
- ל-Go, בחירה פופולרית היא goose (האחד שנבחן כאן).
- וכן הלאה.
Goose: מה זה ולמה זה מועיל
Goose הוא כלי קל משקל בשפת Go שעוזר לך לנהל מיגרציות באופן אוטומטי. הוא מציע:
- פשטות. תלות מינימלית ומבנה קובץ שקוף למיגרציות.
- גמישות. תמיכה במגוון נהגי DB (PostgreSQL, MySQL, SQLite, וכו').
- גמישות. ניתן לכתוב מיגרציות ב-SQL או בקוד Go.
התקנת Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
איך זה עובד: מבנה ההגירה
כברירת מחדל, Goose מחפש קבצי הגירה ב־db/migrations
. כל הגירה עוקבת אחר המבנה הזה:
NNN_migration_name.(sql|go)
NNN
הוא מספר ההגירה (למשל,001
,002
, וכו').- אחרי זה, ניתן להשתמש בשם תיאורי כלשהו, לדוגמה
init_schema
. - הסיומת יכולה להיות
.sql
או.go
.
דוגמה להגירת SQL
קובץ: 001_init_schema.sql
:
-- +goose Up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE users;
הדוגמה הראשונה שלנו
שינוי סוג עמוד (ממחרוזת למספר שלם)
נניח שיש לנו טבלת users
עם עמודה age
מסוג VARCHAR(255)
. כעת רוצים לשנות אותה ל־INTEGER
. הנה כיצד תראה ההגירה (קובץ 005_change_column_type.sql
):
-- +goose Up
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING (age::INTEGER);
-- +goose Down
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(255) USING (age::TEXT);
מה קורה כאן:
-
הגירה למעלה
- אנו משנים את העמודה
age
ל־INTEGER
. סעיף ה־USING (age::INTEGER)
אומר ל־PostgreSQL איך להמיר את הנתונים הקיימים לסוג החדש. - שימו לב שההגירה תיכשל אם ישנם נתונים ב־
age
שאינם מספריים. במקרה כזה, תצטרכו להשתמש באסטרטגיה מורכבת יותר (ראו למטה).
- אנו משנים את העמודה
-
מעבר אחורה
- אם נגלול אחורה, נחזיר את
age
לVARCHAR(255)
. - נשתמש שוב ב
USING (age::TEXT)
כדי להמיר מINTEGER
חזרה לטקסט.
- אם נגלול אחורה, נחזיר את
המקרים השניים והמורכבים: מעברים מרובי שלבים
אם העמודה age
עשויה להכיל נתונים לא מסודרים (לא רק מספרים), זה בטוח יותר לעשות זאת בכמה שלבים:
- הוסיפו עמודה חדשה (
age_int
) מסוגINTEGER
. - העתיקו נתונים תקפים לעמודה החדשה, התמודדו עם או הסירו רשומות לא תקפות.
- מחקו את העמודה הישנה.
-- +goose Up
-- Step 1: Add a new column
ALTER TABLE users ADD COLUMN age_int INTEGER;
-- Step 2: Try to move data over
UPDATE users
SET age_int = CASE
WHEN age ~ '^[0-9]+$' THEN age::INTEGER
ELSE NULL
END;
-- (optional) remove rows where data couldn’t be converted
-- DELETE FROM users WHERE age_int IS NULL;
-- Step 3: Drop the old column
ALTER TABLE users DROP COLUMN age;
-- +goose Down
-- Step 1: Recreate the old column
ALTER TABLE users ADD COLUMN age VARCHAR(255);
-- Step 2: Copy data back
UPDATE users
SET age = age_int::TEXT;
-- Step 3: Drop the new column
ALTER TABLE users DROP COLUMN age_int;
כדי לאפשר חזרה נכונה, הסעיף Down
פשוט משקף את הפעולות הפוכות.
אוטומציה היא המפתח
כדי לחסוך בזמן, זה באמת נוח להוסיף פקודות מעבר לקובץ Makefile (או כל מערכת בנייה אחרת). להלן דוגמת Makefile עם הפקודות העיקריות של Goose עבור PostgreSQL.
נניח:
- ה-DSN עבור בסיס הנתונים הוא
postgres://user:password@localhost:5432/dbname?sslmode=disable
. - קבצי ההגירה נמצאים ב־
db/migrations
.
# File: Makefile
DB_DSN = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
MIGRATIONS_DIR = db/migrations
# Install Goose (run once)
install-goose:
go install github.com/pressly/goose/v3/cmd/goose@latest
# Create a new SQL migration file
new-migration:
ifndef NAME
$(error Usage: make new-migration NAME=your_migration_name)
endif
goose -dir $(MIGRATIONS_DIR) create $(NAME) sql
# Apply all pending migrations
migrate-up:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) up
# Roll back the last migration
migrate-down:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) down
# Roll back all migrations (be careful in production!)
migrate-reset:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) reset
# Check migration status
migrate-status:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) status
איך להשתמש בזה?
1. צור הגירה חדשה (קובץ SQL). זה יוצר קובץ בשם db/migrations/002_add_orders_table.sql
.
make new-migration NAME=add_orders_table
2. החל את כל ההגירות. Goose תיצור טבלת schema_migrations
במסד הנתונים שלך (אם היא עדיין לא קיימת) ותחליף את כל ההגירות החדשות בסדר עולה.
make migrate-up
3. החזר את ההגרה האחרונה. פשוט לשכך את האחרונה.
make migrate-down
4. החזר את כל ההגירות (השתמש בזהירות בסביבת הפרודקשן). איפוס מלא.
make migrate-reset
5. בדוק את מצב הגירה.
make migrate-status
דוגמה לפלט:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
סיכום
באמצעות כלים להגרה ו־Makefile, אנו יכולים:
- להגביל גישה ישירה למסד הנתונים של הפרודקשן, לבצע שינויים רק דרך הגרות.
- לעקוב בקלות אחר גרסאות מסד הנתונים ולשכך אותן אם משהו משתבש.
- לשמור על היסטוריה יחידה ועקבית של שינויים במסד הנתונים.
- לבצע הגירות "חלקיות" שלא יפגעו בסביבת הפרודקשן הפועלת בעולם של מיקרושירות.
- להרוויח אימות נוסף — כל שינוי יעבור דרך תהליך PR וסקירת קוד (בהנחה שיש לך את ההגדרות הללו במקום).
יתרון נוסף הוא שקל לשלב את כל הפקודות הללו בצינור CI/CD שלך. וזכור — אבטחה מעל הכול.
לדוגמה:
jobs
migrate
runs-on ubuntu-latest
steps
name Install Goose
run
make install-goose
name Run database migrations
env
DB_DSN $ secrets.DATABASE_URL
run
make migrate-up
מסכם וטיפים
הרעיונות המרכזיים כל כך פשוטים:
- שמור על המיגרציות שלך קטנות ותכופות. קל יותר לבדוק, לבחון ולהחזיר אם יש צורך.
- השתמש באותו כלי בכל הסביבות כך שהפיתוח, הבמה והפרודקשן יהיו מסונכרנים.
- שלב את המיגרציות ב-CI/CD כך שלא תהיה תלוי באדם אחד שיריץ אותן ידנית.
כך תהיה לך תהליך אמין ומבוקר לשינוי מבנה בסיס הנתונים שלך — אחד שלא שובר את הפרודקשן ומאפשר לך להגיב במהירות אם משהו משתבש.
בהצלחה עם המיגרציות שלך!
תודה על הקריאה!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service