How we scaled async workload processing at GitLab.com using Sidekiq

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

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

Bối cảnh

  • GitLab (Rails) xử lý việc nền bằng Sidekiq trên Redis. Lịch sử: 2013 mỗi worker class một queue, không ai quản; 2016 gom theo tính năng; 2017 lại mỗi worker class một queue, gom thành nhóm realtime, asap, besteffort để giám sát và giới hạn từng queue. Tác giả Sidekiq khuyên không quá vài queue (M process poll N queue = O(M×N) thao tác/giây lên Redis); GitLab chấp nhận đánh đổi này gần hai năm.
  • Giữa 2019 có nhiều sự cố lớn quanh hàng đợi nền: gọi Gitaly n+1 làm queue phình; một user tạo rất nhiều note trên một commit làm chậm queue gửi thông báo; job xử lý pipeline dồn, node pipeline hết CPU, pool pgbouncer của Sidekiq bão hoà.

Thay đổi chính

  • Trạng thái cũ: quan hệ nhiều-nhiều giữa job và role (một job chạy trên tới ba loại node); worker thread dành riêng cho từng queue (một phần tám fleet realtime cho new_note: dồn job thì phần lớn fleet ngồi chơi, thêm node chỉ thêm một phần tám năng lực); job gấp và job chậm (tới 10 phút) chung một queue.
  • Sắp lại: mỗi job chạy trên đúng một role; mọi thread chạy mọi job của role nên dồn job thì dùng được 100% node; tách job nhanh và chậm. Nhưng đó là ảnh chụp một thời điểm, sẽ lỗi thời khi code đổi.
  • Phân loại workload ngay trong code và dùng queue selector (chọn queue theo thuộc tính thay vì theo tên): developer khai báo urgency (high/low/none), có phụ thuộc ngoài không (ví dụ cluster Kubernetes của user), cpu-bound hay memory-bound (quyết định mức đồng thời trong process Ruby, hoặc đưa job tốn RAM lên node ít thread nhiều RAM).
  • SLA tách trách nhiệm: job high chờ không quá 10 giây và chạy không quá 10 giây; low chờ không quá 1 phút; không urgency thì không SLA chờ; job không phải high chạy không quá 5 phút. SLA chờ là việc của đội hạ tầng (cấp đủ fleet), SLA chạy là việc của đội sở hữu worker.
  • Redis: node catchall theo dõi khoảng 200 queue; besteffort có 7 node × 8 process × 15 thread = 840 client cùng BRPOP trên nhiều key, rất tốn thời gian theo chuẩn Redis. GitLab báo lỗi hiệu năng; antirez trả lời trong một giờ, có bản vá cùng ngày (Redis 6, backport Redis 5).
  • Xong chuyển selector cuối 4/2020: fleet Sidekiq từ 49 node/314 CPU xuống 26 node/158 CPU, utilization đều hơn. Sidekiq Cluster thành mặc định ở bản 13.0 để môi trường dev giống production. Tiếp theo: giảm số queue phải theo dõi.

Vì sao

  • Mục tiêu chịu 100 lần traffic lúc đó và để vận hành "không bất ngờ": ai cũng hiểu job chạy ở đâu, vì sao dồn, làm sao giảm; hạ tầng không phải định tuyến job bằng tay theo sự cố.

Bài học cho SWE

  • Dành thread cố định cho từng queue làm fleet không co giãn được khi một loại job bùng nổ.
  • Đừng trộn job gấp với job chậm trong cùng hàng đợi ("làn 10 món" phải được giữ).
  • Đẩy thông tin về workload vào khai báo của chính job để định tuyến tự động khỏi lỗi thời.
  • Nhiều queue × nhiều client có chi phí ẩn trên broker (ở đây là BRPOP của Redis).

Liên quan