Lompat ke konten
20 menit bacamateri 9 dari 96

Telusuri request konkuren

Proses, thread, event loop, dan connection pool: siapa yang ngerjain request dan di mana antreannya terbentuk.

Ditinjau 26 Oktober 2026

Pertanyaan

Saat 500 request datang bersamaan, siapa yang mengerjakannya, dan di mana mereka menunggu?

Setelah baca ini, kamu bisa menelusuri satu request melewati model eksekusi server dan connection pool, lalu menghitung batas konkurensi dari ukuran pool dan waktu layanan.

Prasyarat dan contoh

Kamu sudah bisa menghitung konkurensi dari request per detik dan latensi (M01.02) dan tahu bahwa satu request menempuh beberapa tahap (M02.01).

Sebuah API punya 8 worker dan satu database dengan pool 20 koneksi. Tiap request butuh 1 query berdurasi 50 ms. Traffic 300 request/detik. Apakah cukup? Jawabannya tergantung di mana batas yang paling kecil, dan itu belum terlihat dari angka CPU saja.

Mekanisme

Empat model eksekusi.

  • Proses per request/worker: tiap proses punya memori sendiri. Isolasi bagus, memori mahal. Contoh: pre-fork (Gunicorn, PHP-FPM).
  • Thread per request: satu thread menangani satu request, memori bersama, tapi tiap thread butuh stack (sekitar 0,5 sampai 1 MB) dan context switch. Ribuan thread mulai berat.
  • Event loop: satu thread melayani banyak koneksi; kerja yang menunggu I/O dilepas dan dilanjutkan saat siap. Hemat memori untuk banyak koneksi yang kebanyakan menunggu. Tapi satu fungsi yang memblokir CPU menahan semua request (Node.js, asyncio).
  • Gabungan: event loop per core ditambah thread pool untuk kerja blokir. Banyak runtime modern begini.

Hukum Little untuk kapasitas. Rata-rata request dalam sistem = laju kedatangan × waktu di sistem (L = λ × W). Dari contoh: 300 request/detik × 0,05 detik = 15 request yang sedang memegang koneksi database pada satu waktu rata-rata.

Connection pool. Membuka koneksi database itu mahal (handshake, autentikasi, memori di sisi database). Pool menyimpan N koneksi terbuka. Request yang butuh koneksi mengambil, memakai, mengembalikan. Kalau semua N terpakai, request menunggu di antrean pool.

Dengan pool 20 dan waktu query 50 ms, throughput maksimum = 20 / 0,05 = 400 query/detik. Beban 300/detik memakai 75 persen kapasitas. Rata-rata aman, tapi beban puncak 2× (600/detik) akan membentuk antrean tanpa batas.

Antrean di mana-mana. Antrean bisa ada di: backlog TCP (listen queue), antrean worker server, antrean pool, dan antrean di database. Tanpa batas dan timeout, tiap antrean mengubah kelebihan beban jadi latensi, bukan kegagalan cepat.

| Utilisasi | Waktu tunggu relatif (model antrean sederhana) | | --------- | ----------------------------------------------- | | 50% | sekitar 1× waktu layanan | | 80% | sekitar 4× | | 95% | sekitar 19× |

Angka itu dari model M/M/1 (ρ / (1 − ρ)), sebagai gambaran bentuk kurvanya, bukan prediksi sistemmu. Pelajarannya: latensi naik tajam sebelum utilisasi mencapai 100 persen.

Batas sumber daya lain. File descriptor per proses (default sering 1024), jumlah thread, memori, dan port ephemeral untuk koneksi keluar. Semuanya batas keras yang menghasilkan error spesifik, misalnya "too many open files".

Saat rancangan ini gagal

  • Memblokir event loop. Satu endpoint yang menghitung JSON besar selama 300 ms membuat semua request lain di proses itu menunggu 300 ms. Dari luar tampak seperti jaringan lambat.
  • Pool habis karena koneksi bocor. Kode yang lupa mengembalikan koneksi saat exception menghabiskan pool pelan-pelan. Gejalanya: timeout "menunggu koneksi" padahal CPU database rendah.
  • Query lambat memegang koneksi. Satu query 5 detik memegang satu dari 20 koneksi. Sepuluh query seperti itu menghabiskan setengah kapasitas.
  • Antrean tanpa batas. Beban 2× selama 10 menit menumpuk ribuan request. Saat beban turun, klien sudah timeout, tapi server masih mengerjakan request yang hasilnya tidak dibaca siapa pun.
  • Thread pool diisi pekerjaan I/O. Tiap thread menunggu respons downstream, pool habis, padahal CPU nganggur.

Contoh tandingan: menaikkan jumlah worker dari 8 ke 64 untuk beban yang terikat database tidak menurunkan latensi. Worker bertambah, antrean pindah ke pool koneksi.

Latihan

Sebuah service punya 4 instance, tiap instance pool 10 koneksi ke database. Tiap request melakukan 2 query berurutan, masing-masing 25 ms, dan memegang satu koneksi selama keduanya. Beban rata-rata 600 request/detik, puncak 1.200 request/detik.

  1. Berapa koneksi yang terpakai rata-rata dan saat puncak?
  2. Apakah pool cukup? Berapa throughput maksimum?
  3. Apa yang terjadi saat puncak, dan apa satu perubahan yang kamu pilih?
Lihat pembahasan

Waktu memegang koneksi = 2 × 25 ms = 50 ms.

  1. Rata-rata: 600 × 0,05 = 30 koneksi. Puncak: 1.200 × 0,05 = 60 koneksi.
  2. Total pool = 4 × 10 = 40 koneksi. Throughput maksimum = 40 / 0,05 = 800 request/detik. Rata-rata (75 persen utilisasi) cukup, puncak 1.200 melebihi kapasitas.
  3. Saat puncak, 400 request/detik berlebih mengantre di pool. Antrean tumbuh tanpa batas, latensi naik, dan akhirnya timeout klien. Pilihan paling murah: gabungkan kedua query jadi satu (misalnya dengan join) kalau bisa, sehingga waktu memegang turun mendekati 25 ms dan kapasitas jadi 1.600/detik. Menaikkan pool ke 15 per instance (total 60) juga memberi 1.200/detik, tapi tanpa ruang sama sekali dan menambah koneksi di database, jadi perlu dicek batas koneksi database. Tambahkan juga batas antrean dengan timeout supaya kelebihan beban gagal cepat, bukan menumpuk.

Jawaban yang bagus memakai L = λ × W, membedakan rata-rata dari puncak, dan tidak langsung menaikkan pool tanpa memeriksa database.

Terkait

Catatan

  • Asumsi: waktu layanan konstan, beban dominan I/O database, satu pool per instance.
  • Model M/M/1 menyederhanakan: kedatangan acak dan waktu layanan eksponensial. Sistem nyata bisa lebih baik atau lebih buruk.
  • Ukuran stack thread dan batas file descriptor tergantung OS dan runtime. Periksa konfigurasimu.
  • Yang tidak dibahas: scheduler OS, GC pause, dan tuning kernel.
  • Terakhir ditinjau: 26 Oktober 2026.

Isi · System Design