Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.
01Apa perbedaan latency dan throughput?
Latency adalah waktu satu request dari mulai sampai selesai, dalam milidetik. Throughput adalah jumlah request yang selesai per satuan waktu, dalam request per detik. Sistem bisa punya throughput tinggi sekaligus latency buruk, misalnya pipeline batch yang memproses ribuan item per detik tetapi membuat tiap item menunggu lama.
02Apa isi Little's law tentang latency dan throughput?
Little's law menyatakan L = λW: jumlah rata-rata request di sistem sama dengan laju kedatangan dikali rata-rata waktu tiap request di sana. Pada 200 request per detik dan 50 ms per request, sekitar 10 request in flight. Hukum ini berguna untuk menentukan ukuran connection pool dan jumlah worker.
03Kenapa latency naik saat server hampir penuh dipakai?
Request datang secara acak, sehingga sebagian mendapati worker sibuk dan harus mengantre. Pada queue M/M/1, rata-rata waktu di sistem adalah 1 / (μ − λ), pengali 1 / (1 − ρ) dari waktu layanan. Itu 5x pada utilisasi 80 persen, 10x pada 90 persen, dan 100x pada 99 persen.
04Kenapa p99 lebih penting daripada latency rata-rata?
Rata-rata menyembunyikan request lambat: satu request 2.400 ms di antara sepuluh yang cepat menarik rata-rata ke 252,4 ms, angka yang tidak menggambarkan satu pun. Percentile menunjukkan seberapa buruk pengalaman terlambat. Tail latency juga berlipat saat request memanggil banyak backend, sehingga 1 persen lambat per backend menjadi sekitar 63 persen lambat di 100 backend.
05Bagaimana cara menurunkan latency tanpa menurunkan throughput?
Pakai teknik yang membantu keduanya: caching untuk read yang sering, connection pooling dan keep-alive, menghapus hop network yang tidak perlu, dan memindahkan kerja non-esensial dari request path. Hindari perbaikan yang saling menukar, seperti batch besar atau berjalan mendekati utilisasi penuh. Selalu cek percentile sebelum dan sesudah perubahan.
Latency vs Throughput: Perbedaan, Trade-off dan Tuning
Latency vs throughput dijelaskan: arti masing-masing, hubungan lewat Little's law, kenapa batching dan queue saling menukar keduanya, serta cara optimasinya.
Latency adalah berapa lama satu request selesai; throughput adalah berapa banyak request selesai per satuan waktu. Little's law, L = λW, menghubungkan keduanya, tetapi keduanya saling trade-off: batching dan utilisasi tinggi menaikkan throughput sambil membengkakkan latency, terutama di p99. Turunkan latency dengan mengurangi kerja dan hop, naikkan throughput dengan parallelism, pooling, dan kapasitas.
Dashboard bilang API menangani 800 request per detik, sementara user bilang halaman checkout terasa lambat. Keduanya bisa benar sekaligus, karena yang diukur berbeda. Tertukarnya dua istilah ini membuat tim menghabiskan satu sprint menambah server untuk menyembuhkan delay yang tidak bisa disembuhkan server.
Post ini mendefinisikan kedua istilah, menghubungkannya dengan Little's law, menunjukkan kenapa keduanya saling tarik lewat tabel batching dan queue M/M/1, lalu membahas percentile dan optimasi yang menggeser masing-masing angka. Setiap angka diturunkan langsung di halaman atau berasal dari simulasi TypeScript kecil milik saya, yang hanya mengukur simulasi itu dan bukan server sungguhan. Untuk sisi network dari ide yang sama, lihat post saya sebelumnya tentang latency PaaS Jakarta vs Singapore, dan untuk apa yang dilakukan saat queue penuh, lihat post backpressure.
Apa beda latency dan throughput?
Latency adalah waktu yang dibutuhkan satu unit kerja dari mulai sampai selesai, diukur dalam milidetik per request. Throughput adalah jumlah kerja yang selesai per satuan waktu, diukur dalam request per detik. Yang satu durasi, yang lain laju, dan tabel berikut menunjukkan perbedaannya dalam praktik.
Aspek
Latency
Throughput
Pertanyaan yang dijawab
Berapa lama satu request?
Berapa request selesai tiap detik?
Satuan
Milidetik atau detik per request
Request, transaksi, atau byte per detik
Siapa yang merasakan
User yang menunggu di layar
Operator yang membayar kapasitas
Cara melaporkan
Percentile: p50, p95, p99
Rata-rata berkelanjutan, plus puncak yang sanggup ditahan sistem
Perbaikan umum
Kurangi kerja per request, hapus hop, cache
Parallelism, pooling, batching, tambah instance
Jalan tol bisa jadi analogi. Latency adalah lama satu mobil menempuh jalan itu; throughput adalah jumlah mobil yang lewat suatu titik per jam. Menambah lajur menaikkan throughput tapi tidak mempercepat perjalanan satu mobil, sedangkan batas kecepatan yang lebih rendah merugikan keduanya.
Bagaimana latency dan throughput terhubung lewat Little's law?
Little's law menyatakan jumlah rata-rata request di dalam sistem yang stabil sama dengan laju kedatangan dikali rata-rata waktu tiap request berada di sana: L = λW. Hukum ini berlaku untuk distribusi kedatangan apa pun, sehingga ia jadi sanity check pertama untuk pertanyaan kapasitas.
// Little's law: L = lambda * W (valid for any stable system, any distribution)
// L = average number of requests in the system (in flight)
// lambda = average arrival rate = throughput once stable (requests/second)
// W = average time each request spends in the system (seconds)
// 200 requests/s at 50 ms each:
// L = 200 * 0.050 = 10 requests in flight
// The same 200 requests/s after latency degrades to 250 ms:
// L = 200 * 0.250 = 50 requests in flight
// So a Postgres pool of max: 10 is enough in the first case and
// 40 requests are queueing for a connection in the second.
const pool = new Pool({ max: 10 });
Angka contoh menunjukkan konsekuensinya. Pada 200 request per detik yang stabil, latency 50 ms menjaga 10 request in flight, tetapi jika latency melar jadi 250 ms, traffic yang sama menjaga 50 in flight. Traffic tidak berubah, namun sistem kini butuh concurrency lima kali lipat, dan begitulah connection pool kehabisan.
Pakai L = λW secara terbalik saat menentukan ukuran pool: kalikan request per detik puncak dengan latency p95 dalam detik, lalu tambahkan headroom. Jika hasilnya melebihi yang sanggup dibuka Postgres atau service downstream, Anda menemukan bottleneck di atas kertas sebelum production menemukannya.
Kenapa batching menaikkan throughput tetapi juga latency?
Batching membagi biaya tetap per panggilan ke banyak item, itulah kenapa bulk insert dan consumer queue yang mengambil banyak pesan sekaligus jauh lebih cepat secara total. Konsekuensinya, sebuah item harus menunggu batch-nya terisi. Tabel di bawah memakai model biaya sederhana pilihan saya untuk ilustrasi: overhead tetap 5 ms per batch ditambah 0,5 ms per item, dengan satu item datang tiap 2 ms.
// Fixed 5 ms overhead per batch + 0.5 ms per item. One item arrives every 2 ms (500/s).
batch service capacity/s mean fill wait mean latency
1 5.5 ms 182 0.0 ms 5.5 ms
5 7.5 ms 667 4.0 ms 11.5 ms
10 10.0 ms 1000 9.0 ms 19.0 ms
20 15.0 ms 1333 19.0 ms 34.0 ms
50 30.0 ms 1667 49.0 ms 79.0 ms
Kapasitas naik dari 182 item per detik pada batch size 1 menjadi 1.667 pada batch size 50, sekitar 9 kali lipat. Latency rata-rata naik dari 5,5 ms menjadi 79 ms di rentang yang sama, sekitar 14 kali lipat, karena item rata-rata menunggu separuh waktu pengisian. Perhatikan juga bahwa batch size 1 hanya sanggup 182 item per detik sementara yang datang 500, sehingga queue-nya tumbuh tanpa batas; di situlah batching bukan pilihan lagi.
Aturan praktisnya: batch berdasarkan ukuran dan waktu sekaligus. Flush ketika batch mencapai ukuran maksimum atau ketika item tertua sudah menunggu sekian milidetik maksimum, mana yang lebih dulu, supaya periode sepi tidak membuat item tertahan di batch yang tak kunjung penuh.
Kenapa latency meledak saat utilisasi naik?
Pada queue M/M/1, satu worker dengan kedatangan dan waktu layanan acak, rata-rata waktu di sistem adalah W = 1 / (μ − λ). Dibagi rata-rata waktu layanan 1/μ, didapat pengali tunggu 1 / (1 − ρ), dengan ρ = λ/μ sebagai utilisasi. Pengali itu landai sampai sekitar 80 persen lalu menanjak tegak, seperti terlihat di tabel untuk waktu layanan rata-rata 10 ms.
Utilisasi ρ
Pengali 1 / (1 − ρ)
Rata-rata W (turunan)
p99 simulasi
50%
2x
20 ms
93,6 ms
80%
5x
50 ms
230,7 ms
90%
10x
100 ms
438,5 ms
95%
20x
200 ms
838,2 ms
99%
100x
1.000 ms
4.497,9 ms
Saya memeriksa rumus itu dengan simulasi pendek: kedatangan Poisson, waktu layanan eksponensial, satu worker, 400.000 job per level, dan random generator ber-seed agar hasilnya bisa diulang. Rata-rata simulasi cocok dengan rata-rata turunan dalam kisaran 5 persen sampai utilisasi 95 persen, dan pada 99 persen meleset 7,6 persen ke atas (1.075,5 ms melawan 1.000 ms) karena queue yang sedekat itu dengan saturasi konvergen lambat.
// mm1.ts - run with: node mm1.ts (Node 22.18+ strips the types itself)
const SERVICE_MS = 10; // mean service time -> mu = 100 requests/s
const N = 400_000; // jobs per utilisation level
function simulate(rho: number) {
const rand = rng(42); // seeded PRNG, so the run is reproducible
const exp = (mean: number) => -mean * Math.log(1 - rand());
const meanInterarrival = SERVICE_MS / rho; // lambda = rho * mu
let wait = 0; // Lindley recursion: queueing delay of the current job
const sojourn: number[] = [];
for (let i = 0; i < N; i++) {
const service = exp(SERVICE_MS);
sojourn.push(wait + service); // time in system = queue wait + service
wait = Math.max(0, wait + service - exp(meanInterarrival));
}
sojourn.sort((a, b) => a - b);
return { mean: sojourn.reduce((s, x) => s + x, 0) / N, p99: percentile(sojourn, 99) };
}
// Output (ms), my own simulation of an idealised queue - not a measurement of any real server:
// rho theory W sim mean sim p50 sim p95 sim p99 p99/idle
// 0.50 20.0 20.1 13.9 60.4 93.6 9.4x
// 0.80 50.0 49.9 34.6 148.6 230.7 23.1x
// 0.90 100.0 97.6 68.9 283.1 438.5 43.8x
// 0.95 200.0 189.7 136.4 559.3 838.2 83.8x
// 0.99 1000.0 1075.5 684.1 3525.3 4497.9 449.8x
Simulasi ini juga menunjukkan kenapa rata-rata menyembunyikan rasa sakitnya. Waktu tunggu di queue M/M/1 berdistribusi eksponensial, sehingga p50 ada sekitar 0,69 kali rata-rata sedangkan p99 sekitar 4,6 kali rata-rata. Pada utilisasi 90 persen, p99 adalah 438,5 ms untuk job yang hanya butuh 10 ms saat worker menganggur. Ini queue ideal yang diukur di script, bukan benchmark Postgres, NestJS, atau server sungguhan, tetapi bentuk kurvanya yang bisa dibawa ke mana-mana.
Menjalankan sistem di utilisasi 90 sampai 95 persen demi hemat biaya adalah kesalahan yang mahal. Anda membayar kapasitas cadangan dengan latency, bukan dengan sewa, dan kenaikan traffic kecil saja memindahkan Anda dari tunggu 10x ke 20x. Targetkan utilisasi berkelanjutan jauh di bawah titik tekuk dan scale out sebelum sampai di sana.
Kenapa p95 dan p99 lebih penting daripada rata-rata?
Percentile menjawab seberapa lambat request yang paling lambat. p50 adalah nilai yang dikalahkan separuh request, p95 dikalahkan 95 persen request, dan p99 dikalahkan 99 persen request. Snippet memakai metode nearest-rank: urutkan sampel lalu ambil nilai di rank ceil(p/100 x n). Satu request lambat dari sepuluh menyeret rata-rata ke 252,4 ms, angka yang tidak menggambarkan satu pun dari mereka.
// Nearest-rank percentile: sort ascending, take the value at rank ceil(p/100 * n).
function percentile(sorted: number[], p: number): number {
const rank = Math.ceil((p / 100) * sorted.length);
return sorted[Math.max(0, rank - 1)];
}
const latenciesMs = [12, 14, 13, 15, 12, 16, 13, 14, 15, 2400]; // one slow request in ten
const sorted = [...latenciesMs].sort((a, b) => a - b);
// Wrong: the mean says 252.4 ms, which describes none of the ten requests.
const mean = latenciesMs.reduce((s, x) => s + x, 0) / latenciesMs.length;
// Right: p50 = 14 ms is the typical request, p99 = 2400 ms is the slow one.
console.log(mean, percentile(sorted, 50), percentile(sorted, 99));
Tail latency juga bersifat majemuk. Paper Tail at Scale dari Google berargumen bahwa pada fan-out besar, respons lambat yang jarang menjadi pengalaman yang umum, dan hitungannya cukup singkat untuk dikerjakan manual. Jika tiap backend lambat 1 persen waktu, request yang menyentuh 100 backend lambat dengan probabilitas 1 − 0,99^100, sekitar 63,4 persen.
// A page that fans out to n backends is slow if ANY one of them is slow.
// If each backend is slow 1% of the time, P(page is slow) = 1 - 0.99^n
// n = 1 -> 1 - 0.99 = 1.0%
// n = 10 -> 1 - 0.9044 = 9.6%
// n = 100 -> 1 - 0.3660 = 63.4%
Karena itu service-level objective sebaiknya menyebut percentile dan ambang, misalnya 99 persen checkout di bawah 800 ms, bukan rata-rata. Target rata-rata bisa terpenuhi di hari ketika satu dari seratus pelanggan menunggu beberapa detik.
Bagaimana cara mengoptimasi latency dan throughput?
Mulai dengan menentukan angka mana yang bermasalah, karena perbaikannya menarik ke arah berbeda. Kerja latency mengurangi waktu dari satu request; kerja throughput menambah kapasitas atau menghapus overhead per item. Ukur percentile dulu, baru pilih dari daftar berikut.
Untuk menurunkan latency, kerjakan lebih sedikit per request:
Cache hasil dekat dengan pemanggil, misalnya Redis di depan query Postgres yang panas, sehingga sebagian besar request melewati jalur lambat sama sekali.
Kurangi hop: tiap service, proxy, atau region tambahan menambah sedikitnya satu round trip, jadi ringkas rantai dan tempatkan service berdekatan.
Pindahkan kerja yang tidak esensial dari request path dengan async processing, sehingga user menunggu order tersimpan tetapi tidak menunggu email terkirim.
Pakai ulang koneksi lewat keep-alive dan pooling agar tiap request tidak perlu handshake TCP dan TLS baru.
Untuk menaikkan throughput, tambah kerja yang selesai per satuan waktu:
Tambah parallelism: lebih banyak worker process atau instance di belakang load balancer, dibatasi oleh apa yang sanggup diserap database bersama.
Batch tulis dan antrikan baca agar biaya tetap dibayar sekali per grup, bukan sekali per item.
Tentukan ukuran connection pool dari L = λW, bukan menebak, karena pool yang terlalu kecil membatasi throughput dan yang terlalu besar membebani database.
Jaga utilisasi di bawah titik tekuk agar delay queueing tidak memakan keuntungannya, dan terapkan backpressure bila tidak terhindarkan.
Perhatikan ada yang beririsan. Pooling dan caching membantu kedua angka, sedangkan batching dan utilisasi tinggi membeli throughput dengan harga latency. Setiap perubahan yang memperbaiki satu metrik perlu dicek terhadap metrik lainnya sebelum dirilis.
Bagaimana dengan bandwidth vs latency di network?
Bandwidth adalah berapa bit yang dibawa sebuah link per detik; latency adalah berapa lama satu bit tiba. Keduanya independen, dan bandwidth-delay product, yaitu bandwidth dikali round-trip time, memberi tahu berapa banyak data yang harus in flight untuk memenuhi link. Untuk respons kecil, round trip, bukan bandwidth, yang menentukan biaya.
// Bandwidth-delay product = bandwidth x round-trip time = bytes in flight on the wire
// 100 Mbit/s link, 20 ms RTT:
// 100,000,000 bit/s * 0.020 s = 2,000,000 bits = 250,000 bytes (250 KB)
// A sender whose window is smaller than 250 KB cannot fill the link.
// A 2 KB JSON response, same link: transfer time = 16,000 bits / 100,000,000 = 0.16 ms.
// The 20 ms round trip dominates by roughly 125x. A faster link will not help;
// fewer round trips will (keep-alive, HTTP/2, a closer region).
Angka di atas adalah aritmetika biasa untuk link 100 Mbit/s dan round trip 20 ms. Ini menjelaskan kenapa memindahkan service lebih dekat ke user mengalahkan membeli uplink lebih cepat untuk API yang cerewet, dan kenapa ukuran TCP window penting di link panjang yang cepat. Respons 2 KB menghabiskan 0,16 ms di kabel dan 20 ms menunggu round trip, jadi mengurangi round trip adalah tuasnya.
Perlakukan latency dan throughput sebagai dua kenop di mesin yang sama. Laporkan latency dalam percentile, tentukan concurrency dengan L = λW, jaga utilisasi di bawah titik ketika 1 / (1 − ρ) menjadi curam, dan cek setiap optimasi throughput terhadap latency yang harus dibayar sebagai gantinya.