Go Fiber sebagai framework performant masih bergantung pada database. Ketika endpoint menanggapi lambat, langkah paling cepat adalah mengonfirmasi bahwa Postgres yang memproses query dengan efisien. Dalam artikel ini dibahas bagaimana mengamati EXPLAIN ANALYZE, pg_stat_statements, jenis scan, serta perbaikan pagination yang menjaga perilaku stabil seiring data tumbuh.
Mengamati Query Langsung dari Postgres
Pertama-tama, jalankan EXPLAIN ANALYZE pada query lambat untuk melihat rencana eksekusi dan waktu aktual. Perintah ini memberi detail biaya estimasi, jenis operasi (Seq Scan, Index Scan, Nested Loop, dll), dan durasi. Contoh:
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42 ORDER BY created_at DESC LIMIT 20;
Perhatikan:
- Seq Scan berarti Postgres membaca seluruh tabel; ini wajar pada tabel kecil tapi tidak untuk tabel jutaan baris.
- Index Scan muncul ketika kolom yang ditanyakan memiliki indeks. Periksa apakah ORDER BY dan WHERE sudah memanfaatkan indeks yang sama.
- Jika ada Sort yang memakan waktu, pertimbangkan indeks komposit yang mencakup kolom ORDER BY.
Jangan fokus hanya pada waktu total—cari juga buffer hit rendah dan loops tinggi yang menandakan pengambilan data yang berulang.
Menggunakan pg_stat_statements untuk Observasi Historis
Ekstensi pg_stat_statements menyimpan statistik query yang dieksekusi, termasuk frekuensi, total waktu, dan waktu rata-rata.
Aktifkan jika belum, lalu ambil top query lambat:
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
ORDER BY total_time DESC
LIMIT 10;
Kombinasikan output ini dengan EXPLAIN ANALYZE untuk kasus spesifik. Catatan penting:
- Reset statistik setelah deployment besar atau tuning untuk memperoleh baseline baru (
SELECT pg_stat_statements_reset();). - Jika query sering dipanggil tetapi total_time rendah, mungkin bukan bottleneck utama—lihat CPU atau disk.
Memastikan Indeks dan Jenis Scan
Gunakan 3
t (INDEX) dari pg_indexes atau
SELECT * FROM pg_indexes WHERE tablename='orders'; untuk melihat indeks yang tersedia. Cocokkan dengan kolom yang dipakai di WHERE, JOIN, ORDER BY, GROUP BY.
Contoh kasus: query paginasi dengan WHERE pada customer_id dan ORDER BY created_at. Buat indeks seperti:
CREATE INDEX idx_orders_customer_created ON orders (customer_id, created_at DESC);
Indeks komposit membantu ORDER BY dan filter sekaligus, sehingga Postgres bisa melakukan Index Scan tanpa perlu sort tambahan.
Waspadai kekhawatiran:
- Banyak indeks menambah overhead penulisan. Tambahkan indeks hanya bila query benar-benar lambat.
- Indeks tidak otomatis digunakan jika statistik tidak up-to-date. Jalankan
ANALYZE orders;setelah perubahan data besar.
Menguji dan Memperbaiki Pagination
Pagination berbasis offset (contoh LIMIT 20 OFFSET 1000) memperlambat query karena Postgres harus melewati semua baris sebelumnya. Alihkan ke teknik keyset pagination dengan filter yang menggunakan kolom terindeks.
Contoh endpoint Go Fiber yang memicu query offset:
func listOrders(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
limit := 20
offset := (page - 1) * limit
rows, err := db.Query("SELECT * FROM orders WHERE customer_id=$1 ORDER BY created_at DESC LIMIT $2 OFFSET $3",
c.Query("customer_id"), limit, offset)
if err != nil {
return c.Status(500).SendString(err.Error())
}
defer rows.Close()
// parsing results omitted
return c.SendStatus(200)
}
Ganti dengan keyset pagination:
func listOrders(c *fiber.Ctx) error {
var cursor time.Time
if c.Query("after") != "" {
cursor, _ = time.Parse(time.RFC3339, c.Query("after"))
}
rows, err := db.Query("SELECT * FROM orders WHERE customer_id=$1 AND created_at < $2 ORDER BY created_at DESC LIMIT 20",
c.Query("customer_id"), cursor)
// ...
}
Untuk keyset pagination, indeks komposit pada (customer_id, created_at DESC) menjadi kritis karena Postgres hanya perlu mencari baris di bawah cursor.
Konfigurasi Connection Pooling Go Fiber dan Postgres
Setelah query dioptimalkan, pastikan pooling tidak jadi bottleneck. Go Fiber sendiri tidak mengelola pooling database; gunakan pgx atau database/sql dengan pgxpool. Contoh konfigurasi Go:
func newPool(connStr string) (*pgxpool.Pool, error) {
config, err := pgxpool.ParseConfig(connStr)
if err != nil {
return nil, err
}
config.MaxConns = 30
config.MinConns = 5
config.MaxConnIdleTime = 5 * time.Minute
return pgxpool.NewWithConfig(context.Background(), config)
}
Pengaturan ini membatasi jumlah koneksi sekaligus agar Postgres tidak kehabisan resource ketika tren query naik.
Mitigasi Bottleneck saat Data Bertambah
Setelah perbaikan pertama, pantau pertumbuhan data dan sesuaikan indeks atau arsitektur:
- Gunakan
pg_stat_user_tablesuntuk melihatseq_scanvsidx_scanper tabel. - Tambahkan partitioning jika tabel mencapai puluhan juta baris dan query selalu menyasar rentang waktu tertentu.
- Pertimbangkan materialized view untuk query agregasi yang langka di-refresh namun mahal saat run langsung.
Catatan: partitioning dan materialized view menambah kompleksitas. Uji di staging sebelum deploy.
Monitoring dan Tindakan Pasca-Deploy
Setelah deployment, monitor query lambat melalui:
- Log Postgres dengan
log_min_duration_statementuntuk menangkap query lebih lama dari threshold. - Grafana + Prometheus dengan exporter seperti
postgres_exporter, pantau rata-rata waktu query dan jumlah connection. - Alert jika persentase Index Scan turun drastis atau jika latency API melewati target.
Tip debugging: ketika API lambat, lihat timeline request di server dan bandingkan dengan rencana EXPLAIN ANALYZE terbaru. Jika filter berubah, indeks mungkin sudah tidak relevan lagi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!