COUNT()

COUNT() #

Dalam dunia software engineering, performa database sering menjadi bottleneck tersembunyi. Aplikasi terlihat “baik-baik saja” di awal, tapi perlahan menjadi lambat seiring pertumbuhan data. Salah satu penyebab klasik—dan sering tidak disadari—adalah penggunaan fungsi COUNT() secara sembarangan.

Artikel ini membahas:

  1. Latar belakang kenapa COUNT() sering jadi masalah
  2. Kenapa COUNT() mahal secara performa
  3. Alternatif dan solusi yang lebih efisien
  4. Best practice untuk kebutuhan counting di sistem nyata

Latar Belakang #

Bagi banyak engineer, COUNT() terlihat seperti operasi sederhana:

SELECT COUNT(*) FROM orders;

Atau untuk pagination:

SELECT COUNT(*) FROM users WHERE status = 'active';

Secara konseptual, ini memang terlihat:

  • Mudah
  • Jelas
  • Aman
  • SQL-native

Masalahnya: apa yang terlihat sederhana di level query, tidak selalu sederhana di level engine database.

Di skala kecil, COUNT() nyaris tidak terasa dampaknya. Di skala besar (jutaan atau miliaran row), COUNT() bisa menjadi query paling mahal di sistem.


Kenapa Mahal? #

COUNT() = Scan Data #

Pada sebagian besar database (PostgreSQL, MySQL, MariaDB):

  • COUNT(*) harus membaca row
  • Database tidak bisa “menebak” jumlah data
  • Bahkan dengan index, hasilnya tidak selalu O(1)

Contoh:

SELECT COUNT(*) FROM orders;

➡️ Database harus:

  • Melakukan sequential scan atau index scan
  • Menghitung setiap row yang valid
  • Mengunci resource selama proses berlangsung

COUNT() + WHERE = Lebih Mahal Lagi #

SELECT COUNT(*) FROM orders WHERE created_at >= NOW() - INTERVAL '30 days';

Ini berarti:

  • Scan index created_at
  • Filter row
  • Hitung satu per satu

Jika:

  • Data besar
  • Query sering dipanggil (dashboard, pagination, API)

➡️ Beban database meningkat drastis


COUNT() dalam Pagination: Silent Killer #

Kasus paling umum:

SELECT * FROM users LIMIT 20 OFFSET 0;
SELECT COUNT(*) FROM users;

Untuk setiap request halaman, database:

  • Mengambil data
  • Menghitung total data

Padahal:

  • User tidak selalu butuh total count
  • Total count jarang berubah signifikan

Alternatif & Solusi #

Gunakan LIMIT + 1 (Existence-Based Pagination) #

Alih-alih menghitung total data, cukup tahu:

“Masih ada data berikutnya atau tidak?”

SELECT * FROM users
ORDER BY id
LIMIT 21;

Jika:

  • Data > 20 → ada next page
  • Data ≤ 20 → tidak ada next page

✅ Sangat cepat ✅ Tidak perlu COUNT() ❌ Tidak tahu total halaman (biasanya tidak masalah)


Simpan Counter di Table Terpisah (Precomputed Count) #

Untuk data yang sering dihitung:

table: user_stats
-----------------
active_users_count

Update counter saat:

  • Insert
  • Delete
  • Status berubah
UPDATE user_stats
SET active_users_count = active_users_count + 1;

✅ O(1) read ✅ Cocok untuk dashboard ❌ Butuh konsistensi ekstra


Approximate Count (Estimasi, Bukan Presisi) #

Untuk UI / analytics:

PostgreSQL: #

SELECT reltuples::bigint
FROM pg_class
WHERE relname = 'users';

Ini adalah estimasi, bukan angka pasti.

✅ Sangat cepat ❌ Tidak 100% akurat ✅ Cocok untuk:

  • Dashboard
  • Monitoring
  • Statistik kasar

Event-Driven Counting (Asynchronous) #

Alih-alih hitung saat query:

  1. Event USER_CREATED
  2. Consumer update counter
  3. API hanya baca hasil

Contoh arsitektur:

DB → Event → Queue → Counter Service → Read Model

✅ Scalable ✅ Database ringan ❌ Lebih kompleks


Cache Count (Redis / Memory Cache) #

GET users:active:count
  • Update cache saat ada perubahan
  • TTL pendek atau event-based invalidation

✅ Sangat cepat ❌ Cache invalidation tidak trivial ✅ Cocok untuk high-traffic API


Kapan Boleh Dipakai? #

COUNT() bukan musuh, tapi harus dipakai dengan sadar.

Masih wajar jika: #

  • Data kecil

  • Query jarang dipanggil

  • Query hanya untuk:

    • Admin
    • One-time report
    • Maintenance

❌ Tidak disarankan untuk:

  • Endpoint publik
  • Pagination API
  • Dashboard real-time
  • Query di hot path

Best Practice #

Tanyakan: “Apakah Benar-Benar Perlu Total Count?” #

Sering kali UI hanya butuh:

  • Ada halaman berikutnya atau tidak
  • Estimasi kasar

Pisahkan Read Model dan Write Model #

  • Write: data mentah
  • Read: precomputed / denormalized

Ini prinsip CQRS ringan.


Hindari COUNT() di Hot Path #

Hot path = API yang:

  • Dipanggil sering
  • Latency-sensitive

Monitor Query Plan #

Selalu cek:

EXPLAIN ANALYZE SELECT COUNT(*) FROM ...

Jika terlihat:

  • Sequential scan besar
  • Execution time tinggi

➡️ Itu sinyal bahaya 🚨


Dokumentasikan Keputusan #

Jika kamu sengaja tidak menyediakan total count, dokumentasikan alasannya:

  • Performa
  • Skalabilitas
  • UX trade-off

Ini mencegah engineer lain “menambahkan COUNT()” di masa depan tanpa sadar dampaknya.


Penutup #

COUNT() terlihat sederhana, tapi di sistem skala besar, ia sering menjadi silent performance killer.

Engineer yang matang bukan yang menghindari fitur database, tapi yang:

  • Paham konsekuensinya
  • Memilih solusi sesuai kebutuhan
  • Berani mengatakan “kita tidak butuh total count”

Optimization bukan tentang menulis query yang pintar, tapi tentang memahami biaya di baliknya.

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact