Tự động hóa Migration Database Schema với Flyway: Thiết lập cho PostgreSQL, MySQL và CI/CD

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Bối cảnh & Lý do: Từ bỏ thói quen hotfix SQL giữa đêm

Đầu năm ngoái, quy trình release của chúng tôi gặp phải một điểm nghẽn nghiêm trọng: cập nhật schema thủ công. Các lập trình viên thường dán các đoạn script migration vào thread trên Slack, và kỹ sư trực on-call phải chạy chúng bằng tay trên các node production trong khung giờ bảo trì. Khi hệ thống mở rộng lên khoảng 35 microservice và 120 bảng cơ sở dữ liệu, sai sót từ con người là điều không thể tránh khỏi. Chỉ cần một lập trình viên quên tạo index hoặc thực thi bản vá foreign key sai thứ tự là sự cố đã xảy ra. Điều đó đã dẫn đến ba đợt rollback nghiêm trọng chỉ trong một quý.

Chúng tôi cần trạng thái database phải được lưu trữ và quản lý phiên bản cùng với mã nguồn trong Git. Quan trọng hơn, mọi môi trường—từ Docker ở local cho đến các cụm staging và production đa node—đều phải được cập nhật một cách nhất quán, có thể dự đoán được (deterministic) ngay khi merge code. Yêu cầu đó đã dẫn chúng tôi đến với Flyway.

Flyway theo dõi các thay đổi đã áp dụng bằng các script SQL thuần có quy ước đặt tên cụ thể. Khi chạy trên một database, nó sẽ kiểm tra bảng metadata có tên là flyway_schema_history. Bảng này lưu trữ phiên bản script, trạng thái thực thi, thời gian chạy tính bằng mili-giây và mã checksum CRC32. Nếu ai đó chỉnh sửa một script migration đã được áp dụng trước đó trong Git, Flyway sẽ phát hiện sự không khớp checksum và dừng quá trình build ngay lập tức trước khi kịp áp dụng các thay đổi mới.

Cài đặt: Cho môi trường Local và Container Runtime

Bạn có thể chạy Flyway thông qua file binary độc lập, plugin của các công cụ build (Maven, Gradle), hoặc Docker image chính thức. Đối với môi trường phát triển local, các trình quản lý gói giúp bạn cài đặt chỉ trong vài giây. Còn trên các CI/CD runner tự động, container image mang lại tính nhất quán cao mà không bị phụ thuộc vào driver cài sẵn ở local.

Cách 1: Cài đặt CLI trực tiếp

Đối với máy phát triển chạy macOS và Linux, bạn có thể cài đặt binary độc lập trực tiếp:

# Cài đặt trên macOS bằng Homebrew
brew install flyway

# Tải thủ công CLI cho Linux x64
wget -qO- https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.10.0/flyway-commandline-10.10.0-linux-x64.tar.gz | tar xvz
sudo ln -s $(pwd)/flyway-10.10.0/flyway /usr/local/bin/flyway

# Kiểm tra cài đặt
flyway -v

Cách 2: Chạy thông qua Docker

Docker image chính thức đã tích hợp sẵn các JDBC driver được cấu hình trước cho PostgreSQL, MySQL, MariaDB và Oracle. Bạn chỉ cần mount trực tiếp thư mục migration và thư mục cấu hình:

docker run --rm \
  -v $(pwd)/sql/migrations:/flyway/sql \
  -v $(pwd)/conf:/flyway/conf \
  flyway/flyway:10.10.0 info

Cấu hình: Quy ước đặt tên script, thiết lập đa database và Baseline

Flyway dựa vào các quy ước đặt tên nghiêm ngặt để xác định thứ tự thực thi và hành vi của script.

Quy ước đặt tên file migration

Flyway quét thư mục migration và phân loại các file dựa trên tiền tố của chúng:

  • Versioned Migrations (V<Version>__<Description>.sql): Được áp dụng một lần duy nhất theo thứ tự tăng dần nghiêm ngặt (ví dụ: V1.0__create_users_table.sql, V1.1__add_user_profile.sql).
  • Repeatable Migrations (R__<Description>.sql): Được áp dụng sau khi tất cả các versioned migration hoàn tất. Chúng sẽ tự động chạy lại mỗi khi nội dung file và mã checksum thay đổi (rất phù hợp cho view, stored procedure và trigger).
  • Undo Migrations (U<Version>__<Description>.sql): Đảo ngược các thay đổi cụ thể theo phiên bản. Lưu ý rằng tính năng undo yêu cầu giấy phép Flyway Teams hoặc Enterprise.

Cấu hình kết nối cơ sở dữ liệu

Lưu trữ các tham số kết nối trong file flyway.conf hoặc truyền qua các biến môi trường (FLYWAY_URL, FLYWAY_USER, FLYWAY_PASSWORD). Dưới đây là một file cấu hình mẫu tiêu chuẩn:

# Cấu hình PostgreSQL
flyway.url=jdbc:postgresql://localhost:5432/app_production
flyway.user=postgres_user
flyway.password=secure_password
flyway.schemas=public
flyway.locations=filesystem:./sql/migrations

# Thiết lập baseline cho cơ sở dữ liệu hiện có
flyway.baselineOnMigrate=true
flyway.baselineVersion=0.0
flyway.baselineDescription=Baseline ban đầu

# Ví dụ cấu hình MySQL
# flyway.url=jdbc:mysql://localhost:3306/app_production?useSSL=false&allowPublicKeyRetrieval=true
# flyway.user=mysql_user
# flyway.password=secure_password
# flyway.schemas=app_production

Viết các script migration

Hãy giữ các thay đổi DDL mang tính nguyên tử (atomic) và tuân thủ dialect riêng của từng database. Dưới đây là schema PostgreSQL ban đầu trong sql/migrations/V1.0__init_schema.sql:

-- Bảng users ban đầu cho PostgreSQL
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);

Tiếp theo là bản cập nhật bổ sung trong sql/migrations/V1.1__add_user_profile.sql:

ALTER TABLE users ADD COLUMN full_name VARCHAR(120);
ALTER TABLE users ADD COLUMN metadata JSONB;

Trong quá trình migration ban đầu và chuẩn bị script seed dữ liệu, tôi thường gặp trường hợp dữ liệu người dùng mẫu được cung cấp dưới dạng hàng loạt. Khi cần chuyển đổi nhanh từ CSV sang JSON để import dữ liệu, tôi sử dụng toolcraft.app/vi/tools/data/csv-to-json — công cụ này chạy trực tiếp trên trình duyệt nên dữ liệu không hề rời khỏi máy của bạn. Nó giúp việc chuẩn bị các payload JSON cho seed test migration trở nên rất nhẹ nhàng.

Tự động hóa Migration trong GitHub Actions

Tuyệt đối không chạy migration schema từ các hook khởi động ứng dụng (startup hooks) trong môi trường có nhiều replica. Khi ba pod Kubernetes khởi động cùng lúc và cố gắng chạy migration đồng thời, hiện tượng race condition có thể xảy ra. Thay vào đó, hãy chạy migration như một bước pipeline riêng biệt trước khi deploy mã nguồn ứng dụng:

name: Triển khai Database Migration

on:
  push:
    branches:
      - main
    paths:
      - 'sql/migrations/**'

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout mã nguồn
        uses: actions/checkout@v4

      - name: Chạy Flyway Migration
        uses: docker://flyway/flyway:10.10.0
        with:
          args: >-
            -url="${{ secrets.DB_JDBC_URL }}"
            -user="${{ secrets.DB_USER }}"
            -password="${{ secrets.DB_PASSWORD }}"
            -locations="filesystem:./sql/migrations"
            migrate

Kiểm tra, Khắc phục sự cố và Phục hồi

Các đợt migration database đòi hỏi phải được kiểm tra chủ động trong các pull request CI và cần có quy trình phục hồi rõ ràng khi xảy ra lỗi cú pháp.

1. Kiểm tra trạng thái Migration

Kiểm tra xem migration nào đã được áp dụng và migration nào đang chờ thực thi:

flyway info

CLI sẽ xuất ra bảng log như sau:

+-----------+---------+--------------------+------+---------------------+---------+
| Category  | Version | Description        | Type | Installed On        | State   |
+-----------+---------+--------------------+------+---------------------+---------+
| Versioned | 1.0     | init schema        | SQL  | 2026-03-15 10:20:00 | Success |
| Versioned | 1.1     | add user profile   | SQL  | 2026-04-02 08:14:22 | Success |
| Versioned | 1.2     | add audit triggers | SQL  |                     | Pending |
+-----------+---------+--------------------+------+---------------------+---------+

2. Phát hiện chỉnh sửa file ngoài ý muốn với flyway validate

Hãy thêm flyway validate vào bước kiểm tra của mọi pull request. Nếu một lập trình viên chỉnh sửa file migration cũ thay vì tạo phiên bản mới, Flyway sẽ từ chối bản build trước khi nó kịp tác động đến bất kỳ database staging hay production nào:

flyway validate

Khi quá trình xác thực phát hiện sự sai lệch về checksum, bạn sẽ thấy kết quả đầu ra như sau:

ERROR: Validate failed: Quá trình kiểm tra migration thất bại
Sai lệch checksum đối với migration phiên bản 1.1
- Đã áp dụng trên database : -1829384912
- Phân giải tại local     : 1928374615

3. Khắc phục sự cố khi Migration thất bại

Cách bạn phục hồi sau một đợt migration thất bại phụ thuộc vào hệ quản trị cơ sở dữ liệu bạn đang dùng. PostgreSQL bọc các câu lệnh DDL trong transaction, tự động rollback các thay đổi dở dang nếu xảy ra lỗi. Tuy nhiên, MySQL không hỗ trợ DDL theo transaction. Nếu một script bị lỗi giữa chừng trên MySQL, các câu lệnh trước đó trong file vẫn sẽ được commit trong khi Flyway đánh dấu trạng thái migration là Failed.

Để khắc phục trạng thái thất bại này:

  1. Kiểm tra database thủ công và rollback bất kỳ câu lệnh DDL nào chưa commit hoặc mới chỉ được áp dụng một phần.
  2. Khắc phục nguyên nhân gốc rễ trong script migration.
  3. Chạy flyway repair để xóa bản ghi thất bại khỏi bảng flyway_schema_history và tính toán lại checksum:
flyway repair

Chạy lại flyway migrate sau khi đã hoàn tất sửa lỗi. Với tính năng xác thực tự động trong CI cùng các trigger pipeline rõ ràng, việc đồng bộ hóa schema chỉ mất chưa đầy 5 giây cho mỗi lần deployment, giúp duy trì trạng thái database luôn nhất quán trên toàn bộ hạ tầng của bạn.

Share: