Tập này hướng dẫn bạn deploy migration trên production mà không làm sập ứng dụng và không mất dữ liệu. Kết quả là một quy trình migration an toàn, áp dụng ngay cho ứng dụng quản lý chi tiêu. Nối tiếp tập 5 về static và media, tập này xử lý phần rủi ro nhất của mỗi lần release: đổi schema database khi user vẫn đang dùng hệ thống.

Nguyên tắc vàng: tương thích ngược

Khi deploy không downtime, code cũ và code mới cùng chạy một thời gian, và cả hai đều phải làm việc được với schema hiện tại. Vì vậy mọi thay đổi schema phải tương thích ngược: code mới chạy được với schema cũ, code cũ chạy được với schema mới. Quy tắc rất đơn giản: thêm trước, xóa sau. Mẫu chuẩn expand–migrate–contract gồm ba giai đoạn: mở rộng, chuyển dữ liệu, rồi thu hẹp schema.

Ví dụ: đổi tên trường theo ba lần deploy

Giả sử bạn muốn đổi tên trường note của Transaction thành description. Lần deploy đầu tiên chỉ thêm trường mới, chưa xóa trường cũ:

# expenses/migrations/0002_add_description.py
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("expenses", "0001_initial")]
operations = [
migrations.AddField(
model_name="transaction",
name="description",
field=models.TextField(blank=True, null=True),
),
]

Ở lần này, code mới ghi cả hai trường để dữ liệu luôn đồng bộ. Sau đó, một data migration copy dữ liệu cũ sang trường mới, rồi lần deploy tiếp theo mới chuyển code sang chỉ đọc trường mới. Chỉ khi không còn code nào chạm tới trường cũ, bạn mới xóa nó trong một migration riêng.

Data migration: tách riêng, chạy theo lô

Đừng trộn thay đổi schema và chuyển dữ liệu trong cùng một migration phức tạp, vì lỗi ở nửa sau sẽ khó rollback. Hãy viết data migration riêng, dùng apps.get_model thay vì import model trực tiếp, và xử lý theo lô để tránh khóa bảng lâu:

# expenses/migrations/0003_backfill_description.py
from django.db import migrations
def backfill(apps, schema_editor):
Transaction = apps.get_model("expenses", "Transaction")
while True:
batch = list(
Transaction.objects.filter(description__isnull=True, note__isnull=False)
.values_list("id", "note")[:1000]
)
if not batch:
break
for tx_id, note in batch:
Transaction.objects.filter(id=tx_id).update(description=note)
class Migration(migrations.Migration):
dependencies = [("expenses", "0002_add_description")]
operations = [migrations.RunPython(backfill, migrations.RunPython.noop)]

Thứ tự deploy và những migration nguy hiểm

Thứ tự đúng là: migration cộng thêm chạy trước code mới; migration phá hủy (xóa cột, đổi kiểu bắt buộc) chạy sau khi code mới đã ngừng dùng phần cũ. Trước mỗi lần deploy, hãy chạy python manage.py sqlmigrate để xem SQL thực tế và thử migration trên staging có dữ liệu gần giống production. Cần tránh: thêm cột NOT NULL không có giá trị mặc định trên bảng lớn, đặt UNIQUE trên dữ liệu chưa sạch, và tạo index thường trên bảng hàng triệu dòng vì nó khóa ghi. Với PostgreSQL, hãy tạo index đồng thời:

# expenses/migrations/0004_add_index.py
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
dependencies = [("expenses", "0003_backfill_description")]
operations = [
migrations.RunSQL(
"CREATE INDEX CONCURRENTLY idx_tx_created ON expenses_transaction (created_at);",
reverse_sql="DROP INDEX idx_tx_created;",
),
]

Cuối cùng, luôn backup database trước khi chạy migration phá hủy. Nếu migration lỗi giữa chừng, Django dừng ở migration đó; bạn sửa lỗi rồi chạy lại, thay vì sửa tay schema.

Áp dụng vào dự án của bạn

Với ứng dụng quản lý chi tiêu, hãy chia mỗi thay đổi schema sắp tới thành cặp migration cộng thêm và migration dọn dẹp, deploy cách nhau ít nhất một lần release. Khi có sự cố, bạn chỉ cần rollback code mà không phải khôi phục schema.

Bài viết liên quan

Câu hỏi thường gặp

Migration chạy lỗi giữa chừng thì làm gì?

Dừng lại, xác định migration chưa hoàn thành bằng lệnh showmigrations, sửa nguyên nhân rồi chạy lại migrate. Đừng sửa tay bảng trong database vì Django sẽ mất dấu trạng thái migration.

Có thể đổi tên cột trong một lần deploy không?

Không nên. Đổi tên trực tiếp làm code cũ đang chạy gọi cột cũ và lập tức lỗi. Hãy theo mẫu ba bước: thêm cột mới, chuyển dữ liệu, rồi mới xóa cột cũ ở lần deploy sau.

Thêm cột NOT NULL có an toàn không?

Chỉ an toàn khi bạn cho giá trị mặc định, hoặc thêm cột nullable trước, backfill rồi mới đặt NOT NULL sau. Thêm NOT NULL ngay trên bảng lớn vừa khóa bảng lâu, vừa có thể thất bại nếu đã có dòng trống.

Nguồn tham khảo