Batas Concurrency Adaptif Mengikuti Kapasitas Service

Sebuah service dapat melambat sebelum benar-benar tidak tersedia. Ketika pekerjaan in-flight bertambah, antrean CPU membesar, connection pool terisi, lock contention meningkat, dan panggilan downstream menumpuk. Batas concurrency tetap dapat melindungi service, tetapi satu angka jarang cocok untuk setiap kondisi operasi. Kapasitas berubah mengikuti campuran request, cache hit rate, latency dependency, bentuk deployment, dan tekanan resource.

Kontrol concurrency adaptif memperlakukan batas admission sebagai nilai yang dapat bergerak. Controller mengamati perilaku service terbaru, menaikkan batas selama concurrency tambahan masih produktif, lalu menurunkannya ketika latency menunjukkan antrean yang membesar atau saturation. Sasarannya bukan concurrency maksimum, melainkan pekerjaan paralel yang cukup untuk memakai kapasitas tersedia tanpa membiarkan antrean mendominasi response time.

Concurrency berbeda dari request rate

Rate limit membatasi kedatangan dalam rentang waktu. Concurrency limit membatasi pekerjaan yang sedang berlangsung.

Service yang menerima 1.000 request per detik dengan service time 10 ms membawa sekitar 10 request secara concurrent saat workload stabil. Jika service time naik menjadi 200 ms pada arrival rate yang sama, concurrency dapat mendekati 200. Request rate tidak berubah, tetapi kapasitas yang sedang terpakai meningkat tajam.

Perbedaan ini penting ketika dependency melambat. Rate limiter dapat terus menerima traffic pada rate yang sebelumnya aman sementara setiap request menahan socket, memory, database connection, atau worker slot jauh lebih lama. Concurrency limiter bereaksi langsung terhadap occupancy tersebut.

Kedua kontrol dapat dipakai bersamaan. Rate limiting cocok untuk quota, fairness, dan burst policy. Concurrency limiting cocok untuk membatasi tekanan pada kapasitas eksekusi yang terbatas.

Batas tetap menyimpan satu asumsi kapasitas

Semaphore statis sederhana:

if in_flight < limit:
    terima request
else:
    tolak atau shed request

Perlindungannya nyata, tetapi limit yang dikonfigurasi mewakili asumsi tentang kapasitas service. Terlalu tinggi, overload tetap menghasilkan antrean panjang. Terlalu rendah, kapasitas sehat dibiarkan menganggur.

Nilai aman juga dapat berubah setelah deployment. Query plan baru, instance yang lebih besar, cache yang lebih dingin, atau downstream yang lebih lambat dapat menggeser titik operasi yang berguna tanpa perubahan konfigurasi.

Kontrol adaptif mempertahankan boundary admission yang sama, tetapi mengubah limit dari perilaku yang teramati. Mekanisme menjadi responsif, bukan mampu menebak kondisi masa depan. Controller tetap dapat terlambat bereaksi, berosilasi, atau mengikuti sinyal bising jika tuning-nya buruk.

Latency minimum dapat menjadi referensi

Queueing delay sering tampak sebagai latency di atas baseline saat load rendah. Controller dapat menyimpan estimasi minimum round-trip atau service latency terbaru lalu membandingkan sample saat ini dengan referensi tersebut.

Sinyal sederhana dapat berbentuk:

baseline = latency rendah terbaru
sample   = latency saat ini

queue_signal = sample / baseline

Ketika sample tetap dekat dengan baseline, concurrency tambahan mungkin masih produktif. Ketika rasionya naik secara berarti, request kemungkinan menghabiskan lebih banyak waktu menunggu resource yang terbatas.

Baseline perlu diperbarui dengan hati-hati. Nilai yang disimpan selamanya dapat menjadi usang setelah perubahan infrastructure atau workload. Pembaruan yang terlalu agresif dapat menyerap queueing berkepanjangan ke baseline sehingga overload terlihat normal.

Latency juga bukan metric antrean murni. Garbage collection, network delay, variasi downstream, storage stall, dan request mix dapat menggesernya. Controller produksi karena itu memerlukan smoothing dan perubahan yang dibatasi, bukan menganggap satu sample sebagai bukti saturation.

Naikkan perlahan, kurangi tekanan dengan cepat

Bentuk kontrol yang umum menyerupai additive increase dengan penurunan yang lebih cepat. Selama periode stabil dengan latency rendah, limit naik dalam langkah kecil. Ketika sinyal antrean melewati threshold, controller mengurangi kapasitas pada admission window dengan lebih tegas.

interval sehat:
    limit = limit + small_step

interval padat:
    limit = max(min_limit, limit * reduction_factor)

Ini merupakan bentuk policy, bukan formula universal. Sampling interval, percentile atau aggregate latency signal, step size, reduction factor, dan floor semuanya memengaruhi perilaku.

Kenaikan besar dapat melewati kapasitas dan membentuk antrean sebelum feedback tiba. Langkah yang sangat kecil membuat pemulihan lambat setelah tekanan sementara. Penurunan agresif melindungi latency, tetapi dapat menyisakan kapasitas jika sinyal hanya spike sesaat.

Controller juga memerlukan batas maksimum. Bug pada feedback atau latency yang sangat rendah tidak boleh membuat limit tumbuh tanpa kendali.

Admission perlu terjadi sebelum pekerjaan mahal

Concurrency limit paling efektif ketika diterapkan dekat titik saat pekerjaan mulai memakai resource yang dilindungi. Jika request lebih dulu masuk ke antrean aplikasi yang besar dan baru kemudian memperoleh limiter, antrean sudah telanjur terbentuk.

Untuk HTTP service, admission dapat terjadi dekat request boundary:

request
  |
  +-- permit tersedia --> eksekusi --> lepas permit
  |
  +-- tanpa permit -----> response overload

Request yang ditolak sebaiknya gagal dengan biaya rendah. Membaca request body besar, membuka database transaction, atau memulai downstream call sebelum admission mengurangi manfaat perlindungan.

Permit juga harus dilepas pada setiap completion path: success, application error, cancellation, dan timeout. Permit yang bocor mengubah failure sementara menjadi kehilangan kapasitas buatan.

Limit terpisah dapat melindungi bottleneck yang berbeda

Satu limit global mengasumsikan semua request memakai kapasitas dengan pola yang kurang lebih sama. Service nyata sering memiliki beberapa workload class.

Endpoint report dapat menahan database connection selama ratusan milidetik, sedangkan endpoint metadata dari cache memakai sedikit CPU tanpa database connection. Berbagi satu limit dapat membuat pekerjaan mahal menyingkirkan pekerjaan ringan.

Boundary yang berguna dapat mencakup:

concurrency seluruh service
operasi berat pada database
remote API call
pekerjaan per tenant
background job

Setiap limiter sebaiknya terkait dengan resource atau sasaran isolasi yang dapat dijelaskan operator. Terlalu banyak controller yang saling berinteraksi membuat perilaku sistem sulit didiagnosis.

Admission hierarkis sering lebih jelas: satu request mungkin memerlukan global permit sekaligus permit untuk satu dependency langka. Pola ini mencegah satu dependency menghabiskan seluruh budget service sambil mempertahankan batas keseluruhan.

Retry tidak boleh memperbesar shedding

Response overload sering memicu retry. Jika setiap request yang ditolak langsung mengembalikan response dan setiap client langsung mencoba lagi, adaptive shedding dapat berubah menjadi retry loop yang mempertahankan tekanan arrival.

Client memerlukan retry policy terbatas dengan backoff dan jitter. Server dapat memakai status overload eksplisit seperti HTTP 429 atau 503 sesuai kontrak API, serta dapat memberikan waktu retry ketika informasi tersebut memang bermakna.

Limiter perlu menghitung pekerjaan yang diterima, bukan percobaan yang ditolak, sebagai in-flight service load. Metric tetap perlu mencatat rejection agar operator dapat membedakan eksekusi sehat dari demand yang di-shed.

Concurrency controller dan retry policy membentuk satu feedback system. Tuning salah satunya secara terpisah dapat menyembunyikan amplifikasi pada boundary keduanya.

Amati limit bersama latency dan utilization

Limit yang bergerak merupakan operational state. Hanya mengekspor request latency membuat bagian penting controller tidak terlihat.

Telemetry yang berguna mencakup:

current concurrency limit
current in-flight requests
admitted request count
shed request count
baseline latency estimate
sampled latency signal
controller increase/decrease events
resource utilization

Hubungan antar nilai tersebut lebih informatif daripada satu metric. Limit yang turun bersamaan dengan latency yang naik dapat menunjukkan tekanan nyata. Limit yang turun saat utilization rendah dapat menunjukkan sinyal bising, bottleneck pada dependency, atau masalah konfigurasi controller.

Dimensi per route atau workload class dapat membantu ketika admission dipartisi, tetapi label dengan cardinality tinggi perlu tetap dibatasi.

Kontrol adaptif tidak menciptakan kapasitas

Limiter memutuskan pekerjaan mana yang masuk; limiter tidak membuat code lambat menjadi lebih cepat atau database menjadi lebih besar. Demand yang terus berada di atas kapasitas service tetap memerlukan shedding, queueing di tempat lain, scaling, atau pengurangan pekerjaan.

Mekanisme ini paling berguna sebagai stability boundary. Service tidak menerima begitu banyak pekerjaan concurrent sampai inflasi latency ikut menghabiskan kapasitasnya sendiri. Saat kondisi membaik, boundary dapat terbuka secara bertahap. Saat kondisi memburuk, boundary dapat menyempit sebelum antrean tanpa batas menjadi perilaku dominan.

Dengan fungsi tersebut, adaptive concurrency melengkapi timeout, bounded queue, circuit breaker, autoscaling, dan rate policy. Masing-masing bekerja pada bagian berbeda dari overload control. Concurrency limit memberi satu jaminan spesifik: service memiliki batas berbasis feedback yang eksplisit atas jumlah pekerjaan yang diterima pada saat bersamaan.