Menghitung dalam kondisi berebut sumber daya
Counter atomik, counter terpartisi, batching, dan rollup: kapan satu baris counter jadi bottleneck, dan apa yang dikorbankan tiap solusi.
Pertanyaan
Apa yang terjadi saat ribuan request memperbarui satu angka yang sama, dan bagaimana menyebar bebannya tanpa merusak hasilnya?
Setelah baca ini, kamu bisa memilih antara counter atomik, counter terpartisi, batching, dan rollup untuk satu beban, lengkap dengan harga yang dibayar masing-masing.
Prasyarat dan contoh
Kamu sudah punya definisi event dari M12.01 dan paham hot key dari partisi (M07). Kamu juga tahu
bahwa UPDATE ... SET n = n + 1 itu aman secara atomik.
Contoh: sebuah postingan menjadi viral dan tombol "suka" menerima 8.000 klik per detik pada satu
post_id. Kodenya benar: UPDATE posts SET likes = likes + 1 WHERE id = ?. Tapi database
mengunci baris itu selama update, jadi 8.000 transaksi mengantre satu per satu pada baris yang
sama. Kalau tiap update menahan kunci 1 ms, batas maksimumnya sekitar 1.000 per detik. Sisanya
menumpuk jadi latensi dan akhirnya timeout.
Masalahnya bukan hitungannya salah. Masalahnya semua penulis berebut satu titik serialisasi.
Mekanisme
Ada empat cara, dan masing-masing menukar sesuatu.
1. Counter atomik pada satu tempat. Satu baris atau satu key (misalnya INCR di Redis, yang
sanggup puluhan ribu operasi per detik per instance). Hasilnya selalu akurat dan dibaca langsung.
Batasnya: kapasitas satu baris atau satu shard. Cocok kalau tulisan per key di bawah beberapa
ratus per detik.
2. Counter terpartisi (sharded). Pecah satu counter jadi N baris: likes:{post_id}:{0..N-1}.
Tulis ke shard acak, baca dengan menjumlahkan semuanya. Dengan N = 16, batas tulis naik kira-kira
16 kali, tapi pembacaan jadi 16 baca. Harganya: pembacaan lebih mahal, dan hasil jumlah bisa
sedikit tertinggal kalau shard dibaca pada waktu berbeda. Kamu juga tidak bisa lagi menegakkan
aturan seperti "berhenti di tepat 1.000" tanpa koordinasi.
3. Batching di memori. Setiap server aplikasi menjumlahkan lokal, lalu mengirim satu update
per interval, misalnya +312 setiap 1 detik. Delapan ribu klik per detik di 10 server jadi hanya
10 tulisan per detik. Harganya: angka tertunda sampai satu interval, dan batch yang belum
dikirim hilang kalau server crash. Kalau kehilangan itu tidak bisa diterima, tulis event ke log
tahan lama lebih dulu dan hitung dari log.
4. Rollup. Simpan counter halus (per menit), lalu jumlahkan secara berkala ke counter kasar (per jam, per hari). Tulisan panas hanya menyentuh baris menit. Pembacaan dashboard bulanan membaca 30 baris harian, bukan 43.200 baris menit. Harganya: ada pekerjaan latar belakang yang harus idempoten, dan angka kasar tertinggal dari angka halus.
Saat rancangan ini gagal
- Shard terlalu sedikit. Dengan N = 4 untuk beban 8.000 per detik, tiap shard masih menerima 2.000 per detik dan tetap jadi hot row. Hitung dulu: beban per shard = beban total / N.
- Shard acak membuat pembacaan tidak konsisten. Dua pembaca berturut-turut bisa melihat angka yang turun, kalau salah satunya membaca replika yang tertinggal. Untuk "jumlah suka" itu bisa diterima. Untuk "stok tersisa" tidak.
- Retry menggandakan hitungan. Counter atomik tidak idempoten. Kalau klien me-retry
INCRsetelah timeout, kamu menghitung dua kali. Hitung dari event ber-ID (M12.01), bukan dari panggilan increment mentah, kalau akurasi penting. - Batch hilang saat deploy. Server direstart dan 1 detik data, sekitar 800 klik, hilang. Kalau ini terjadi tiap deploy, angka berkurang konsisten. Flush batch pada sinyal shutdown, dan catat kehilangan itu sebagai batas yang diketahui.
Latihan
Papan peringkat menghitung "reaksi" per postingan. Satu postingan puncaknya 12.000 reaksi per detik. Satu baris database tahan sekitar 800 update per detik. Ada 20 server aplikasi.
- Berapa shard minimum kalau memakai counter terpartisi?
- Berapa tulisan per detik ke database kalau memakai batching 1 detik di 20 server?
- Pilih satu pendekatan untuk papan peringkat yang dilihat pengguna dan sebutkan harganya.
Lihat pembahasan
- 12.000 / 800 = 15 shard secara teoretis. Itu tanpa margin, jadi ambil 32 supaya tiap shard berada sekitar 375 per detik, dan distribusi acak yang tidak rata tidak langsung membuatnya panas. Pembacaan jadi menjumlahkan 32 baris.
- 20 tulisan per detik (satu per server), masing-masing membawa sekitar 600 reaksi. Itu jauh di bawah batas 800, bahkan kalau dikali beberapa postingan panas bersamaan.
- Batching 1 detik, karena papan peringkat tidak butuh angka real-time yang tepat. Harganya: angka tertinggal sampai sekitar 1 detik, dan hingga satu detik reaksi hilang kalau server crash tanpa flush. Kalau kehilangan tidak bisa diterima, tulis ke log durabel dan hitung dari sana.
Perhatikan bahwa jawaban 3 menyebut kerugian, bukan hanya keuntungan. Itu yang membedakan keputusan dari preferensi.
Terkait
- Sebelumnya: M12.01 Definisikan event.
- Lanjut ke M12.03 Gunakan pemeriksaan keanggotaan aproksimatif.
- Hot key dan partisi dibahas di M07, yang belum tersedia.
- Rancangan D13 belum tersedia.
Catatan
- Asumsi: batas 800 update per detik per baris adalah angka ilustrasi. Ukur di database kamu.
- Yang nggak dibahas: CRDT counter lintas wilayah dan counter dengan batas keras (kuota).
- Angka Redis adalah orde besar, bergantung pada hardware, jaringan, dan ukuran payload.
- Terakhir ditinjau: 26 Oktober 2026.