Migrate database không downtime: dual-write, backfill và cutover an toàn cho hệ thống đang chạy
Đổi engine database, tách schema, hay chuyển nhà cung cấp cloud cho một hệ thống đang phục vụ khách hàng thật không thể giải quyết bằng một cửa sổ bảo trì nửa đêm nữa. Bốn giai đoạn của một migration an toàn — dual-write, backfill, verify, cutover — và những cái bẫy phổ biến nhất ở từng bước.
Đổi database engine, tách một schema monolith thành nhiều service, hay chuyển nhà cung cấp cloud là những thay đổi hạ tầng không thể tránh khỏi khi một hệ thống tăng trưởng — nhưng với một sản phẩm đang phục vụ khách hàng thật 24/7, "tắt hệ thống lúc 2 giờ sáng để migrate" không còn là phương án chấp nhận được như 10 năm trước. Khách hàng ở nhiều múi giờ, đơn hàng có thể đến bất kỳ lúc nào, và một cửa sổ bảo trì vài giờ đủ để gây thiệt hại doanh thu hoặc vi phạm SLA đã cam kết. Bài viết này trình bày kiến trúc bốn giai đoạn để migrate dữ liệu mà không cần downtime — cùng những cái bẫy phổ biến nhất khiến các đội kỹ thuật gặp sự cố ở từng bước.
Bốn giai đoạn của một migration an toàn
Một migration zero-downtime không phải một bước "chuyển đổi" duy nhất, mà là bốn giai đoạn tuần tự, mỗi giai đoạn có thể dừng lại và rollback nếu phát hiện vấn đề:
- **Dual-write** — mọi ghi mới được ghi đồng thời vào cả DB cũ và DB mới, trong khi ứng dụng vẫn đọc từ DB cũ
- **Backfill** — chuyển toàn bộ dữ liệu lịch sử (đã tồn tại trước khi bật dual-write) sang DB mới theo batch
- **Verify** — đối soát dữ liệu giữa hai DB để đảm bảo không có sai lệch trước khi tin tưởng DB mới
- **Cutover** — chuyển đọc sang DB mới theo từng bước, rồi mới ngắt dual-write và DB cũ
Dual-write: cái bẫy dễ gặp nhất là ghi đôi thủ công trong code ứng dụng
Cách trực quan nhất — và cũng dễ sai nhất — là sửa mọi hàm ghi dữ liệu trong ứng dụng để gọi cả hai DB. Vấn đề: chỉ cần bỏ sót một điểm ghi (một job nền, một endpoint ít dùng, một đường xử lý lỗi ghi trực tiếp bằng raw SQL), dữ liệu giữa hai DB sẽ lệch nhau âm thầm mà không có lỗi nào được log. Cách an toàn hơn là dùng Change Data Capture (CDC) — một công cụ như Debezium đọc trực tiếp write-ahead log của DB cũ và replicate mọi thay đổi sang DB mới ở tầng hạ tầng, tách biệt hoàn toàn khỏi logic ứng dụng. Ứng dụng chỉ ghi vào một nơi duy nhất như trước giờ; CDC đảm bảo DB mới luôn đồng bộ mà không cần sửa code nghiệp vụ.
Một vấn đề khác cần xử lý rõ ràng: khi ghi vào DB mới thất bại nhưng ghi vào DB cũ thành công (hoặc ngược lại), hệ thống cần biết đây là một bản ghi "lệch" cần backfill lại, không phải một lỗi im lặng. Outbox pattern — ghi sự kiện thay đổi vào một bảng riêng trong cùng transaction với ghi dữ liệu chính — là cách phổ biến để đảm bảo tính nhất quán này mà không cần distributed transaction giữa hai DB.
Backfill: chuyển dữ liệu lịch sử mà không làm chậm production
Sau khi dual-write đã chạy ổn định, dữ liệu lịch sử (mọi thứ tồn tại trước thời điểm bật dual-write) vẫn chỉ có ở DB cũ. Backfill copy khối dữ liệu này sang DB mới — nhưng chạy một câu lệnh `INSERT INTO ... SELECT` cho toàn bộ bảng hàng chục triệu dòng sẽ khóa bảng hoặc chiếm hết I/O, ảnh hưởng trực tiếp đến traffic thật. Cách tiếp cận đúng là chia thành batch nhỏ theo khóa chính hoặc timestamp, ghi lại một watermark (con trỏ đánh dấu đã backfill đến đâu) sau mỗi batch để có thể dừng và tiếp tục mà không phải chạy lại từ đầu, và giới hạn tốc độ (throttle) để job backfill luôn nhường tài nguyên cho traffic production.
Verify: đối soát dữ liệu trước khi tin tưởng DB mới
Đây là bước dễ bị bỏ qua nhất khi đội ngũ đang chịu áp lực thời gian — nhưng bỏ qua nó đồng nghĩa cutover dựa trên niềm tin thay vì bằng chứng. Cách verify hiệu quả kết hợp hai lớp: so sánh checksum tổng hợp theo từng batch giữa hai DB (phát hiện nhanh chênh lệch ở mức tổng thể), và deep comparison từng trường dữ liệu trên một mẫu ngẫu nhiên các bản ghi (phát hiện lỗi tinh vi hơn như sai kiểu dữ liệu, mất độ chính xác số thập phân, hay lỗi encoding ký tự). Nguyên tắc ở bước này: không có ngưỡng "sai lệch chấp nhận được" — bất kỳ chênh lệch nào cũng cần được giải thích rõ ràng (do timing của CDC, hay do lỗi thật) trước khi chuyển sang cutover.
Cutover: chuyển đọc dần, không phải big-bang
Cutover an toàn không phải là đổi connection string một lần cho toàn bộ traffic. Dùng feature flag để chuyển phần trăm lưu lượng đọc sang DB mới theo từng bước — 1%, rồi 10%, 50%, và cuối cùng 100% — theo dõi error rate và latency ở mỗi bước trước khi tăng tiếp. Đây là cùng nguyên tắc progressive rollout mà chúng tôi áp dụng khi thiết kế alert pipeline — thay đổi lớn luôn nên đi qua các bước nhỏ có thể quan sát và đảo ngược, không phải một lần chuyển toàn bộ. Chỉ sau khi 100% traffic đọc ổn định trên DB mới trong một khoảng thời gian đủ dài, mới ngắt dual-write và chính thức nghỉ hưu DB cũ.
Khi nào không cần áp dụng toàn bộ kiến trúc này
Bốn giai đoạn trên phù hợp cho hệ thống có traffic 24/7 và không thể chấp nhận downtime dù chỉ vài phút — nhưng không phải hệ thống nào cũng ở quy mô đó. Một hệ thống nội bộ dùng trong giờ hành chính, hoặc một sản phẩm early-stage với vài trăm người dùng, thường có thể chấp nhận một cửa sổ bảo trì ngắn đã thông báo trước, với chi phí kỹ thuật thấp hơn nhiều so với dựng toàn bộ pipeline dual-write/CDC. Đây là loại đánh giá chúng tôi luôn làm trước khi đề xuất kiến trúc — một Module Sprint 3–4 tuần cho một hệ thống nhỏ không nên đội chi phí vì áp dụng nguyên xi một pattern thiết kế cho hệ thống ở quy mô enterprise.
Kết luận
Migrate database không downtime không phải một thao tác kỹ thuật đơn lẻ, mà là một chuỗi quyết định có thể kiểm chứng ở từng bước: ghi kép an toàn qua CDC thay vì sửa code thủ công, backfill có watermark thay vì một câu lệnh khổng lồ, đối soát bằng bằng chứng thay vì niềm tin, và chuyển đổi dần thay vì một lần. Đây là loại bài toán hạ tầng chúng tôi giải quyết trong lớp Server & Database của mỗi Pilot Build — bao gồm cả những lần hợp nhất 4 hệ thống legacy như portal nội bộ cho 14 team kỹ thuật mà chúng tôi đã triển khai. Liên hệ nếu hệ thống của bạn đang cần một kế hoạch migrate an toàn cho dữ liệu đang phục vụ khách hàng thật.
Bài viết liên quan
Kubernetes cho ML và data workload: thiết kế cluster từ node pool đến GPU scheduling
Kubernetes là tiêu chuẩn cho container orchestration — nhưng cấu hình mặc định không đủ cho ML workload. GPU node cần taint và toleration riêng; training job có thể chiếm hết cluster nếu không có resource quota; model serving cần PodDisruptionBudget để không bị interrupt giữa inference. Bốn vấn đề thường gặp và cách thiết kế cluster đúng ngay từ đầu.
Kiến trúc database cho hệ thống IoT: khi nào dùng TimescaleDB, khi nào dùng ClickHouse
Hai engine time-series phổ biến nhất cho dữ liệu cảm biến — tiêu chí chọn, cách thiết kế schema, và chiến lược lifecycle dữ liệu để chi phí không 'nổ' theo số lượng thiết bị.
AI agent cho doanh nghiệp: vì sao độ tin cậy là phép nhân, không phải phép cộng
Gartner dự báo hơn 40% dự án agentic AI bị hủy trước cuối 2027. Nguyên nhân thường không nằm ở chất lượng model: một agent 20 bước với độ tin cậy 95% mỗi bước chỉ hoàn thành trọn vẹn 35,8% số lần — và đó là phép tính cần làm trước khi cam kết ngân sách, không phải sau.