การเพิ่ม Schema Migration อย่างค่อยเป็นค่อยไปด้วย Flyway
บทนำ
“ติดตั้ง Flyway แล้วแต่ไม่รู้จะใช้ยังไง”
นั่นคือความรู้สึกตอนแรกที่ใช้ Flyway
เอกสาร official แสดงตัวอย่างเริ่มต้นจาก V1__create_table.sql แต่ยากที่จะเข้าใจว่า:
- จะเริ่มยังไงเมื่อมี DB ที่มีอยู่แล้ว (dvdrental)
- ควรสร้างไฟล์ในหน่วยอะไรเมื่อเพิ่มหรือแก้ไข schema ระหว่าง development
- จะจัดการยังไงเมื่อสถานะของ production และ local ไม่ตรงกัน
บทความนี้เป็นบันทึกวิธีใช้ Flyway จริงๆ ในโปรเจกต์จริง
Flyway คืออะไร
เครื่องมือสำหรับจัดการประวัติการเปลี่ยนแปลง DB schema ด้วยไฟล์ SQL
resources/db/migration/
V1__initial_schema.sql
V2__add_customer_app_schema.sql
V3__add_taste_tags_to_film.sql
- รัน SQL ที่ยังไม่ได้ apply ตามลำดับเมื่อ app เริ่มต้น
- SQL ที่ apply แล้วจะถูกบันทึกใน table
flyway_schema_history - SQL ที่ apply แล้วห้ามแก้ไข (ให้เพิ่ม SQL file ใหม่แทน)
การตั้งค่า
<!-- pom.xml -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
# application.yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true # จำเป็นเมื่อ apply กับ DB ที่มีอยู่แล้ว
baseline-version: 0
การ Apply กับ DB ที่มีอยู่แล้ว (กรณี dvdrental)
dvdrental เป็น sample DB ที่มีอยู่แล้ว ฉันเพิ่ม Flyway เข้าไปเมื่อ tables ของมันมีอยู่แล้ว
ความสำคัญของ baseline-on-migrate
spring:
flyway:
baseline-on-migrate: true
baseline-version: 0
เมื่อ apply Flyway กับ DB ที่มีอยู่แล้วเป็นครั้งแรก โดยไม่มี option นี้
จะเกิดปัญหา “พยายามรัน V1 SQL เมื่อ tables มีอยู่แล้ว และล้มเหลว”
ด้วย baseline-on-migrate: true Flyway จะถือว่า DB ปัจจุบันเป็น baseline state (V0)
และ apply เฉพาะ SQL จาก V1 เป็นต้นไป
วิธีเขียน SQL เริ่มต้น
เนื่องจาก tables ที่มีอยู่แล้วอยู่นอกการจัดการของ Flyway V1 จึงต้องเขียนแค่ delta จากสถานะปัจจุบัน
-- V1__baseline.sql (ไฟล์เปล่าหรือแค่ comment)
-- tables ที่มีอยู่ของ dvdrental อยู่นอกการจัดการของ Flyway
-- ตั้งแต่ V2 เป็นต้นไปจัดการการเพิ่มใน customer_app schema
ไฟล์ Migration ที่ใช้จริง
V2: การสร้าง customer_app Schema
-- V2__create_customer_app_schema.sql
CREATE SCHEMA IF NOT EXISTS customer_app;
CREATE TABLE customer_app.app_user (
user_id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
full_name VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
V3: การเพิ่ม Column ของ Film Tags
-- V3__add_taste_tags_to_film.sql
ALTER TABLE film ADD COLUMN IF NOT EXISTS taste_tags TEXT[];
COMMENT ON COLUMN film.taste_tags IS 'Mood tags auto-generated by LLM';
V4: การสร้าง Cart Tables
-- V4__create_cart_tables.sql
CREATE TABLE customer_app.cart (
cart_id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES customer_app.app_user(user_id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE customer_app.cart_item (
cart_item_id BIGSERIAL PRIMARY KEY,
cart_id BIGINT NOT NULL REFERENCES customer_app.cart(cart_id),
film_id INTEGER NOT NULL REFERENCES film(film_id),
added_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
กฎการตั้งชื่อไฟล์
V{version}__{description}.sql
- Version เป็นจำนวนเต็มหรือคั่นด้วยจุด (
V1,V1.1,V2ฯลฯ) - Underscore สองตัว (
__) - Description ใน snake_case
V1__baseline.sql
V2__create_customer_app_schema.sql
V3__add_taste_tags_to_film.sql
V3_1__add_index_to_film_taste_tags.sql ← เมื่อต้องการเพิ่มหลัง V3
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
① แก้ไข SQL ที่ Apply แล้วโดยไม่ตั้งใจ
ERROR: Detected failed migration to version 3
FlywayException: Validate failed:
Detected applied migration not resolved locally: 3
Flyway บันทึก checksum ของ SQL ที่ apply แล้ว
การแก้ไขทำให้เกิด error checksum ไม่ตรง
วิธีแก้: ห้ามแก้ไขเด็ดขาด ถ้าต้องแก้ไขให้สร้าง SQL version ใหม่
② สถานะ Flyway ไม่ตรงกันระหว่าง Local และ Production
ระหว่าง development อาจต้องการแก้ไข V2 SQL หลังจาก V3 apply แล้ว
ในกรณีนั้นให้ตรวจสอบสถานะใน table flyway_schema_history
SELECT * FROM flyway_schema_history ORDER BY installed_rank;
③ ไม่ต้องการใช้ Flyway ใน Tests
# application-test.yml
spring:
flyway:
enabled: false
jpa:
hibernate:
ddl-auto: create-drop # จัดการ schema ด้วย Hibernate สำหรับ tests
Development Flow
1. ต้องการ DB schema สำหรับ feature ใหม่
↓
2. สร้าง V{หมายเลขถัดไป}__{คำอธิบาย}.sql
↓
3. Start app ที่ local → Flyway auto-apply
↓
4. ตรวจสอบการทำงาน
↓
5. Commit → auto-apply ด้วยเมื่อ deploy production
workflow จะกลายเป็น “schema change = เพิ่ม SQL file”
ซึ่งหมายความว่าการเปลี่ยนแปลง DB จะปรากฏเป็น Git diffs ทำให้ review และ rollback ง่ายขึ้น
สรุป
| จุดสำคัญ | รายละเอียด |
|---|---|
| Apply กับ DB ที่มีอยู่แล้ว | ตั้งสถานะเริ่มต้นด้วย baseline-on-migrate: true |
| การตั้งชื่อไฟล์ | V{version}__{description}.sql (V เป็นตัวพิมพ์ใหญ่ __ เป็น underscore 2 ตัว) |
| ห้ามแก้ไข SQL ที่ apply แล้ว | การแก้ไขไปในเวอร์ชันใหม่ |
| หน่วยของการเพิ่ม | แยกไฟล์ต่อ operation: สร้าง table เพิ่ม column เพิ่ม index ฯลฯ |
ด้วย Flyway การเปลี่ยนแปลง DB schema จะจัดการเหมือนกับการเปลี่ยนแปลง code ได้
ความถี่ในการ login ตรงไปยัง DB เพื่อตรวจสอบ “production schema ตอนนี้เป็นยังไง” ลดลงอย่างมาก