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.