Optimasi Query Lambat Go Fiber dengan Indexing dan Cursor Pagination dimulai dari deteksi jelas permasalahan: apakah API terhambat karena query yang tidak memanfaatkan index, hasilnya terlalu besar, atau pagination mengandalkan OFFSET ketika data terus bertambah. Dalam paragraf ini kita menjawab secara langsung bahwa solusi utama adalah memantau log query dan execution plan, menambah index sesuai pola akses, serta menggunakan cursor pagination berbasis bounded key untuk menjaga latensi stabil.
Artikel ini langsung menunjukkan teknik praktis: konfigurasi log slow query, cara membaca EXPLAIN, penetapan primary/composite/partial index sesuai filter/sort, serta contoh handler Go Fiber dengan query terindeks lengkap peringatan terhadap bottleneck seperti N+1 atau missing index. Fokusnya adalah perbaikan nyata, bukan teori kosong.
Optimasi Query Lambat Go Fiber dengan Indexing dan Cursor Pagination: Deteksi dan Profiling
Mulai dari pengamatan: aktifkan logging query pada level database. Di PostgreSQL, setel log_statement = 'all' atau lebih hemat, aktifkan log_min_duration_statement untuk menangkap query yang melebihi ambang waktu. Di MySQL gunakan log-output dan slow_query_log dengan long_query_time. Di aplikasi Go Fiber, keluarkan query dan parameter utama ke log di mode debug agar Anda bisa mencocokkan entry database dengan log aplikasi.
Langkah Profiling Dasar
- Catat query dan parameter yang dijalankan, termasuk nilai limit atau cursor.
- Periksa slow query log dan filter berdasarkan table atau query snippet.
- Jalankan
EXPLAIN ANALYZEterhadap query bermasalah, lalu catat apakah planner memilih sequential scan.
Interpretasi execution plan fokus pada kolom rows dan cost. Jika planner memilih Seq Scan pada tabel besar, atau menerapkan Bitmap Heap Scan karena kombinasi filter tidak punya index, maka inilah sinyal untuk menambah index. Perhatikan juga node Sort atau Hash Join yang menghabiskan memori; menambah index yang mencakup kolom sort/where bisa menghindar dari sort eksternal.
Indexing yang Efektif berdasarkan Pola Filter dan Sort
Indexing hanya bekerja jika struktur query sejalan dengan urutan kolom index. Prioritaskan:
- Primary key untuk lookup langsung via id.
- Composite index ketika query memfilter beberapa kolom sekaligus dan mengurutkan berdasarkan kolom yang sama. Misalnya:
CREATE INDEX idx_orders_customer_created ON orders (customer_id, created_at DESC); - Partial index untuk filter yang selalu menyertakan kondisi tertentu, seperti status aktif.
CREATE INDEX idx_active_orders ON orders (created_at) WHERE status = 'paid';
Selalu cocokkan index dengan WHERE dan ORDER BY. Jika pagination menggunakan created_at sebagai cursor, index composite dengan urutan descending memastikan planner tidak perlu sort tambahan. Hindari index dengan terlalu banyak kolom jika hanya beberapa yang selalu dipakai, karena menambah beban penulisan.
Cursor Pagination dan Dampak OFFSET Besar
Query dengan OFFSET akan memindai baris sampai offset tercapai, sehingga API menjadi lambat ketika pengguna mengakses halaman ke belakang pada tabel besar. Solusi yang stabil adalah cursor pagination berbasis bounded key (keyset pagination). Pendekatan ini menarik data berikutnya berdasarkan kolom terurut dan nilai terakhir.
Contoh pola: WHERE customer_id = ? AND created_at < ? ORDER BY created_at DESC LIMIT ?. Pastikan index mencakup customer_id dan created_at agar filter dan sort berjalan di index.
Trade-off: cursor pagination hanya mendukung traversal satu arah dan memerlukan client menyimpan nilai cursor terakhir. Namun, latensinya tetap konsisten meski tabel tumbuh menjadi jutaan baris.
Contoh Handler Go Fiber dengan Query Terindeks
Handler berikut menampilkan cursor pagination dengan parameter before yang mewakili created_at terkini. Ia menggunakan prepared statement dan memanfaatkan index composite.
func ListOrders(c *fiber.Ctx) error {
db := c.Locals("db").(*sql.DB)
limit, _ := strconv.Atoi(c.Query("limit", "20"))
cursor := c.Query("before")
args := []any{"active", limit}
query := `
SELECT id, customer_id, total, created_at
FROM orders
WHERE status = $1
ORDER BY created_at DESC
LIMIT $2`
if cursor != "" {
query = `
SELECT id, customer_id, total, created_at
FROM orders
WHERE status = $1 AND created_at < $2
ORDER BY created_at DESC
LIMIT $3`
args = []any{"active", cursor, limit}
}
rows, err := db.QueryContext(c.Context(), query, args...)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
defer rows.Close()
results := make([]Order, 0, limit)
for rows.Next() {
var o Order
rows.Scan(&o.ID, &o.CustomerID, &o.Total, &o.CreatedAt)
results = append(results, o)
}
return c.JSON(results)
}
Pastikan index seperti CREATE INDEX idx_orders_status_created ON orders (status, created_at DESC); ada agar query tersebut tidak melakukan full table scan. Catat juga potensi N+1 jika handler memanggil query tambahan per baris; gunakan JOIN atau batch query untuk menghindarinya.
Perawatan Database dan Debugging Lanjutan
Untuk PostgreSQL, gunakan VACUUM dan ANALYZE rutin agar statistik index tetap akurat setelah masif operasi penulisan. Jika index menjadi fragmentasi tinggi atau planner tetap memilih sequential scan, pertimbangkan REINDEX untuk membersihkan struktur index.
Debugging tambahan mencakup:
- Periksa EXPLAIN ANALYZE secara berkala setelah perubahan schema.
- Gunakan
pg_stat_statementsatauperformance_schemauntuk melihat frekuensi dan durasi query. - Perhatikan missing index warnings di log database; sebagian besar RDBMS mencatat saran index saat planner memutuskan sequential scan.
Kesimpulannya, optimasi query lambat di Go Fiber membutuhkan siklus observasi (log + EXPLAIN), desain index sesuai pola filter/sort, cursor pagination untuk data yang tumbuh, serta perawatan rutin seperti VACUUM/REINDEX. Dengan pendekatan ini, latency tetap rendah dan API dapat mempertahankan throughput tinggi meski beban data meningkat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!