Preview Bản nháp chờ review — title, nội dung, link có thể sửa trước khi bỏ nhãn.

Tàu: vách ngăn (bulkhead) — một khoang ngập không chìm cả thân. Service: search chậm 30s không được nuốt hết thread / pool / event-loop budget của payment.

Circuit breaker: ngừng gọi khi đỏ. Bulkhead: dù chưa đỏ, không cho một dependency ôm sạch resource dùng chung.

Không bulkhead — chuyện gì xảy ra

Shared thread pool 100
Search dependency latency 20s
→ 100 request search ôm hết pool
→ Payment (nhanh) xếp hàng / timeout
→ “Cả app chết” dù payment DB khỏe

Trên Node một event loop: sync/block hoặc quá nhiều in-flight chờ search cũng làm p99 toàn cục — bulkhead + timeout + offload vẫn cần tư duy “khoang”.

Cách làm

  • Pool / semaphore riêng theo dependency: paymentPool=20, searchPool=10.
  • HTTP client / DB pool riêng (connection inventory) — xem connection pool.
  • Queue / worker riêng cho job hạng nặng (email, report).
  • Timeout + cancel trong từng khoang — đừng để slot treo.

Bulkhead + breaker + shed

Bulkhead  — giới hạn resource per dependency
Breaker   — ngừng gọi khi fail rate cao
Shed      — từ chối request mới khi khoang đầy
            (load-shedding, 503)

Ba lớp bổ sung, không thay nhau. Chỉ breaker: vẫn có thể 100 call chậm trước khi mở. Chỉ bulkhead: vẫn đập dependency đang chết nếu không fail-fast.

Chọn kích thước khoang

  • Theo SLA dependency và budget timeout (bao nhiêu call song song chịu nổi).
  • Theo inventory hẹp nhất (memory, FD).
  • Để headroom cho khoang critical (auth, pay) > nice-to-have.

Node / một event loop

Không có “thread pool HTTP” kiểu servlet: bulkhead thường là semaphore per dependency (max in-flight search = 10) + timeout + cancel. Sync CPU trên loop vẫn xuyên mọi khoang — event loop / offload.

// Ý: không start search thứ 11 khi 10 đang bay
await searchSem.acquire()
try { return await search(q, { signal }) }
finally { searchSem.release() }

Một câu để nhớ

Cô lập tài nguyên theo dependency — chậm một khoang, không chìm cả process.
Breaker ngắt gọi; bulkhead giới hạn chỗ đứng.