Quản lý Database Schema theo hướng Declarative với Atlas: Tự động hóa Migration cho PostgreSQL và MySQL

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

Vấn đề: Tại sao Imperative Migration gặp trục trặc ở quy mô lớn

Hầu hết các đội ngũ kỹ thuật đều triển khai mã nguồn ứng dụng hàng chục lần mỗi ngày nhờ các pipeline CI/CD tự động. Thế nhưng, các thay đổi cơ sở dữ liệu lại thường bị tụt hậu, mắc kẹt trong những quy trình thủ công dễ xảy ra sai sót. Thông thường, các nhóm dựa vào những công cụ migration dạng imperative như Flyway hoặc Liquibase. Mỗi lần điều chỉnh schema, kỹ sư đều phải tự tay viết một script gia tăng (incremental script)—chẳng hạn như V14__add_status_to_users.sql chứa các câu lệnh ALTER TABLE tường minh.

Mô hình này nhanh chóng bộc lộ điểm yếu khi quy mô nhóm gia tăng. Hãy tưởng tượng ba kỹ sư cùng tách nhánh từ main vào cùng một buổi chiều. Hai người trong số họ cùng đặt tiền tố V15 cho file migration của mình. Git vẫn cho phép merge sạch sẽ, nhưng pipeline triển khai sẽ gặp lỗi crash khi rollout do trùng lặp sequence.

Nghiêm trọng hơn nữa là hiện tượng schema drift (lệch schema). Trong một sự cố lúc 03:00 sáng, một kỹ sư thêm trực tiếp index vào read replica trên production để dập tắt tình trạng tăng đột biến CPU. Index khẩn cấp đó không bao giờ được đưa vào Git repository. Nhiều tháng sau, một migration mới tác động lên bảng đó thất bại hoặc hoạt động bất thường vì môi trường production đã không còn đồng nhất với môi trường local development.

Migration dạng imperative buộc bạn phải chỉ định cách thức thực hiện các bước trung gian, nhưng lại hiếm khi xác thực được trạng thái cuối cùng (what) thực tế trông như thế nào trên các môi trường.

Giải pháp: Declarative Schema với Atlas

Ngành kỹ thuật đám mây (cloud engineering) đã giải quyết thách thức này từ cả thập kỷ trước. Các công cụ như Terraform và Kubernetes đã thay thế những shell script mỏng manh bằng việc định nghĩa trạng thái mong muốn theo hướng declarative. Bạn khai báo trạng thái mong muốn trong code, và reconciliation engine (công cụ điều phối) sẽ tự tính toán các bước cần thiết để đạt được trạng thái đó.

Atlas mang đúng workflow này đến với các cơ sở dữ liệu quan hệ. Thay vì phải tự viết các DDL script theo kiểu thủ tục, bạn định nghĩa trạng thái mong muốn (target state) bằng SQL thuần, Atlas HCL, hoặc trực tiếp qua các ORM model (chẳng hạn như Prisma, GORM, hay SQLAlchemy). Atlas sẽ tính toán độ sai khác (diff) toán học giữa database đang hoạt động và trạng thái mong muốn của bạn, sau đó tự động sinh ra các câu lệnh migration phù hợp.

Sự khác biệt này đặc biệt quan trọng giữa các relational database engine khác nhau. PostgreSQL hỗ trợ transactional DDL, cho phép rollback schema an toàn khi gặp sự cố. Trái lại, MySQL 8.0 không đóng gói các câu lệnh DDL trong atomic transaction. Một thao tác vô tình gây table lock trên một bảng MySQL có 15 triệu dòng có thể làm gián đoạn toàn bộ lưu lượng truy cập trong nhiều phút. Atlas thấu hiểu sẵn những khác biệt cú pháp này ngay từ đầu. Công cụ cung cấp hai chế độ hoạt động riêng biệt:

  • Schema Apply (Declarative trực tiếp): Atlas kiểm tra database mục tiêu và áp dụng ngay các thay đổi (diff). Workflow này được thiết kế tối ưu cho local development, review app và các môi trường kiểm thử tạm thời (ephemeral test environment).
  • Versioned Migrations (Imperative định hướng bằng Declarative): Atlas so sánh trạng thái mong muốn với các file SQL đã commit trong Git, từ đó tạo ra các migration script tất định kèm timestamp phục vụ quá trình pull request review và pipeline CI/CD trên production.

Hướng dẫn thực hành: PostgreSQL và MySQL

1. Cài đặt

Cài đặt binary độc lập trên máy trạm hoặc CI agent của bạn:

# macOS / Linux
curl -sSf https://atlasgo.sh | sh

# Kiểm tra binary đã sẵn sàng
atlas version

2. Định nghĩa trạng thái mong muốn bằng SQL

Tạo một thư mục có tên là schema. Dù Atlas hỗ trợ HCL, các file DDL viết bằng SQL thuần vẫn hoạt động trơn tru ngay từ đầu mà không đòi hỏi cú pháp cấu hình mới. Lưu các định nghĩa sau vào file schema/schema.sql:

-- schema/schema.sql
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    full_name VARCHAR(120) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    amount NUMERIC(10, 2) NOT NULL DEFAULT 0.00,
    status VARCHAR(50) NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_orders_user_status ON orders(user_id, status);

3. Kiểm tra (Inspect) một Database hiện có

Khi làm dự án, hiếm khi chúng ta bắt đầu từ con số không. Khi chuyển một database đã vận hành sang quản lý theo hướng declarative, hãy trích xuất trạng thái hiện tại bằng lệnh schema inspect:

# Dành cho PostgreSQL
atlas schema inspect \
  -u "postgres://postgres:secret@localhost:5432/store_prod?sslmode=disable" \
  > current_schema.sql

# Dành cho MySQL
atlas schema inspect \
  -u "mysql://root:secret@localhost:3306/store_prod" \
  > current_schema.sql

4. Áp dụng thay đổi trực tiếp trong Local Development

Bạn cần đồng bộ container Docker ở local với những chỉnh sửa mới nhất trong schema.sql? Hãy bỏ qua các migration script thủ công và chạy lệnh schema apply:

atlas schema apply \
  -u "postgres://postgres:secret@localhost:5432/store_dev?sslmode=disable" \
  --to "file://schema/schema.sql" \
  --dev-url "docker://postgres/16/dev"

Cờ --dev-url cung cấp cho Atlas một môi trường container tạm thời dùng một lần. Atlas sẽ nạp schema của bạn lên instance tạm thời này trước. Công cụ sẽ kiểm tra cú pháp, giải quyết các phụ thuộc giữa các bảng và mô phỏng diff một cách an toàn trước khi thực hiện thay đổi trên database mục tiêu của bạn.

5. Tạo các file Migration được quản lý phiên bản

Việc sửa đổi schema trực tiếp chỉ phù hợp với môi trường development, không dành cho production. Các môi trường staging và production tuân thủ quy chuẩn khắt khe luôn đòi hỏi các file SQL bất biến được lưu trữ trong Git để phục vụ mục đích kiểm toán (audit) và peer review. Atlas đáp ứng nhu cầu này bằng cách tự động tính toán diff cần thiết và soạn thảo migration script giúp bạn:

# Khởi tạo thư mục migration
mkdir -p migrations

# Tạo file versioned migration so sánh schema.sql với thư mục migrations/
atlas migrate diff add_orders_and_users \
  --dir "file://migrations" \
  --to "file://schema/schema.sql" \
  --dev-url "docker://postgres/16/dev"

Atlas sẽ tạo ra hai file bên trong thư mục migrations/:

  • migrations/20261102110130_add_orders_and_users.sql: Chứa chính xác các câu lệnh DDL cần thiết để cập nhật schema.
  • migrations/atlas.sum: File checksum đảm bảo tính toàn vẹn mật mã. Nếu một kỹ sư khác sửa đổi một migration script đã được merge trước đó, checksum sẽ không khớp, ngăn chặn tình trạng drift âm thầm trong pipeline của bạn.

6. Linting trước khi triển khai và thực thi trong CI/CD

Phát hiện sớm các thay đổi mang tính phá hủy (destructive changes) trước khi chúng đến được staging. Việc chạy atlas migrate lint trong GitHub Actions sẽ kiểm tra các pattern không an toàn, chẳng hạn như xóa một cột đang có dữ liệu hoặc tạo index không dùng cờ CONCURRENTLY trên một bảng đang có tải cao:

# Chạy schema linting trong CI dựa trên baseline của branch main
atlas migrate lint \
  --dir "file://migrations" \
  --dev-url "docker://postgres/16/dev" \
  --latest 1

Sau khi được phê duyệt và merge, hãy triển khai các migration đã tạo trong job release container của bạn:

atlas migrate apply \
  --dir "file://migrations" \
  --url "postgres://db_admin:${DB_PASSWORD}@prod-primary.internal:5432/store_prod"

Tổng kết

Các migration script dạng imperative biến việc duy trì schema thành một gánh nặng dễ phát sinh lỗi. Chuyển sang quản lý schema theo hướng declarative với Atlas sẽ gắn kết vòng đời database của bạn với các tiêu chuẩn CI/CD hiện đại. Bạn chỉ cần định nghĩa trạng thái mong muốn một lần duy nhất, để engine tự động sinh ra DDL đã được kiểm chứng, và sử dụng cơ chế băm tính toàn vẹn (integrity hash) để giữ cho các database PostgreSQL và MySQL luôn đồng bộ nhất quán trên mọi môi trường.

Share: