Quản lý phiên bản Database với Liquibase: Tự động hóa CI/CD và Rollback ‘không đau đớn’

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

Vấn đề thực tế: Cơn ác mộng “Chạy SQL bằng cơm”

Hồi mới vào nghề, mình từng chứng kiến một anh Senior suýt “bay màu” database staging chỉ vì copy-paste nhầm lệnh ALTER TABLE từ khung chat vào terminal. Kịch bản này chắc không lạ: Dev sửa DB ở local, lưu lệnh SQL vào file .sql hoặc ghi chú linh tinh. Đến ngày release, một ông DevOps tội nghiệp phải login vào Production để chạy tay từng script.

Cách làm này cực kỳ rủi ro. Chỉ cần quên một script hoặc chạy sai thứ tự, ứng dụng sẽ sập ngay lập tức. Việc nhớ lại các thay đổi để viết lệnh DROP hay RENAME ngược lại khi cần rollback thực sự là một cực hình. Mình từng mất 3 ngày planning chỉ để migrate database 100GB từ MySQL sang PostgreSQL, đảm bảo hàng trăm script khớp nhau giữa các môi trường.

Tại sao code có Git mà Database lại không?

Chúng ta dùng Git để quản lý code, biết rõ ai sửa gì và có thể quay xe bất cứ lúc nào. Thế nhưng, nhiều đội dự án vẫn quản lý Database theo kiểu “truyền miệng”.

Vấn đề nằm ở việc thiếu một Source of Truth (nguồn dữ liệu gốc) cho cấu trúc database. Khi hệ thống phình to với hàng trăm bảng, việc dùng file SQL rời rạc chắc chắn dẫn đến tình trạng lệch schema (schema drift) giữa Dev, Staging và Production.

Ba cấp độ quản lý Database Migration

Tùy quy mô dự án, bạn có thể chọn một trong ba cách sau:

  • Cấp độ 1: Lưu file SQL vào Git theo ngày tháng (ví dụ: 20231027_add_user_col.sql). Cách này dễ làm nhưng vẫn phải chạy thủ công, dễ sai sót.
  • Cấp độ 2: Dùng Migration tích hợp trong Framework (như TypeORM, Entity Framework). Tuy nhiên, cách này khiến database bị phụ thuộc chặt chẽ vào ngôn ngữ lập trình của ứng dụng.
  • Cấp độ 3: Sử dụng công cụ chuyên biệt như Flyway hoặc Liquibase. Đây là lựa chọn của dân chuyên nghiệp để tách biệt hoàn toàn logic quản lý database khỏi code.

Tại sao Liquibase là ‘chân ái’ cho CI/CD?

So với Flyway, Liquibase nhỉnh hơn nhờ hỗ trợ đa dạng định dạng (XML, YAML, JSON, SQL) và khả năng tự động tạo script rollback cực mạnh. Dưới đây là cách mình áp dụng Liquibase vào dự án thực tế.

1. Cấu trúc một file Changeset chuẩn

Thay vì viết SQL thuần, mình khuyến khích bạn dùng format YAML hoặc XML. Liquibase sẽ tự biên dịch sang cú pháp tương ứng của từng loại DB. Nếu dự án đổi từ MySQL sang PostgreSQL, bạn gần như không phải sửa lại bất kỳ dòng script migration nào.

databaseChangeLog:
  - changeSet:
      id: 1
      author: tech_editor
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: int
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: username
                  type: varchar(50)
                  constraints:
                    nullable: false

2. ‘Cỗ máy thời gian’ DATABASECHANGELOG

Khi chạy lần đầu, Liquibase tự tạo bảng DATABASECHANGELOG để theo dõi lịch sử. Mỗi changeSet thực thi thành công sẽ được lưu lại kèm mã hash (MD5Sum).

Cơ chế kiểm tra rất nghiêm ngặt:

  • Bỏ qua những gì đã chạy.
  • Thực thi các thay đổi mới.
  • Báo lỗi ngay nếu file cũ bị sửa nội dung (sai mã hash) để tránh hỏng dữ liệu.

3. Đưa Database vào Pipeline CI/CD

Đừng bao giờ để Dev tự chạy Liquibase từ máy cá nhân lên Prod. Hãy tích hợp nó vào Jenkins, GitLab CI hoặc GitHub Actions. Một quy trình chuẩn thường gồm 4 bước:

  1. Dev tạo branch, viết changeSet mới.
  2. Khi tạo Pull Request, CI chạy liquibase validate để kiểm tra cú pháp.
  3. Sau khi merge vào develop, CI tự động update lên database Staging.
  4. Cuối cùng, pipeline đẩy thay đổi lên Production sau khi qua các lớp kiểm duyệt.

Mẹo nhỏ: Sử dụng Docker để chạy Liquibase giúp môi trường luôn đồng nhất:

docker run --rm -v $(pwd):/liquibase/changelog \
  liquibase/liquibase \
  --changelog-file=/liquibase/changelog/db-changelog.yaml \
  --url="jdbc:postgresql://db_host:5432/mydb" \
  --username=admin --password=secret update

4. Tuyệt chiêu Rollback: Phao cứu sinh khi Deploy lỗi

Với định dạng XML/YAML, Liquibase có thể tự suy luận lệnh ngược (ví dụ createTable thì rollback là dropTable). Tuy nhiên, với các thay đổi logic phức tạp, bạn nên tự định nghĩa block rollback để đảm bảo an toàn 100%.

  - changeSet:
      id: 2
      author: tech_editor
      changes:
        - renameColumn:
            tableName: users
            oldColumnName: username
            newColumnName: login_name
      rollback:
        - renameColumn:
            tableName: users
            oldColumnName: login_name
            newColumnName: username

Kinh nghiệm xương máu từ thực chiến

  • Chia để trị: Mỗi ChangeSet chỉ nên làm một nhiệm vụ duy nhất. Nếu bạn gom 10 bảng vào một changeset và bảng cuối bị lỗi, việc debug sẽ rất mệt mỏi.
  • Nguyên tắc bất biến: Không bao giờ sửa ChangeSet đã commit. Nếu viết sai, hãy tạo một changeset mới để fix. Việc sửa file cũ sẽ làm hỏng mã MD5Sum và khiến Liquibase dừng hoạt động.
  • Tận dụng Contexts: Dùng context: test để chỉ chèn dữ liệu mẫu ở môi trường Dev/Staging, tránh làm bẩn database Production.
  • Luôn chạy Dry-run: Sử dụng lệnh update-sql để xem trước các câu lệnh SQL mà Liquibase sắp thực thi trước khi thực sự áp dụng chúng.

Áp dụng Liquibase có thể khiến bạn mất thêm chút thời gian cấu hình ban đầu. Tuy nhiên, sự an tâm khi bấm nút Deploy là phần thưởng xứng đáng. Bạn sẽ không còn phải soi từng dòng SQL hay lo lắng về việc lệch schema nữa. Chúc các bạn quản lý database nhàn hạ hơn!

Share: