Bulkhead Mengisolasi Concurrency Sebelum Satu Dependency Menghabiskannya
Sebuah service dapat memiliki CPU dan memori yang cukup tetapi berhenti membuat progres berguna karena concurrency habis. Thread, koneksi database, outbound socket, worker slot, dan permit request in-flight bersifat finite. Jika satu dependency melambat, call menuju dependency tersebut dapat memenuhi seluruh shared pool.
Bulkhead isolation membagi kapasitas itu sebelum saturation terjadi. Workload yang dapat gagal secara independen memperoleh anggaran concurrency terpisah, sehingga tekanan pada satu jalur tidak otomatis memakai setiap slot yang diperlukan jalur lain.
Shared concurrency menghubungkan jalur yang tidak berkaitan
Perhatikan API dengan 100 worker slot. Endpoint A memanggil reporting service yang lambat; endpoint B membaca local cache yang sehat.
shared workers = 100
A requests -> reporting service
B requests -> cacheJika 100 request A terblokir pada reporting service, B dapat ikut menunggu walaupun dependency miliknya sehat. Shared pool telah mengubah slowdown lokal menjadi starvation di seluruh service.
Masalahnya bukan B menjadi mahal. B kehilangan akses ke execution capacity yang dibutuhkannya.
Bulkhead memberi anggaran terpisah
Service dapat mencadangkan permit independen:
reporting calls: max 60 in flight
cache calls: max 30 in flight
other work: max 10 in flightSetelah reporting mencapai 60 call concurrent, pekerjaan reporting tambahan menunggu sesuai kebijakan terbatas atau menerima hasil overload. Kapasitas yang tersisa tetap tersedia bagi jalur lain.
Semaphore sering cukup untuk isolasi dalam satu proses:
if reporting_permits.try_acquire() == false:
return overloaded
try:
call_reporting_service()
finally:
reporting_permits.release()Worker pool, connection pool, queue, process group, atau replica service terpisah dapat memberi isolasi lebih kuat ketika batas resource memerlukannya.
Limit perlu berada dekat resource yang langka
Bulkhead efektif ketika permit mewakili resource yang berisiko habis. Membatasi request handler menjadi 50 tidak banyak membantu jika setiap handler dapat menjalankan sepuluh query database secara concurrent.
Boundary yang relevan dapat berupa:
outbound calls per dependency
database connections per workload
concurrent jobs per tenant
CPU-heavy tasks per process
in-flight requests per endpoint classSatu request dapat melewati beberapa boundary tersebut. Setiap titik akumulasi memerlukan limit yang sesuai dengan resource yang dilindunginya.
Waktu tunggu tetap memerlukan batas
Concurrency limit dapat memindahkan tekanan ke waiting queue. Jika queue itu tidak terbatas, service hanya menukar slot exhaustion dengan pertumbuhan latency dan penggunaan memori.
Bulkhead yang praktis karena itu memasangkan concurrency cap dengan admission rule:
active <= concurrency limit
waiting <= queue limit
wait time <= deadlineKetika seluruh anggaran tersebut habis, rejection merupakan hasil yang terkendali. Terus menerima pekerjaan yang tidak dapat dimulai dalam masa gunanya hanya menyembunyikan saturation.
Isolasi memiliki biaya utilisasi
Partisi statis dapat menyisakan kapasitas idle. Reporting dapat memakai seluruh 60 permit sementara 20 permit cache tidak terpakai. Fully shared pool tampak lebih efisien pada saat itu.
Headroom yang tidak terpakai tersebut merupakan bagian dari kontrak isolasi. Kapasitas itu dipertahankan untuk traffic yang belum tiba.
Sistem dapat memakai elastic reservation, weighted limiter, atau aturan borrowing, tetapi borrowing memerlukan mekanisme reclaim. Jika satu class dapat terus memakai reserve milik class lain, isolasi hilang tepat saat overload berkelanjutan terjadi.
Limit perlu mengikuti failure domain
Membuat satu pool untuk setiap endpoint tidak otomatis berguna. Partisi perlu mengikuti resource dan perilaku kegagalan.
Dua endpoint yang memanggil database yang sama dapat berada dalam anggaran concurrency database yang sama. Satu endpoint yang memanggil tiga external service independen dapat memerlukan limiter terpisah untuk setiap dependency.
Isolasi tenant juga dapat penting. Tanpa limit per tenant, lonjakan satu customer dapat menghabiskan shared downstream quota dan menunda customer lain.
Boundary bulkhead perlu mempertahankan kapasitas bagi pekerjaan yang harus tetap berjalan ketika class lain melambat atau jenuh.
Timeout dan circuit breaker menangani masalah yang berdekatan
Timeout membatasi durasi call menunggu penyelesaian. Circuit breaker dapat menghentikan call ke dependency setelah kebijakan failure terpicu. Bulkhead membatasi jumlah concurrency yang boleh dipakai dependency sebelum kedua mekanisme tersebut menyelesaikan situasi.
Kontrol tersebut saling memperkuat, tetapi tidak saling menggantikan.
Dependency dapat cukup lambat untuk memenuhi seluruh worker tetapi tetap mengembalikan response sukses sebelum timeout yang longgar. Circuit breaker dapat tetap closed karena response secara teknis berhasil. Concurrency bulkhead tetap mencegah jalur tersebut membawa seluruh service ke kondisi jenuh.
Metrik memerlukan data saturation dan rejection
Telemetry bulkhead yang berguna mencakup:
active permits
permit capacity
waiter count
permit wait duration
admission rejections
operation latency
timeout count
completion rateLimiter yang terus berada pada capacity dalam waktu lama menunjukkan tekanan berkelanjutan walaupun error rate tetap rendah. Permit wait duration menampilkan latency tambahan sebelum pekerjaan dimulai.
Metrik perlu mempertahankan dimensi workload atau dependency yang dipakai kebijakan isolasi. Menggabungkan seluruh pool menjadi satu angka utilisasi menyembunyikan boundary yang hendak ditampilkan bulkhead.
Test perlu menahan satu dependency
Resilience test yang terarah dapat sengaja menahan call menuju dependency A sambil meneruskan traffic melalui dependency B:
penuhi bulkhead A
kirim traffic A tambahan -> rejection atau wait terbatas
kirim traffic B -> tetap berjalan dalam anggaran B
lepaskan A
verifikasi recoveryAssertion utama adalah isolasi: habisnya alokasi A tidak boleh memakai execution capacity yang dicadangkan untuk B.
Bulkhead tidak menambah total kapasitas. Mekanisme ini menentukan kegagalan mana yang boleh bersaing memakainya. Dengan menetapkan concurrency finite sebelum saturation, service dapat mencegah satu dependency lambat, tenant yang terlalu aktif, atau workload mahal mengubah masalah kapasitas lokal menjadi starvation global.