Backpressure (bài 5) chặn nguồn khi hàng đợi đầy — hợp với producer nội bộ chờ được. Nhưng với request từ client, bắt họ chờ 5 giây rồi vẫn timeout thì tệ hơn từ chối thẳng. Load shedding là lựa chọn đó: khi tải vượt khả năng, từ chối bớt request ngay (trả 503) để những request được nhận vẫn được phục vụ nhanh. Nghe có vẻ tàn nhẫn — bỏ một phần khách — nhưng phương án còn lại (nhận hết) khiến tất cả cùng chậm và có khi sập cả hệ. Bài này (phần 6 loạt Hệ thống chịu tải) chạy thật để thấy con số.

Cơ chế: giới hạn in-flight, đầy thì từ chối ngay

Ý tưởng: giới hạn số request đang xử lý đồng thời (in-flight). Vượt ngưỡng → trả 503 ngay, không xếp hàng. Trong Go, dùng một semaphore (channel có buffer) với thao tác thử lấy không chờ:

sem := make(chan struct{}, 10)  // capacity: 10 request đồng thời
select {
case sem <- struct{}{}:   // còn slot -> xử lý
    time.Sleep(work); <-sem
default:                   // ĐẦY -> trả 503 NGAY, KHÔNG xếp hàng
    shed++                // từ chối sớm để không tích hàng đợi
}

Khác biệt cốt lõi với backpressure: backpressure chặn (nguồn chờ), load shedding từ chối (không chờ). Với client, "nhận 503 sau 1ms" tốt hơn nhiều "chờ 5 giây rồi timeout".

Ảnh chụp đoạn mã Go nền tối minh hoạ load shedding, vấn đề nhận hết khi quá tải mọi request đều chậm tải vượt capacity nếu nhận hết vào hàng đợi hàng đợi dài request nào cũng chờ lâu p99 nổ có khi sập công bằng kiểu này ai cũng khổ không ai được phục vụ tử tế, cơ chế giới hạn in-flight đầy thì shed 503 ngay sem make chan struct 10 capacity 10 request đồng thời select case sem nhận struct còn slot xử lý time Sleep work nhận sem default đầy trả 503 ngay không xếp hàng shed cộng từ chối sớm để không tích hàng đợi, khác backpressure bài 5 shed bằng từ chối không chờ backpressure đầy chặn nguồn nguồn chờ hợp producer nội bộ load shedding đầy từ chối 503 ngay hợp request client client nhận 503 nhanh còn hơn chờ 5 giây rồi vẫn lỗi timeout

Hình 1: Load shedding giới hạn số request in-flight bằng semaphore; đầy thì trả 503 ngay không xếp hàng — khác backpressure (chặn/chờ), shed là từ chối, hợp với request client.

Đo thật: p99 4,8s vs 21ms

Mình giả lập server capacity 10 slot, mỗi request 20ms, bắn tải 2000 request (vượt xa capacity):

Ảnh chụp bảng kết quả chạy thật load shedding output thật, một không shedding xếp hàng chờ slot nhận hết 2000 request p50 2.507s p99 4.827s max 4.881s mọi request chậm dần p99 nổ vì hàng đợi dài ai cũng khổ, hai có shedding semaphore đầy trả 503 ngay nhận 1246 shed 503 754 tỉ lệ nhận 62 phần trăm độ trễ request được nhận p50 21ms p99 21ms max 22ms phần được nhận vẫn nhanh xấp xỉ thời gian xử lý 20ms phần thừa bị bỏ sớm, ý nghĩa không shedding nhận hết p99 4.8s mọi người chờ mòn mỏi có shedding 62 phần trăm được phục vụ ở 21ms 38 phần trăm nhận 503 nhanh không chờ vô ích hy sinh một phần để cứu phần còn lại giữ độ trễ thấp, kết luận shed giữ p99 của request được nhận thấp 21ms vs 4.8s đánh đổi bỏ một phần request 503 client phải xử lý thử lại chọn ngưỡng in-flight theo capacity thật ưu tiên request quan trọng shed loại thấp trước health check nhỏ hơn thanh toán khác backpressure bài 5 chờ shed từ chối ngay hợp client

Hình 2: Chạy thật — không shedding: 2000 request nhận hết, p50=2,5s, p99=4,8s (ai cũng chậm); có shedding: nhận 1246 (62%), shed 754, độ trễ request được nhận p50=p99=21ms (~thời gian xử lý).

Đọc kết quả đo được:

  • Không shedding: p99 nổ tung, ai cũng khổ: nhận hết 2000 request, tất cả xếp hàng chờ 10 slot. Kết quả: p50=2,5 giây, p99=4,8 giây. Không request nào bị từ chối, nhưng mọi request đều chậm khủng khiếp — thứ tự phục vụ "công bằng" này thực ra khiến ai cũng nhận trải nghiệm tệ, và với client thật thì hầu hết đã timeout từ lâu.
  • Có shedding: 62% nhanh, 38% bị từ chối ngay: giới hạn in-flight 10, nhận 1246 request (62%), shed 754 (38%) với 503. Điều quan trọng: độ trễ của request được nhận là p50=p99=21ms — gần bằng đúng thời gian xử lý (20ms), không có chờ đợi thừa. Hệ phục vụ tốt phần nó có thể phục vụ.
  • Bản chất: hy sinh một phần để cứu phần còn lại: đây là đánh đổi trung tâm của load shedding. Thay vì để quá tải làm hỏng trải nghiệm của tất cả (và có khi sập), ta chấp nhận từ chối một phần sớm và rõ ràng để phần còn lại được phục vụ đúng chất lượng. 62% khách hài lòng + 38% nhận lỗi nhanh (có thể thử lại) tốt hơn 100% khách chờ mòn mỏi.

Đánh đổi cần cân nhắc

Shed thông minh: ưu tiên request quan trọng, bỏ loại thấp trước. Không phải mọi request đáng giá như nhau. Khi phải bỏ, nên bỏ theo thứ tự ưu tiên: health check nội bộ, prefetch, analytics có thể bỏ trước; thanh toán, đăng nhập giữ lại. Cài đặt: phân loại request và dùng ngưỡng khác nhau (hoặc hàng riêng) cho từng loại — khi tải cao, loại thấp bị shed ở ngưỡng thấp hơn. Shed mù (bỏ ngẫu nhiên) tốt hơn không shed, nhưng shed theo ưu tiên tốt hơn nhiều.

Chọn ngưỡng in-flight theo capacity thật, không theo phỏng đoán. Ngưỡng quá cao → shed quá muộn, độ trễ đã nổ trước khi shed kích hoạt. Quá thấp → shed oan khi hệ còn chịu được. Cách đúng: đo số request đồng thời tối đa mà hệ giữ được độ trễ chấp nhận (như các bài đo throughput), đặt ngưỡng quanh đó. Nâng cao hơn: adaptive load shedding — tự điều chỉnh ngưỡng theo độ trễ đo được thời gian thực (khi p99 tăng thì shed mạnh hơn).

Load shedding và backpressure bổ sung nhau, không thay thế. Chúng là hai cách xử lý "đầy": backpressure chặn/chờ (hợp luồng nội bộ, producer chịu được chậm), load shedding từ chối (hợp request client cần phản hồi nhanh). Một hệ tốt dùng cả hai ở các tầng khác nhau: shed ở biên (từ chối client thừa sớm), backpressure ở trong (điều tiết pipeline nội bộ). Và cả hai đều cần đo — không có con số thì không biết ngưỡng đúng.

Ba ý mang về

  1. Nhận hết khi quá tải khiến mọi request chậm: đo thật 2000 request không shedding có p50=2,5s, p99=4,8s — "công bằng" kiểu xếp hàng khiến ai cũng khổ và client thật đã timeout.
  2. Load shedding giữ p99 thấp cho phần được nhận: đo thật có shedding phục vụ 62% ở p99=21ms (gần bằng thời gian xử lý), 38% nhận 503 ngay — hy sinh một phần để cứu phần còn lại + giữ độ trễ.
  3. Shed theo ưu tiên, ngưỡng theo capacity thật, kết hợp backpressure: bỏ loại thấp trước (health check < thanh toán); đặt ngưỡng in-flight quanh capacity đo được (adaptive càng tốt); shed ở biên + backpressure ở trong.

Nguồn

Phần sau ta gặp cách kiểm soát song song ở phía xử lý: worker pool — giới hạn số goroutine làm việc đồng thời để không tạo vô hạn goroutine; đo thật throughput theo số worker và tìm điểm bão hòa.