How We Replaced Our Data Pipeline With Zero Downtime

Bài tóm tắt Twilio 2014 2. Tăng trưởng Độ khó: Trung bình

Twilio · 2014 · 2. Tăng trưởng · Độ khó: Trung bình — bài gốc

Bối cảnh

  • Mỗi cuộc gọi hay tin nhắn đi qua các trạng thái in-flight (đổ chuông, đang gọi, xếp hàng), rồi thành bản ghi post-flight để lưu lâu dài và phân tích. Trước đây cả hai nằm chung một cụm MySQL.
  • Ba vấn đề:
    • Tối ưu cho mọi thứ là không tối ưu cho gì: in-flight cần ghi nhiều, post-flight cần đọc nhiều mà không sửa;
    • DB dùng chung bị nhiều client truy cập trực tiếp: thành điểm hỏng chung, khó biết lỗi của ai, mọi client gắn chặt vào công nghệ lưu trữ;
    • giới hạn scale dọc: dữ liệu gần chạm giới hạn một node; replication đơn luồng của slave bị trễ quá ngưỡng nhất quán.

Thay đổi chính

  • Đặt service trước DB để chỉ một service biết cách lưu và shard.
  • Tách in-flight và post-flight ra hai kho.
  • Chống "nhân tiện sửa luôn": họ cân nhắc đổi công nghệ DB (MySQL thiếu shard và HA sẵn có) nhưng giữ MySQL để giới hạn phạm vi.
  • Khoá shard: AccountSid, vì hầu hết query nằm trong một account. Ánh xạ kết hợp range + lookup: range để cấu hình gọn, lookup để tách riêng một account rất lớn.
  • Rollout trong vài tháng, không downtime (với công ty viễn thông, downtime là rớt cuộc gọi):
    1. Shadow (công cụ tự viết, mã nguồn mở): proxy gửi cùng request tới bản ổn định và bản mới, báo khác biệt, chỉ trả kết quả bản ổn định. Họ chạy shadow topology mới khoảng một tháng.
    2. Account Flags: bit trong DB quyết định request đi đường code nào, để bật dần theo khách.
    3. Đặt service abstraction (in-flight, post-flight, aggregator) giữa client (API, Web, Billing) và DB.
    4. Tách DB in-flight: chỉ cần trỏ service sang DB mới.
    5. Shard DB post-flight:
      • định tuyến đọc và ghi riêng, để không có lúc một luồng ghi shard mới trong khi luồng khác còn đọc shard cũ;
      • shard mới làm slave của cụm cũ, nên có đủ dữ liệu ngay khi chuyển;
      • chuyển đọc trước, ghi sau;
      • tránh trùng AUTO_INCREMENT khi cả hai master cùng nhận ghi: shard0 sinh số lẻ, shard1 số chẵn (auto_increment_offset/increment), rồi cắt replication;
      • xoá phần dữ liệu không còn thuộc shard và rebuild bảng để thu hồi dung lượng.

Bài học (theo Twilio)

  • Hiệu ứng cánh bướm: quyết định thiết kế sớm có hậu quả lâu dài; hãy hỏi ở 10x, 100x thì sao.
  • SOA giúp phân rã theo quyền sở hữu; chỗ nào mơ hồ về chủ sở hữu thì còn tách được nữa.
  • Đổi kiến trúc một hệ có trạng thái đang chạy rất phức tạp; cần công cụ (shadow), tự động hoá và "tay chậm mà chắc".

Bài học cho SWE

  • Tách dữ liệu theo vòng đời (đang chạy và đã xong) trước khi shard: mỗi phần có mẫu truy cập khác nhau (Chọn database theo workload).
  • Service trước DB là bước chuẩn bị để shard mà không phải sửa mọi client (Sharding và tách database).
  • Sinh ID xen kẽ chẵn/lẻ là mẹo tạm thời để hai master cùng ghi trong lúc chuyển (Sinh ID phân tán).
  • Shadow proxy năm 2014 là tiền thân của các kỹ thuật shadow/parallel run sau này (Grab 2016, Zalando 2021) (Migration không downtime).

Liên quan