6 Lessons we learned when debugging a scaling problem on GitLab.com

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

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

Bối cảnh

  • Khách hàng báo lỗi git pull qua SSH chập chờn, chủ yếu từ CI và job tự động: ssh_exchange_identification: connection closed by remote host. Không tái hiện được, không thấy gì rõ trên graph hay log.
  • GitLab.com nhận khoảng 26–28 triệu kết nối Git-over-SSH/ngày (khoảng 300/giây trung bình, 2019); 16 frontend HAProxy chính + 2 cho cổng SSH thay thế, 27 backend SSH.

Diễn tiến điều tra

  1. Packet capture với IP của một khách bị lỗi thường xuyên: HAProxy chủ động đóng kết nối (FIN đúng chuẩn) ngay sau khi client gửi version string, nên lỗi nằm phía GitLab. (Tác giả tự nhận: lẽ ra nên dùng ngay bảng "Conversations" của Wireshark để lọc kết nối ít gói.)
  2. Log HAProxy (lưu trong BigQuery) có t_state = SD: backend đóng phiên. Capture giữa HAProxy và Git server: sshd tự đóng kết nối ngay sau bắt tay TCP.
  3. Gom lỗi theo giây trong phút: đỉnh trong 10 giây đầu mỗi phút, cao nhất khoảng giây 5–6, đỉnh trung bình gấp 3 lần nền. Tác giả gọi đây là "the tyranny of the clock": cron và scheduler của người dùng khắp internet chạy đúng đầu phút/giờ (NTP làm chúng đồng bộ hoàn hảo), chuẩn bị vài giây rồi cùng git fetch.
  4. Nguyên nhân: MaxStartups của sshd (số kết nối đang ở giai đoạn chưa xác thực). Mặc định 10:30:100, GitLab đã đặt 100:30:200. OpenSSH 7.2 chỉ báo vượt ngưỡng ở log mức Debug, nên bật Debug ngắn trên một máy để xác nhận.

Thay đổi chính

  • Tăng MaxStartups 50% (150:30:300): lỗi giảm mạnh, CPU không tăng. Lỗi còn lại dồn vào các mốc tròn: đầu giờ, mỗi 30, 15, 10 phút.
  • Rate limit phiên mới ở HAProxy (rate-limit sessions): kết nối vượt ngưỡng không bị từ chối mà chờ trên socket, client chỉ thấy bắt tay chậm hơn. Tính ngưỡng từ bốn đại lượng: số frontend, số backend, MaxStartups, và T = thời gian một phiên SSH qua được xác thực (ước 2 giây → khoảng 110/giây mỗi frontend). Triển khai xong không có tác dụng thấy được.
  • Phát hiện cân bằng tải lệch: HAProxy dùng balance source (cùng IP luôn vào cùng backend, không ai nhớ vì sao chọn), một giây có backend nhận 30, backend khác 121 kết nối; CPU các backend lệch 2:1. Đổi sang leastconn thì đều hẳn.
  • Tăng tiếp MaxStartups lên 250 thì lỗi gần như hết; suy ngược ra T thực khoảng 2,7–3,4 giây, dài hơn ước lượng. Sau khi triển khai chính thức leastconn, lỗi về 0.
  • Nhìn lại: dấu hiệu nhận diện chính xác là t_state = SD và client đọc 0 byte; ở đỉnh vấn đề khoảng 1,5% kết nối SSH bị rớt, lớn hơn nhiều so với tưởng ban đầu. Đo sớm thì đã ưu tiên sớm hơn.
  • Giai đoạn xác thực chậm một phần vì tra SSH key qua API nội bộ, mà các worker unicorn trên máy Git bị xếp hàng; việc tối ưu tiếp tục.

Bài học (theo GitLab)

  • Hệ phức tạp có nhiều đòn bẩy cho cùng một nút thắt, mỗi cái có đánh đổi riêng; ước lượng sai một đại lượng (T) làm cả phép tính sai.
  • Cần cảnh báo trên chính dấu hiệu lỗi trong log HAProxy, vì đáng ra nó không bao giờ xảy ra.
  • Lời khuyên cho người dùng dịch vụ tập trung: thêm sleep ngẫu nhiên (khoảng 30 giây) hoặc chọn phút ngẫu nhiên trong giờ cho job định kỳ.

Bài học cho SWE

  • Thundering herd không chỉ đến từ cache hết hạn; lịch tròn của con người cũng tạo đỉnh đồng bộ ở phía nhà cung cấp (Hot partition và thundering herd).
  • Cho kết nối mới chờ (rate limit ở proxy) dịu hơn là từ chối; nhưng nó chỉ hiệu quả khi tải đã chia đều (Load shedding, rate limit và backpressure).
  • Cấu hình "sticky" không ai nhớ lý do là món nợ; định lượng lỗi từ đầu (bao nhiêu % bị ảnh hưởng) quyết định mức ưu tiên điều tra (Sự cố và postmortem).

Liên quan