Tech Blog

การเพิ่ม Schema Migration อย่างค่อยเป็นค่อยไปด้วย Flyway

by y104
Flyway Spring Boot PostgreSQL Java

บทนำ

“ติดตั้ง 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 ตอนนี้เป็นยังไง” ลดลงอย่างมาก


แผนที่บทความสำหรับซีรีส์นี้

สร้างแอป DVD Rental สำหรับผู้ใช้ปลายทาง — โครงสร้าง Vue 3 + Spring Boot คู่กับแอปผู้ดูแลระบบ พร้อมแผนที่บทความ

ส่งข้อความได้ตามสบาย

กรุณาส่งข้อความ หากมีคำปรึกษาด้านเทคนิค ความคิดเห็น หรือคำถาม