Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 7 — Tránh phá vỡ khi buộc phải đổi hành vi: expand–migrate–contract

Đã đọc 0/8 mục · còn khoảng 3 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Nhiều migration không chỉ đổi cấu trúc mà đổi cả hợp đồng: đổi schema DB, đổi chữ ký API công khai, đổi định dạng dữ liệu. Lúc này "giữ nguyên hành vi" không còn đúng — nhưng bạn vẫn phải tránh làm sập hệ thống đang chạy và những nơi khác đang phụ thuộc. Kỹ thuật xương sống là expand–migrate–contract (mở rộng – di trú – thu hẹp), còn gọi là parallel change.

Ba pha

EXPAND   : thêm cái mới BÊN CẠNH cái cũ, cả hai cùng sống
MIGRATE  : chuyển dần mọi nơi dùng sang cái mới (agent làm phần này)
CONTRACT : khi không còn ai dùng cái cũ -> xóa cái cũ

Mỗi pha giữ hệ thống luôn deployable: bạn có thể dừng giữa chừng mà production không sập.

Ví dụ: đổi cột full_name thành first_name + last_name

Pha EXPAND — thêm cột mới, không xóa cột cũ, ghi cả hai:

ALTER TABLE users ADD COLUMN first_name text;
ALTER TABLE users ADD COLUMN last_name  text;
-- backfill từ dữ liệu cũ
UPDATE users SET first_name = split_part(full_name,' ',1),
                 last_name  = substr(full_name, position(' ' in full_name)+1);
// tầng ghi: giai đoạn quá độ ghi CẢ HAI để không lệch dữ liệu
function saveUser(u){
  writeFullName(u.first + ' ' + u.last);   // cũ
  writeNames(u.first, u.last);             // mới
}

Pha MIGRATE — đây là nơi agent tỏa sáng: chuyển từng nơi đọc full_name sang đọc first_name/last_name theo lô (Bài 3–6), mỗi lô verify. Vì cột cũ vẫn còn, lỡ sai một lô cũng không sập.

Pha CONTRACT — khi grep chứng minh không còn ai đọc/ghi full_name:

ALTER TABLE users DROP COLUMN full_name;

Vì sao không đổi "một phát"

Đổi thẳng (drop cột cũ + sửa hết code cùng lúc) tạo một big bang: mọi thứ phải đúng đồng thời, không có bước lùi, và không deploy nổi từng phần. Với hệ thống lớn hoặc nhiều service, gần như chắc chắn có nơi bị bỏ sót → sự cố production.

Với API công khai: thêm phiên bản, deprecate, rồi gỡ

v1: getUser() -> { fullName }        (giữ, đánh dấu deprecated)
v2: getUser() -> { firstName, ... }  (thêm mới)
-> theo dõi lưu lượng v1; khi về 0 hoặc hết hạn hỗ trợ -> gỡ v1

Giao agent nhiệm vụ MIGRATE mọi caller nội bộ sang v2, và cập nhật SDK/tài liệu — nhưng giữ v1 sống cho tới khi caller ngoài tầm kiểm soát cũng chuyển xong.

Feature flag để kiểm soát rủi ro

Bọc đường dẫn mới sau cờ để bật/tắt nhanh mà không deploy lại:

if (flags.useNewNames) return readNames();
return parseFullName(readFullName());

Bật cho 1% người dùng, quan sát, tăng dần. Có sự cố thì tắt cờ, không cần revert code.

Checklist migration có breaking change

[ ] Mỗi pha có deployable độc lập chưa?
[ ] EXPAND: cái mới thêm bên cạnh, chưa xóa cái cũ?
[ ] Có ghi kép (dual-write) / backfill để dữ liệu không lệch?
[ ] MIGRATE chia lô, verify từng lô, cái cũ vẫn là lưới đỡ?
[ ] Có cách đo "còn ai dùng cái cũ không" (grep + telemetry)?
[ ] CONTRACT chỉ chạy khi đã chứng minh về 0?
[ ] Có feature flag / rollback không cần deploy?

Sai lầm thường gặp

  • Big bang: xóa cái cũ và sửa hết cùng lúc — không có đường lùi, dễ sập.
  • Bỏ dual-write ở pha quá độ: dữ liệu mới/cũ lệch nhau, sinh bug khó lần.
  • CONTRACT quá sớm: xóa cái cũ khi vẫn còn caller (nhất là service khác, client mobile cũ) đang dùng.
  • Không đo lưu lượng cái cũ: đoán mò "chắc không ai dùng" rồi xóa → sự cố.
  • Đổi API công khai không version hóa: phá vỡ mọi tích hợp bên ngoài trong tích tắc.
Đã biết đổi hành vi mà không phá vỡ, bài cuối bàn chuyện vận hành thực tế: giám sát, chi phí, và xử lý khi agent đi chệch giữa một đợt lớn.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi