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:
- Latar belakang kenapa
COUNT()sering jadi masalah - Kenapa
COUNT()mahal secara performa - Alternatif dan solusi yang lebih efisien
- 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:
- Event
USER_CREATED - Consumer update counter
- 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.