How we seamlessly migrated high volume real-time streaming traffic with zero data loss and duplication

Bài tóm tắt Grab 2024 3. Quy mô lớn Độ khó: Trung bình

Grab · 2024 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Bối cảnh

  • Grab tách phần đọc và phần ghi của một backend service thành hai service riêng để scale độc lập.
  • Bài kể việc chuyển phần xử lý stream (ghi) sang service mới: trung bình 20.000 lượt đọc/giây từ 16 Kafka stream, ghi ra các stream khác và nhiều bảng DynamoDB.
  • Yêu cầu: chuyển từng stream một; kiểm chứng service mới trên production trước khi chuyển hẳn; không mất, không trùng dữ liệu, không downtime.

Thay đổi chính

  • Loại feature flag (đã có nền tảng config/experiment): trong vài phút deploy, flag có thể được cập nhật ở hai service vào hai thời điểm hơi lệch nhau, nên có cửa sổ mất hoặc trùng dữ liệu.
  • Chọn chuyển theo mốc thời gian, viết trong code dùng chung:
    1. Chuyển code xử lý stream vào thư mục /commons của monorepo; cả service cũ lẫn mới import cùng một code, có logic bật/tắt theo service.
    2. Tạo tài nguyên "sink" tạm (stream, bảng DynamoDB) để service mới ghi vào và được kiểm chứng trên production mà không đụng tài nguyên chính.
    3. Một map stream → thời điểm chuyển nằm trong code chung. Đến giờ đó, service mới bắt đầu ghi vào tài nguyên chính và service cũ dừng ghi, không chồng lấn, không có khoảng trống.
    4. Deploy cả hai service trước mốc giờ một khoảng an toàn; có monitor riêng cho migration. Service cũ vẫn đọc stream thêm một thời gian nhưng không ghi, rồi mới gỡ hẳn.

Kết quả

Không mất, không trùng dữ liệu, không downtime. Toàn bộ, kể cả chuyển dần từng stream, mất khoảng 3 tuần.

Vì sao

Theo Grab: cách chuyển theo mốc thời gian đảm bảo hai bên đổi trạng thái cùng lúc, điều mà flag cập nhật lệch nhau không làm được.

Bài học cho SWE

  • Hạn chế Grab tự nêu: nếu phải rollback một trong hai service thì không tránh được sai lệch dữ liệu. Logic theo thời gian nguy hiểm khi hai hệ thống lệch nhau; họ chỉ dùng vì đây là biện pháp tạm và đã test kỹ.
  • Kiểm chứng bằng "sink" tạm là biến thể của shadow traffic cho luồng ghi, giống ý tưởng mock DAO năm 2016 (Grab 2016).
  • Monorepo + code dùng chung làm cho hai service hành xử giống hệt nhau tại thời điểm chuyển.

Liên quan