Blue-green vs canary: triển khai thay đổi API và DB an toàn
Giải thích blue-green và canary cho thay đổi API và database, kèm bước thực tế để giảm rủi ro downtime khi migration schema và khi client mobile cập nhật chậm.

Tại sao triển khai trở nên rủi ro khi có thay đổi schema và client mobile cập nhật chậm
Một lần deploy có thể chạy hoàn hảo trong test mà vẫn gặp lỗi ngay khi có lưu lượng thực. Nguyên nhân thường là bạn không chỉ thay đổi mã. Hợp đồng API và schema database cũng thay đổi, và chúng hiếm khi thay đổi cùng nhịp.
Mọi thứ hỏng khi các phần khác nhau của hệ thống không đồng ý về khái niệm "đúng". Backend mới kỳ vọng một cột chưa tồn tại. Backend cũ ghi dữ liệu theo định dạng mà mã mới không còn hiểu. Ngay cả những thay đổi nhỏ như đổi tên trường, siết validation, hoặc thay đổi giá trị enum cũng có thể gây lỗi production.
Mobile làm tăng mức độ rủi ro vì các phiên bản cũ tồn tại lâu. Một số người cập nhật trong vài phút, người khác trong vài tuần. Điều đó có nghĩa backend phải phục vụ nhiều thế hệ client cùng lúc. Nếu bạn phát hành API chỉ hoạt động với app mới nhất, bạn có thể làm hỏng tính năng thanh toán, đăng ký, hoặc đồng bộ nền cho một nhóm người dùng mà không nhận ra ngay.
"Rủi ro downtime" không chỉ là site sập. Trong hệ thống thực tế, nó thường biểu hiện dưới dạng lỗi từng phần:
- tăng đột biến lỗi 4xx/5xx trên một vài endpoint trong khi phần còn lại có vẻ ổn
- đăng nhập thất bại vì token, role hoặc bản ghi user không còn khớp
- vấn đề dữ liệu im lặng (giá trị mặc định sai, text bị cắt, quan hệ bị mất) lộ ra sau vài ngày
- job nền bị kẹt và tạo hàng đợi mất nhiều giờ mới xử lý xong
Đó là lý do các team so sánh blue-green vs canary: bạn đang cố giảm phạm vi ảnh hưởng khi các thay đổi không hoàn toàn tương thích.
Blue-green và canary nói ngôn ngữ đơn giản
Khi người ta so sánh blue-green vs canary deployments, thường là để trả lời câu hỏi: bạn muốn một lần chuyển lớn, có kiểm soát, hay một thử nghiệm nhỏ, thận trọng?
Blue-green: hai phiên bản đầy đủ và một công tắc chuyển traffic
Blue-green nghĩa là bạn chạy hai môi trường hoàn chỉnh cùng lúc. "Blue" là phiên bản hiện tại đang phục vụ người dùng. "Green" là phiên bản mới, đã được deploy và test song song. Khi sẵn sàng, bạn chuyển lưu lượng từ blue sang green.
Cách này tốt cho tính dự đoán. Bạn có thể xác nhận phiên bản mới với cấu hình giống production trước khi nó phục vụ người dùng thật, rồi thực hiện một lần cắt chuyển gọn.
Rollback cũng dễ: nếu có vấn đề sau khi chuyển, hướng traffic về blue. Việc chuyển lại gần như tức thời, nhưng cache, job nền và thay đổi dữ liệu vẫn có thể làm phức tạp việc phục hồi.
Canary: cho một phần nhỏ traffic trước
Canary nghĩa là phiên bản mới đi vào hoạt động cho một lát cắt nhỏ người dùng hoặc request trước. Nếu nó ổn, bạn tăng tỉ lệ đó dần đến khi phục vụ mọi người.
Canary phù hợp khi bạn lo hành vi không ngờ dưới lưu lượng thực. Bạn có thể phát hiện sớm vấn đề trước khi hầu hết người dùng bị ảnh hưởng.
Rollback là giảm tỉ lệ canary về 0 (hoặc ngưng chuyển đến phiên bản mới). Thường nhanh, nhưng không luôn sạch, vì một số người dùng đã tạo dữ liệu hoặc trạng thái mà cả hai phiên bản phải xử lý.
Một cách đơn giản để nhớ các đánh đổi:
- Blue-green ưu tiên cắt chuyển sạch và quay lại nhanh.
- Canary ưu tiên học hỏi từ lưu lượng thực với phạm vi ảnh hưởng hạn chế.
- Không có cái nào tự động giải quyết rủi ro database. Nếu schema không tương thích, cả hai đều có thể thất bại.
- Canary phụ thuộc vào monitoring vì bạn quyết định dựa trên tín hiệu thực tế.
- Blue-green thường cần thêm tài nguyên vì bạn chạy hai stack đầy đủ.
Ví dụ: nếu bạn phát hành API thỉnh thoảng trả về một trường mới, canary giúp bạn thấy liệu client cũ có crash khi nhận dữ liệu bất ngờ hay không. Nếu thay đổi yêu cầu đổi tên cột và mã cũ không xử lý được, blue-green cũng không cứu bạn trừ khi migration được thiết kế để hỗ trợ cả hai phiên bản.
Điều gì làm migrations database khác so với deploy mã
Deploy mã thường dễ rollback. Nếu phiên bản mới sai, bạn redeploy bản cũ và hầu hết trở lại trạng thái trước.
Thay đổi database khác vì nó làm thay đổi hình dạng dữ liệu. Khi hàng được viết lại, cột bị drop, hoặc ràng buộc siết chặt, quay lại hiếm khi tức thời. Ngay cả khi bạn rollback mã, nó có thể không hiểu schema mới.
Đó là lý do rủi ro downtime khi migration thường ít liên quan đến phương pháp deploy và nhiều hơn đến cách thiết kế migration.
Nguyên tắc migration online
Các migration an toàn nhất được xây dựng để chạy khi cả phiên bản cũ và mới cùng tồn tại. Mẫu đơn giản: làm bước an toàn để phiên bản cũ có thể bỏ qua, cập nhật mã để dùng nó, rồi dọn dẹp sau.
Một chuỗi expand-then-contract phổ biến:
- Thay đổi bổ sung trước: thêm cột nullable, thêm bảng mới, thêm index không khóa ghi.
- Hành vi kép: ghi cả cũ và mới, hoặc đọc từ mới có fallback về cũ.
- Backfill riêng: migrate dữ liệu cũ theo lô nhỏ.
- Chuyển sang: chuyển phần lớn traffic sang hành vi mới.
- Thay đổi phá huỷ cuối cùng: drop cột cũ, bỏ path mã cũ, siết ràng buộc.
"Big bang" migrations gộp các bước rủi ro nhất trong một release: khóa dài, backfill nặng, và mã giả định schema mới tồn tại khắp nơi.
Tại sao mobile cập nhật chậm làm tiêu chuẩn cao hơn
Client mobile có thể ở phiên bản cũ hàng tuần. Backend phải tiếp tục chấp nhận request cũ và trả về response cũ trong khi database tiến hoá.
Nếu app cũ gửi request thiếu trường mới, server không thể bất ngờ bắt trường đó bắt buộc trong database. Bạn cần một giai đoạn cả hai hành vi cùng tồn tại.
Chiến lược nào giảm rủi ro downtime cho schema migrations
Lựa chọn an toàn nhất ít phụ thuộc vào công cụ deploy mà phụ thuộc vào một câu hỏi: cả phiên bản cũ và mới có thể chạy đúng trên cùng một schema database trong một thời gian không?
Nếu câu trả lời là có, blue-green thường là lựa chọn ít downtime nhất. Bạn chuẩn bị thay đổi database trước, giữ traffic trên stack cũ, rồi chuyển traffic sang stack mới trong một lần cắt. Nếu có vấn đề, bạn chuyển lại nhanh.
Blue-green vẫn thất bại khi app mới yêu cầu schema mới ngay lập tức. Ví dụ thường gặp: drop hoặc đổi tên cột mà phiên bản cũ vẫn đọc, hoặc thêm ràng buộc NOT NULL trước khi app ghi giá trị. Trong những trường hợp đó, rollback không an toàn vì database đã bất tương thích.
Canary tốt hơn khi bạn cần học có kiểm soát. Một phần nhỏ lưu lượng vào phiên bản mới trước giúp phát hiện edge-case như thiếu index, kiểu truy vấn bất ngờ, hoặc job nền hành xử khác dưới tải production. Đổi lại, bạn phải giữ cả hai phiên bản cùng hoạt động, thường có nghĩa là thay đổi database tương thích ngược.
Quy tắc ra quyết định thực tế
Khi cân nhắc blue-green vs canary cho rủi ro downtime migration:
- Chọn blue-green khi bạn có thể giữ migration ở dạng additive và tương thích, và bạn chủ yếu muốn cắt chuyển nhanh, sạch.
- Chọn canary khi bạn không chắc hành vi trong production, hoặc dự đoán các dạng dữ liệu hiếm sẽ quan trọng.
- Nếu migration buộc phải phá vỡ ngay lập tức, đừng chọn giữa blue-green và canary. Thay đổi kế hoạch sang expand-then-contract.
"Tương thích" trông như thế nào trong thực tế
Giả sử bạn thêm trường mới vào bảng orders. Con đường an toàn: thêm cột nullable, deploy app ghi nó, backfill các hàng cũ, rồi sau đó áp ràng buộc. Trong kịch bản đó, blue-green cho bạn cắt chuyển sạch, còn canary cho bạn cảnh báo sớm nếu có path mã nào vẫn giả định cấu trúc cũ.
Cách mobile cập nhật chậm thay đổi lựa chọn triển khai
Người dùng web refresh. Người dùng mobile thì không.
Trên iOS và Android, nhiều người ở lại phiên bản cũ vài tuần hoặc vài tháng. Một số chỉ cập nhật khi app bắt buộc, một số thiết bị offline lâu. Điều đó biến client "cũ" thành bài kiểm tra liên tục cho tương thích ngược.
Điều này chuyển mục tiêu từ "deploy không downtime" sang "giữ nhiều thế hệ client cùng hoạt động". Trên thực tế, mobile thường đẩy bạn hướng tư duy giống canary cho API, ngay cả khi bạn dùng blue-green cho hạ tầng.
Thay đổi tương thích ngược vs versioning API
Hầu hết thời gian, bạn muốn thay đổi tương thích ngược, vì chúng cho phép client cũ và mới dùng cùng endpoint.
Ví dụ tương thích ngược: thêm trường mới, chấp nhận cả payload cũ và mới, giữ các trường response hiện có, và tránh đổi nghĩa. Versioning API hữu ích khi hành vi phải thay đổi (không chỉ thêm dữ liệu), hoặc khi bạn phải loại bỏ/đổi tên trường.
Ví dụ: thêm trường tuỳ chọn marketing_opt_in thường an toàn. Thay đổi cách tính price thường không an toàn.
Lên kế hoạch cho cửa sổ deprecation
Nếu cần thay đổi phá vỡ, coi việc kết thúc hỗ trợ như một quyết định sản phẩm. Một cửa sổ deprecation hữu dụng được đo bằng "người dùng còn hoạt động trên phiên bản cũ", không phải theo ngày trên lịch.
Chuỗi thực tế:
- Ship backend hỗ trợ cả client cũ và mới.
- Phát hành app mới và theo dõi tỉ lệ adoption theo version.
- Cảnh báo hoặc giới hạn chỉ khi phiên bản cũ giảm dưới ngưỡng an toàn.
- Loại bỏ hành vi cũ cuối cùng, kèm kế hoạch rollback.
Theo bước: pattern rollout an toàn cho thay đổi API + database
Khi bạn thay đổi API và database cùng lúc, kế hoạch an toàn thường gồm hai hoặc ba giai đoạn. Mỗi bước nên an toàn để deploy độc lập, ngay cả khi người dùng giữ app cũ vài tuần.
Pattern rollout tránh phá client cũ
Bắt đầu với thay đổi database additive. Thêm cột hoặc bảng mới, tránh đổi tên hoặc drop, cho phép null khi cần, và dùng default để mã cũ không đột ngột chạm ràng buộc.
Sau đó ship mã ứng dụng chịu được cả hai hình dạng dữ liệu. Read nên chấp nhận "trường cũ không có" và "trường mới có". Write giữ ghi trường cũ trước, và tùy chọn ghi cả trường mới.
Chuỗi điển hình:
- Thêm phần schema mới (cột, bảng, index) mà không xóa phần cũ.
- Deploy mã đọc cả cũ lẫn mới và không crash khi gặp null.
- Backfill các hàng hiện có theo lô nhỏ, rồi xác minh số lượng, tỉ lệ null và hiệu năng truy vấn.
- Chuyển đường ghi sang trường mới, vẫn giữ fallback đọc.
- Khi phiên bản mobile cũ biến mất, loại bỏ trường cũ và dọn mã.
Backfill và xác minh: nơi outages ẩn náu
Backfill hay thất bại vì bị coi là script nhanh. Chạy dần, giám sát tải, và xác minh kết quả. Nếu hành vi mới cần index, thêm index trước khi chuyển read/write, không phải sau.
Ví dụ: bạn thêm phone_country_code để format tốt hơn. Trước tiên thêm cột (nullable), cập nhật API chấp nhận nó nhưng vẫn chạy khi thiếu, backfill từ số điện thoại hiện có, rồi bắt đầu ghi cho đăng ký mới. Vài tuần sau, khi app cũ hầu như biến mất, bạn bỏ path parsing cũ.
Công cụ làm cả hai chiến lược an toàn hơn (không cần phức tạp)
Bạn không cần setup phức tạp để làm blue-green hay canary an toàn hơn. Một vài thói quen giảm bất ngờ khi API và schema di chuyển khác nhịp.
Dual-read và dual-write (chỉ tạm thời)
Dual-write là việc app ghi dữ liệu vào cả chỗ cũ và chỗ mới trong giai đoạn chuyển tiếp (ví dụ users.full_name và users.display_name). Dual-read là đọc từ chỗ mới nhưng fallback về chỗ cũ. Điều này mua thời gian cho client cập nhật chậm, nhưng nên là cầu nối ngắn hạn. Quyết định cách loại bỏ, theo dõi đường đi nào được dùng, và thêm kiểm tra cơ bản để đảm bảo cả hai ghi nhất quán.
Feature flags cho thay đổi hành vi
Feature flag cho phép deploy mã mà chưa kích hoạt hành vi rủi ro. Điều này hữu ích cho cả blue-green và canary vì bạn tách việc "deploy" khỏi "bật".
Ví dụ: deploy support cho trường response mới, nhưng giữ server trả về dạng cũ cho đến khi sẵn sàng. Rồi bật cho nhóm nhỏ, quan sát lỗi, và mở rộng. Nếu có lỗi, tắt flag mà không cần rollback toàn bộ.
Tư duy contract testing (API là một lời hứa)
Nhiều sự cố migration không thực sự là "vấn đề database" mà là kỳ vọng client. Hãy coi API như một lời hứa. Tránh bỏ trường hoặc đổi nghĩa. Làm trường không biết trước thành optional. Thay đổi additive thường an toàn; thay đổi phá vỡ nên chờ version API mới.
Job migration dữ liệu đáng tin cậy
Migrations thường cần job backfill để sao chép dữ liệu, tính toán giá trị, hoặc dọn dẹp. Những job này nên nhàm chán và có thể lặp lại: an toàn chạy hai lần, có retry, dễ theo dõi, và giới hạn tốc độ để không gây spike tải.
Sai lầm phổ biến gây outage khi migration
Hầu hết outage xảy ra khi release giả định mọi thứ di chuyển cùng nhau: tất cả service deploy cùng lúc, dữ liệu sạch, và mọi client cập nhật ngay. Hệ thống thực tế không như vậy, đặc biệt với mobile.
Các pattern thất bại thông thường:
- Drop hoặc đổi tên cột quá sớm. API cũ, job nền, hoặc app mobile cũ vẫn còn dùng.
- Giả định client cập nhật nhanh. Release mobile mất thời gian đến người dùng, nhiều người không cập nhật ngay.
- Chạy migration khóa bảng lớn giờ cao điểm. Một index hoặc thay đổi cột "đơn giản" có thể chặn ghi.
- Test chỉ với dữ liệu mẫu sạch. Dữ liệu production có null, định dạng lạ, duplicates, và giá trị legacy.
- Không có kế hoạch rollback thực sự cho mã và dữ liệu. "Chúng ta redeploy bản trước" là không đủ nếu schema đã thay đổi.
Ví dụ: bạn đổi tên status thành order_status và deploy API mới. Web app hoạt động. App mobile cũ vẫn gửi status, và giờ API từ chối request đó, gây thất bại thanh toán. Nếu bạn drop cột, khôi phục hành vi không phải là chuyển ngay được.
Một mặc định tốt hơn: thay đổi nhỏ, đảo ngược được, giữ cả hai đường làm việc cùng nhau, và ghi ra rõ bạn sẽ làm gì nếu metric nhảy (cách chuyển traffic, feature flag tắt hành vi mới, và cách xác thực & sửa dữ liệu nếu backfill sai).
Checklist nhanh trước khi deploy
Ngay trước release, một checklist ngắn bắt các vấn đề thường dẫn đến rollback nửa đêm. Điều này quan trọng nhất khi bạn thay đổi API và database cùng lúc, đặc biệt với mobile cập nhật chậm.
Năm kiểm tra ngăn hầu hết outage
- Tương thích: xác nhận cả app cũ và mới đều chạy với cùng schema database. Một test thực tế là chạy build production hiện tại trên database staging đã áp migration mới.
- Thứ tự migration: đảm bảo migration đầu là additive, và lên lịch thay đổi phá huỷ (drop cột, siết ràng buộc) cho sau này.
- Rollback: định nghĩa thao tác undo nhanh nhất. Với blue-green là chuyển traffic về lại. Với canary là gửi 100% traffic về phiên bản ổn định. Nếu rollback cần migration khác, thì nó không đơn giản.
- Hiệu năng: đo latency truy vấn sau thay đổi schema, không chỉ kiểm tra đúng/sai. Thiếu index có thể làm một endpoint như thể bị outage.
- Thực tế client: xác định phiên bản mobile cũ nhất vẫn gọi API. Nếu một tỉ lệ đáng kể còn ở đó, lên kế hoạch cửa sổ tương thích dài hơn.
Kịch bản minh chứng nhanh
Nếu bạn thêm trường mới như preferred_language, deploy thay đổi database trước dưới dạng nullable. Sau đó ship server đọc khi có nhưng không bắt buộc. Chỉ khi phần lớn traffic trên app đã cập nhật thì mới bắt buộc hoặc loại bỏ hành vi cũ.
Ví dụ: thêm trường mới mà không phá app mobile cũ
Giả sử bạn thêm trường profile country, và kinh doanh muốn trường đó là bắt buộc. Điều này có thể gây lỗi ở hai nơi: client cũ không gửi trường, và database có thể từ chối write nếu bạn áp NOT NULL quá sớm.
Cách an toàn hơn là hai thay đổi riêng: trước tiên thêm trường backward compatible, rồi sau đó enforce "bắt buộc" khi client đã cập nhật.
Trông như thế nào với blue-green
Với blue-green, bạn deploy phiên bản mới bên cạnh phiên bản cũ. Bạn vẫn cần thay đổi database tương thích cả hai phiên bản.
Luồng an toàn:
- deploy migration (thêm
countrynhư nullable) - deploy green chấp nhận thiếu
countryvà dùng fallback - test các luồng chính với green (signup, edit profile, checkout)
- chuyển traffic
Nếu có vấn đề, chuyển lại. Điểm mấu chốt là chuyển lại chỉ hiệu quả nếu schema vẫn hỗ trợ phiên bản cũ.
Trông như thế nào với canary
Với canary, bạn mở hành vi API mới cho một lát cắt nhỏ (thường 1%-5%) và theo dõi lỗi validate thiếu trường, thay đổi độ trễ, và lỗi database không mong muốn.
Bất ngờ phổ biến là client mobile cũ gửi cập nhật profile không có country. Nếu API coi đó là bắt buộc ngay lập tức, bạn thấy lỗi 400. Nếu database enforce NOT NULL, bạn có thể thấy 500.
Một chuỗi an toàn:
- thêm
countrylà nullable (hoặc default an toàn như "unknown") - chấp nhận thiếu
countrytừ client cũ - backfill
countrycho user hiện có bằng job nền - enforce "bắt buộc" sau đó (trước tiên ở API, rồi ở database)
Sau release, ghi lại client cũ có thể gửi gì và server đảm bảo gì. Hợp đồng viết ra đó ngăn cùng loại lỗi lặp lại ở migration sau.
Nếu bạn xây trên AppMaster (appmaster.io), nguyên tắc rollout này vẫn áp dụng dù bạn có thể sinh backend, web và native từ một mô hình. Dùng nền tảng để ship thay đổi schema additive và logic API chịu lỗi trước, rồi siết ràng buộc khi adoption tăng.
Câu hỏi thường gặp
Blue-green chạy hai môi trường đầy đủ và chuyển tất cả lưu lượng cùng lúc. Canary phát hành phiên bản mới cho một phần nhỏ người dùng trước rồi tăng dần dựa trên dữ liệu thực tế.
Chọn blue-green khi bạn muốn một lần cắt chuyển sạch và bạn tự tin phiên bản mới tương thích với schema database hiện tại. Nó đặc biệt hữu ích khi rủi ro chính nằm ở mã ứng dụng, không phải hành vi bất ngờ trong production.
Chọn canary khi bạn cần học từ lưu lượng thực tế trước khi quyết định triển khai toàn bộ, ví dụ khi mẫu truy vấn, dữ liệu ngoại lệ, hoặc job nền có thể hành xử khác trong production. Nó giảm phạm vi ảnh hưởng, nhưng bạn phải theo dõi metric chặt và sẵn sàng dừng rollout.
Không. Nếu thay đổi schema làm mất tương thích (ví dụ drop hoặc đổi tên cột còn được mã cũ dùng), cả blue-green lẫn canary đều có thể thất bại. Cách an toàn hơn là thiết kế migration online cho phép chạy đồng thời cả phiên bản cũ và mới.
Người dùng mobile có thể ở lại phiên bản cũ hàng tuần, nên backend phải hỗ trợ nhiều thế hệ client cùng lúc. Điều này thường có nghĩa là giữ tương thích ngược lâu hơn và tránh thay đổi buộc mọi client phải cập nhật ngay.
Bắt đầu với thay đổi additive mà mã cũ có thể bỏ qua, như thêm cột nullable hoặc bảng mới. Sau đó triển khai mã chấp nhận cả hai dạng dữ liệu và ghi theo cách tương thích, backfill dần dần, chuyển hành vi, rồi cuối cùng loại bỏ trường cũ hoặc siết ràng buộc.
Liệt kê những gì client cũ gửi và mong nhận lại, rồi tránh bỏ trường hoặc đổi nghĩa. Ưu tiên thêm trường tuỳ chọn, chấp nhận cả hai dạng request cũ và mới, và hoãn validate "bắt buộc" cho tới khi tỉ lệ khách hàng cập nhật đủ cao.
Dual-write là ghi đồng thời vào cả trường cũ và trường mới trong giai đoạn chuyển tiếp. Dual-read là đọc từ trường mới và fallback về trường cũ nếu cần. Giữ cầu nối này tạm thời, theo dõi đường đi nào đang được dùng và lên kế hoạch dọn dẹp rõ ràng khi client cũ biến mất dần.
Feature flag cho phép bạn deploy mã nhưng tắt hành vi rủi ro, rồi bật dần. Nếu lỗi tăng, bạn tắt flag mà không phải redeploy, hữu ích cả cho cutover blue-green và ramp-up canary.
Drop hoặc đổi tên cột quá sớm, ép NOT NULL trước khi client gửi giá trị, và chạy migration khóa bảng trong giờ cao điểm là các nguyên nhân thường gặp. Một lỗi phổ biến khác là giả định dữ liệu test giống production, khiến backfill và validate thất bại trên null hoặc dạng dữ liệu lạ.


