Menjawab Masalah Query Lambat Go Fiber

Query Lambat Go Fiber umumnya disebabkan oleh Postgres harus memindai tabel besar tanpa indeks yang sesuai, atau karena pagination dengan OFFSET yang meningkatkan biaya baca saat data tumbuh. Artikel ini menjelaskan langkah konkret untuk menemukan dan memperbaiki bottleneck, memilih index yang tepat, serta memanfaatkan pagination berbasis keyset agar performa query tetap konsisten.

Dalam waktu singkat Anda akan belajar membaca EXPLAIN ANALYZE, menentukan bila composite/partial/covering index paling sesuai, serta menulis handler Go Fiber yang menggunakan GORM atau database/sql untuk paging yang tetap memanfaatkan index saat volume data membesar. Serta menyertakan cara monitoring sederhana untuk memastikan index digunakan.

Identifikasi Bottleneck dengan EXPLAIN ANALYZE

EXPLAIN ANALYZE menjalankan query dan memberikan estimasi serta waktu eksekusi nyata agar kita tahu apakah query melakukan seq scan, index scan, atau nested loop yang mahal. Pastikan query yang Anda lihat di handler Fiber dijalankan langsung di Postgres untuk mendapatkan hasil aktual.

Cara Menggunakan EXPLAIN ANALYZE

Jalankan seperti berikut pada query yang bermasalah: EXPLAIN ANALYZE SELECT .... Fokus perhatikan kolom Plan Rows dan Actual Loops. Jika query melakukan Seq Scan pada tabel besar, saatnya menambahkan index. Jika waktu eksekusi panjang karena Bitmap Heap Scan, pertimbangkan index lain atau rewrite filter.

Menginterpretasi Output

Perhatikan juga Buffers dan I/O jika Anda menyalakan statistik buffer. Jika banyak cache yang digunakan tapi tetap lambat, mungkin data tidak menggunakan index. Catat kondisi parameter query (misalnya variabel filter) karena query parametrik bisa menyebabkan generic plan yang tidak optimal.

Memilih Index yang Tepat di Postgres

Index yang tepat tergantung pola filtrasi. Ada tiga tipe utama yang sering jadi jawaban untuk query Go Fiber: composite index untuk kombinasi kolom, partial index saat filter tertentu sering muncul, dan covering index untuk menghindari heap fetch.

Composite Index

Gunakan composite index ketika query selalu menggunakan beberapa kolom dalam WHERE atau ORDER BY. Pastikan urutan kolom mencerminkan urutan filter dan sort. Contoh: CREATE INDEX ON orders (user_id, created_at DESC) untuk query yang menyaring user_id dan mengurutkan tanggal.

Partial Index

Jika query sering menarget baris dengan kondisi tetap (misal status = 'pending'), partial index mengurangi ukuran indeks. Contoh: CREATE INDEX ON orders (user_id, created_at) WHERE status = 'pending'. Ini membantu Postgres menggunakan index hanya untuk subset relevan dan menjadikan EXPLAIN ANALYZE menampilkan Index Scan kecil.

Covering Index

Menambahkan kolom yang hanya dibutuhkan query ke bagian INCLUDE memungkinkan Postgres menjawab query langsung dari index tanpa mengakses tabel. Contoh: CREATE INDEX ON orders (user_id, created_at) INCLUDE (total_amount). Ini mempercepat query select yang hanya membutuhkan kolom-kolom tersebut.

Pagination Efisien: Offset vs Keyset

Offset pagination bekerja walau sederhana, namun semakin besar offset, semakin banyak Postgres mengabaikan baris awalnya. Keyset pagination (juga dikenal sebagai cursor pagination) menggunakan nilai terakhir dari halaman sebelumnya agar query tetap memanfaatkan index.

Implementasi Keyset di Go Fiber

Contoh handler menggunakan database/sql:

func listOrders(c *fiber.Ctx) error {
    db := c.Locals("db").(*sql.DB)
    lastCreated := c.Query("after")
    var rows *sql.Rows
    var err error

    if lastCreated == "" {
        rows, err = db.Query(`SELECT id, created_at, total_amount FROM orders
            WHERE user_id = $1
            ORDER BY created_at DESC
            LIMIT 20`, c.Query("user_id"))
    } else {
        rows, err = db.Query(`SELECT id, created_at, total_amount FROM orders
            WHERE user_id = $1 AND created_at < $2
            ORDER BY created_at DESC
            LIMIT 20`, c.Query("user_id"), lastCreated)
    }
    if err != nil {
        return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
    }

    defer rows.Close()
    orders := make([]Order, 0)
    for rows.Next() {
        var o Order
        rows.Scan(&o.ID, &o.CreatedAt, &o.TotalAmount)
        orders = append(orders, o)
    }
    return c.JSON(orders)
}

Pastikan index mencakup user_id dan created_at agar filter dan urutan berjalan pada index. Untuk GORM, cukup tambahkan kondisi dan Order yang serupa.

Monitoring Index dan Pertumbuhan Tabel

Memantau hit rate index membantu memastikan query lama tetap berjalan optimal walau volume data naik.

Langkah Monitoring

  • Gunakan pg_stat_user_indexes untuk melihat idx_scan dan idx_tup_read. Jika idx_scan rendah sementara query seharusnya memanfaatkan index, evaluasi kembali kondisi WHERE atau parameter binding.
  • Periksa tabel dengan pg_stat_user_tables untuk melihat seq_scan yang tinggi. Bila seq_scan meningkat, mungkin index tidak dipakai karena statistik belum updated—jalankan ANALYZE.
  • Catat pertumbuhan tabel utama (misal orders) dengan SELECT relname, n_live_tup FROM pg_stat_user_tables;. Pertumbuhan tajam dapat memperlambat query jika index tidak sesuai.

Catatan Debugging

Jika query masih lambat setelah menambahkan index, pastikan parameter yang masuk ke Go Fiber tidak menyebabkan PostgreSQL memilih suboptimal plan. Untuk query dinamis, pertimbangkan PREPARE atau hint SET enable_seqscan = OFF hanya untuk debugging sementara. Simpan juga hasil EXPLAIN ANALYZE per release agar bisa membandingkan sebelum dan sesudah perubahan indeks.

Kesimpulan

Dengan memahami EXPLAIN ANALYZE, memilih index yang akurat, serta menerapkan keyset pagination, Go Fiber dapat tetap responsif walau tabel Postgres tumbuh. Tambahkan monitoring sederhana untuk memastikan index selalu digunakan dan jangan lupa meriview query apabila ada perubahan pola akses.