Atlas (Ariga): Quản lý Schema Database dưới dạng Code và Tự động hóa Migration

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

Nếu bạn đã từng ngồi so sánh tay từng file SQL migration giữa staging và production để tìm xem “cái gì đang thiếu”, bạn sẽ hiểu cảm giác đó tệ như thế nào. Mình đã trải qua chuyện này nhiều lần — đặc biệt trong các dự án dùng PostgreSQL với schema phức tạp, khi cả team cùng commit migration mà không có công cụ quản lý đàng hoàng.

Atlas của Ariga sinh ra để giải quyết đúng bài toán đó: quản lý schema database như code (Schema as Code / IaC), version control được, tự động hóa được, và cho kết quả nhất quán trên mọi môi trường.

Ba cách quản lý schema: So sánh thực tế

Để thấy rõ Atlas khác gì, mình điểm qua 3 hướng tiếp cận phổ biến trước:

1. Migration file thủ công (SQL/ORM)

Cách này phổ biến nhất — dùng Flyway, Liquibase, hoặc ORM migration (Alembic, Django migrations, Rails ActiveRecord). Bạn viết file SQL theo thứ tự, commit vào git, khi deploy thì chạy migrate.

  • Ưu điểm: Quen thuộc, nhiều tài liệu, tích hợp sẵn với ORM
  • Nhược điểm: File migration chồng chất theo thời gian, khó audit schema hiện tại, dễ conflict khi nhiều người cùng làm, không tự detect drift giữa code và DB thật

2. Declarative schema (mô tả trạng thái mong muốn)

Thay vì viết “làm gì” (ALTER TABLE…), bạn viết “muốn gì” — schema cuối cùng trông như thế nào. Công cụ tự tính toán diff và sinh migration. Terraform làm điều này với infrastructure; Atlas áp dụng cùng tư tưởng cho database.

  • Ưu điểm: Schema luôn là source of truth, dễ đọc, dễ review, không bị drift
  • Nhược điểm: Cần hiểu tool, một số edge case phức tạp cần can thiệp thủ công

3. Atlas: Hybrid approach — tốt nhất cả hai

Atlas hỗ trợ cả hai mode: declarative (schema.sql/HCL mô tả trạng thái cuối) và versioned migration (sinh file .sql có thể audit). Bạn chọn cái nào phù hợp, hoặc kết hợp cả hai tùy stage của dự án.

Phân tích ưu nhược: Atlas vs Flyway/Liquibase

Tiêu chí Flyway / Liquibase Atlas
Schema drift detection Không có Có (atlas schema diff)
Declarative mode Không
CI/CD integration Trung bình Tốt (GitHub Actions native)
Lint migration Không có Có (atlas migrate lint)
Multi-database Có (nhiều) PG, MySQL, SQLite, MS SQL
Định dạng schema SQL / XML / YAML HCL + SQL

Điểm mình thích nhất ở Atlas là atlas migrate lint — nó phân tích migration trước khi apply, cảnh báo các thao tác destructive (DROP TABLE, DROP COLUMN) hoặc thiếu index trên foreign key. Flyway không có cái này, và mình đã từng bị một lần DROP COLUMN nhầm trên staging vì không ai review kỹ.

Khi nào nên chọn Atlas?

Sau khi dùng Atlas trên vài dự án PostgreSQL production, mình thấy tool này phù hợp nhất khi:

  • Team từ 3+ người cùng thay đổi schema, cần review và approve migration
  • Có nhiều môi trường (dev/staging/prod) và cần đảm bảo schema nhất quán
  • Muốn tích hợp migration check vào CI/CD pipeline để block merge khi có vấn đề
  • Dự án dùng PostgreSQL hoặc MySQL với schema tương đối phức tạp

Ngược lại, nếu bạn làm solo project nhỏ với Django/Rails, ORM migration của framework vẫn đủ dùng — đừng over-engineer.

Cài đặt Atlas

Atlas là binary đơn, không cần runtime phụ thuộc:

# macOS
brew install ariga/tap/atlas

# Linux (one-liner)
curl -sSf https://atlasgo.sh | sh

# Kiểm tra version
atlas version

Triển khai thực tế: Versioned Migration với PostgreSQL

Workflow dưới đây mình đang dùng trên production. Tất cả demo với PostgreSQL 15.

Bước 1: Tạo project structure

mkdir myapp-db && cd myapp-db
mkdir -p migrations

Bước 2: Tạo file schema.sql — source of truth

-- schema.sql: "Trạng thái mong muốn" của database
CREATE TABLE users (
  id          BIGSERIAL PRIMARY KEY,
  email       VARCHAR(255) NOT NULL UNIQUE,
  username    VARCHAR(100) NOT NULL,
  created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE TABLE posts (
  id          BIGSERIAL PRIMARY KEY,
  user_id     BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  title       VARCHAR(500) NOT NULL,
  body        TEXT,
  published   BOOLEAN NOT NULL DEFAULT FALSE,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_published ON posts(published) WHERE published = TRUE;

Bước 3: Sinh migration file đầu tiên

atlas migrate diff initial_schema \
  --dir "file://migrations" \
  --to "file://schema.sql" \
  --dev-url "docker://postgres/15/dev"

Flag --dev-url "docker://postgres/15/dev" khá tiện — Atlas tự spin up container PostgreSQL tạm để tính diff, không cần bạn cài Postgres sẵn ở local. Sau khi chạy xong:

migrations/
  20260521000001_initial_schema.sql
  atlas.sum   # checksum file — commit cùng với migration

File atlas.sum quan trọng: Atlas dùng nó để phát hiện nếu ai sửa migration đã commit (tương tự go.sum). Luôn commit file này vào git.

Bước 4: Apply migration lên database thật

atlas migrate apply \
  --dir "file://migrations" \
  --url "postgres://user:pass@localhost:5432/myapp?sslmode=disable"

# Output:
# Migrating to version 20260521000001 (1 migration in total):
#   -- migrating version 20260521000001
#     -> CREATE TABLE users ...
#     -> CREATE TABLE posts ...
#   -- ok (23ms)

Bước 5: Thêm column mới — workflow thay đổi schema

Product yêu cầu thêm cột bio vào bảng users. Thay vì viết tay ALTER TABLE, chỉ cần sửa schema.sql:

-- Thêm vào định nghĩa bảng users:
  bio  TEXT,

Rồi chạy lại migrate diff:

atlas migrate diff add_user_bio \
  --dir "file://migrations" \
  --to "file://schema.sql" \
  --dev-url "docker://postgres/15/dev"

# Atlas tự sinh:
# ALTER TABLE "users" ADD COLUMN "bio" text;

Tips thực chiến

1. Dùng migrate lint trước khi merge PR

atlas migrate lint \
  --dir "file://migrations" \
  --dev-url "docker://postgres/15/dev" \
  --latest 1

# Atlas cảnh báo destructive changes:
# L1: Dropping non-virtual column "email" (MF103)
# L2: Adding a non-nullable column "phone" to existing table (MF103)

Tích hợp cái này vào GitHub Actions để block merge khi có destructive changes chưa được review kỹ:

# .github/workflows/atlas-lint.yml
name: Atlas Migration Lint
on:
  pull_request:
    paths: ['migrations/**', 'schema.sql']

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ariga/setup-atlas@v0
      - name: Lint migrations
        uses: ariga/atlas-action/migrate/lint@v1
        with:
          dir: 'file://migrations'
          dev-url: 'docker://postgres/15/dev'

2. Detect drift giữa schema code và DB thật

# So sánh schema hiện tại của DB với schema.sql
atlas schema diff \
  --from "postgres://user:pass@prod-host:5432/myapp?sslmode=require" \
  --to "file://schema.sql"

# Nếu có drift, output show rõ DB đang "lạc" khỏi code ở đâu

Flyway không có feature này. Mình chạy cái này định kỳ trong CI để phát hiện sớm nếu ai đó ALTER TABLE trực tiếp trên production — chuyện này xảy ra thường hơn bạn nghĩ, đặc biệt khi hotfix lúc 2 giờ sáng.

3. Dùng atlas.hcl để gọn command

# atlas.hcl
env "dev" {
  src = "file://schema.sql"
  url = "postgres://user:pass@localhost:5432/myapp?sslmode=disable"
  dev = "docker://postgres/15/dev"
  migration {
    dir = "file://migrations"
  }
}

env "prod" {
  url = getenv("DATABASE_URL")
  migration {
    dir = "file://migrations"
  }
}

Với file này, câu lệnh ngắn lại đáng kể:

atlas migrate apply --env dev
atlas migrate apply --env prod

Lỗi hay gặp và cách xử lý

  • Checksum mismatch: Ai đó sửa file migration đã commit. Xác nhận thay đổi là intentional rồi chạy atlas migrate hash --force để regenerate.
  • docker:// không hoạt động: Docker cần đang chạy và Atlas có quyền socket. Thay bằng URL database dev thật nếu không có Docker.
  • Migration stuck do advisory lock: Nếu process trước bị kill giữa chừng, chạy SELECT pg_advisory_unlock_all(); trên PostgreSQL để giải phóng lock.

Tổng kết

Atlas không phải tool duy nhất cho database migration. Nhưng detect drift, lint destructive changes, và CI/CD integration là những gì Flyway hay Liquibase không làm được. Mình đã chuyển một dự án PostgreSQL từ Alembic sang Atlas — chưa có lý do gì để quay lại, nhất là khi cả team cần review migration trước khi merge.

Nếu bạn đang setup dự án mới, thử Atlas ngay từ đầu — 30 phút setup ban đầu tiết kiệm rất nhiều đau đầu về sau khi schema phức tạp dần lên.

Share: