Memecahkan Query Lambat di Spring Boot dengan JPA
Masalah utama yang ingin dijawab adalah: bagaimana mendeteksi query JPA lambat dan memperbaikinya secara sistematis. Langkah pertama ialah mengukur durasi query secara nyata, lalu memahami apakah indeks database sudah optimal, strategi fetch sudah tepat, serta pagination dan limit/offset sudah dipakai ketika data besar. Artikel ini membahas rangkaian tindakan praktis agar Spring Boot Anda tetap responsif.
1. Mengukur Query Lambat secara Real-time
Tanpa pengukuran, sulit menentukan query mana yang menyebabkan delay. Tambahkan p6spy atau datasource-proxy untuk mencatat SQL yang dikirim oleh Hibernate:
// Contoh konfigurasi p6spy di application.properties
spring.datasource.url=jdbc:p6spy:postgresql://localhost:5432/appdb
spring.datasource.username=user
spring.datasource.password=secret
logging.level.p6spy=DEBUGP6Spy menyelipkan log SQL dan durasi eksekusi. Perhatikan durasi (>200ms) dan parameter. Alternatif, gunakan datasource-proxy dengan DataSourceProxyBuilder.create(dataSource).logQueryBySlf4j() agar log mengandung duration serta stack trace pemanggil.
Memahami durasi sebenarnya
Catat waktu dari log p6spy, lalu jalankan EXPLAIN ANALYZE langsung di database untuk melihat rencana eksekusi:
EXPLAIN ANALYZE
SELECT f.* FROM faktur f
WHERE f.tanggal BETWEEN '2024-01-01' AND '2024-12-31';Jika output memperlihatkan Seq Scan dan estimasi jauh dari real, berarti ada indeks yang tidak digunakan atau filter tidak cocok.
2. Indeks Efektif untuk Postgres/MySQL
Setelah tahu query bermasalah, periksa kondisi WHERE/ORDER BY yang sering dipakai. Jenis indeks yang bisa dipertimbangkan:
- B-tree: Default untuk banyak kondisi equality/range. Cocok untuk kolom tanggal, id, status.
- Composite: Gunakan saat query memfilter beberapa kolom sekaligus, misalnya
WHERE user_id = ? AND status = ?. Urutan kolom harus mengikuti pola query. - Partial: Di Postgres, buat indeks hanya pada subset data, seperti
CREATE INDEX ON faktur (user_id) WHERE status='AKTIF';agar indeks lebih kecil dan faster scan.
Ingat bahwa indeks menambah beban tulis; jangan buat semuanya kecuali terlihat dalam EXPLAIN.
Contoh kasus nyata
Aplikasi transaksi harian mengalami Node dengan filter status + created_at. Setelah log menunjukkan sequential scan, perintah berikut mempercepat query:
CREATE INDEX ON order_log (status, created_at DESC);
-- Setelah indeks ditambah, EXPLAIN ANALYZE menunjukkan penggunaan Index Scan dengan cost lebih rendah.Tindakan ini mengubah waktu eksekusi dari 800ms menjadi sekitar 45ms.
3. Tuning Fetch Strategy dan Mitigasi Bottleneck SQL
N+1 sering muncul saat fetch relasi secara default. Gunakan @EntityGraph atau JOIN FETCH pada query kustom untuk menghindarinya. Contoh:
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Order findWithItems(@Param("id") Long id);Jika tidak perlu menampilkan seluruh relasi, biarkan lazy loading, atau gunakan DTO projection agar Hibernate tidak memuat seluruh entitas.
Untuk relasi besar, pertimbangkan batch fetch size di application.properties:
spring.jpa.properties.hibernate.default_batch_fetch_size=50Nilai ini membantu Hibernate menggabungkan beberapa SELECT menjadi satu saat fetch koleksi.
4. Pagination dan Limit/Offset saat Data Tumbuh
Pagination wajib untuk hasil besar. Gunakan Pageable dari Spring Data JPA agar limit/offset ditambahkan otomatis:
Page findByUserId(Long userId, Pageable pageable); Jika offset besar (>10000), pertimbangkan keyset pagination (cursors) dengan filter berdasarkan kolom terurut, karena offset besar memaksa database scan banyak baris.
Contoh keyset:
SELECT * FROM order_log
WHERE user_id = ? AND created_at < ?
ORDER BY created_at DESC
LIMIT 50;5. Studi Kasus dan Troubleshooting
Skenario: API laporan transaksi flash sale lambat saat traffic tinggi. Log menunjukkan query JPA.
- Catat log p6spy, identifikasi query dengan duration > 500ms.
- Jalankan
EXPLAIN ANALYZEdi console. Ditemukan Seq Scan karena filter menggunakan fungsiLOWER(status). - Solusi: tambahkan kolom ter-normalisasi dan indeks B-tree biasa, serta ubah query agar tidak memakai fungsi di WHERE, sehingga indeks dapat digunakan.
- Untuk relasi produk, tambahkan
JOIN FETCHpada endpoint laporan agar jumlah query tetap satu. - Terakhir, terapkan pagination dengan
LIMIT 100dan keyset berdasarkancreated_atuntuk menahan jumlah data per respons.
Setelah perubahan: waktu respon turun dari 1.2 detik menjadi 180ms dan beban DB stabil karena lebih sedikit baris yang discan.
Kesimpulan
Diagnosa query lambat di Spring Boot dengan JPA memerlukan log yang akurat, pemahaman atas indeks yang diterapkan, serta tuning fetch dan pagination. Pastikan indeks sesuai pola query, hindari N+1 dengan fetch strategies yang tepat, dan gunakan paging/limit untuk menjaga konsistensi performa saat data bertumbuh. Terus pantau EXPLAIN ANALYZE untuk memvalidasi asumsi dan hindari pembuatan indeks berlebihan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!