Untuk API Go Fiber yang menangani dataset besar, fokus utama adalah menurunkan total I/O database tanpa mengorbankan akurasi data. Dalam artikel ini dijelaskan bagaimana mengenali query yang menjadi bottleneck, memakai partial index untuk mengurangi scan, serta menjaga responsivitas dengan pagination yang bisa diskalakan, termasuk cara mengukur efeknya dengan profil Postgres/MySQL.
Memetakan Bottleneck Query di API
Langkah pertama yang harus dilakukan adalah menganalisis query yang paling banyak mengonsumsi waktu. Gunakan middleware Fiber untuk mencatat latency endpoint dan hubungkan ke tracing ringan seperti OpenTelemetry atau logging custom. Di sisi database, aktifkan pg_stat_statements (Postgres) atau slow query log (MySQL) untuk melihat query mana yang mendominasi waktu CPU dan I/O.
Contoh pengamatan latency di Fiber handler
app.Get("/orders", func(c *fiber.Ctx) error {
start := time.Now()
// query ke database
// catat latency setelah respons
log.Printf("GET /orders latency: %s", time.Since(start))
return c.JSON(result)
})
Data latency ini bisa dikorelasikan dengan hasil EXPLAIN ANALYZE untuk melihat apakah query tersebut melakukan "Seq Scan" atau memindai lebih banyak baris daripada yang dibutuhkan.
EXPLAIN ANALYZE untuk validasi
Jalankan:
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE status = 'pending'
ORDER BY created_at DESC
LIMIT 20;
Lihat apakah planner menggunakan index yang tepat. Jika tidak, sebelum indeks ditingkatkan, query ini akan selalu membaca banyak baris, menjelaskan latency tinggi.
Partial Index sebagai Pengurang Scan
Partial index hanya menyimpan baris yang memenuhi kondisi tertentu, sehingga mengurangi I/O saat query melibatkan filter yang sama. Di Postgres, buat index seperti:
CREATE INDEX CONCURRENTLY idx_orders_pending
ON orders (created_at DESC)
WHERE status = 'pending';
Index tersebut hanya mencakup status pending dan disusun berdasarkan created_at. Query yang sesuai filter tersebut bisa langsung memakai index tanpa scanning seluruh tabel.
Untuk MySQL, karena tidak ada partial index yang eksplisit, pendekatan serupa dapat dicapai dengan generated column yang hanya bernilai bukan null jika kondisi tertentu terpenuhi, lalu membuat index pada kolom tersebut.
Alur verifikasi
- Jalankan kembali EXPLAIN ANALYZE dan pastikan planner memakai Index Scan.
- Bandingkan latensi rata-rata endpoint sebelum dan sesudah dengan monitoring (Prometheus, Grafana, atau log).
Pagination Efisien untuk Respon Konsisten
Query dengan OFFSET tinggi tetap akan memindai baris meskipun sudah ada index. Gunakan pagination berbasis kursors (misalnya keyset pagination) untuk menghindari overhead tersebut.
SELECT * FROM orders
WHERE status = 'pending' AND created_at < :last_created_at
ORDER BY created_at DESC
LIMIT 20;
Dengan keyset pagination, database hanya membaca baris hingga limit, tanpa menghitung offset besar. Pastikan API menyertakan token last_created_at pada respons sehingga klien bisa melanjutkan dari posisi sebelumnya.
Pengelolaan pertumbuhan data
- Arsipkan data lama ke tabel terpisah agar tabel utama tetap ringkas.
- Sibling partial index untuk status lain juga membantu agar setiap query tetap terpaku pada subset kecil.
- Gunakan covering index dengan kolom yang sering dipilih (misalnya id, status, created_at) agar query tidak perlu menyentuh heap page.
Profiling dan Metrik untuk Mengukur Dampak
Profiling Postgres bisa dilakukan dengan pg_stat_statements. Ambil aggregate waktu per query sebelum dan sesudah perubahan index/pagination, lalu bandingkan total_time dan rows. Untuk MySQL, periksa slow query log dan gunakan performance_schema.
Selain itu:
- Gunakan latency metric dari API gateway atau Fiber middleware.
- Monitoring throughput (request per detik) untuk memastikan partial index tidak meningkatkan CPU secara drastis.
Strategi Migrasi Index Tanpa Downtime
Buat index baru dengan CONCURRENTLY (Postgres) agar operasi tidak mengunci tabel. Setelah index tersedia, pantau penggunaan via EXPLAIN ANALYZE; jika sudah digunakan, hapus index lama dengan perintah serupa.
Untuk MySQL, lakukan pembentukan index di salinan read-only atau gunakan alat migrasi seperti gh-ost atau pt-online-schema-change agar tidak mengunci tabel sejalan.
Menerapkan di Handler Go Fiber
Berikut contoh implementasi endpoint yang menerima pagination key dan memanfaatkan struktur query yang sudah teroptimasi:
app.Get("/orders", func(c *fiber.Ctx) error {
lastCreated := c.Query("last_created_at")
limit := c.QueryInt("limit", 20)
rows, err := db.QueryContext(ctx, `
SELECT id, status, created_at
FROM orders
WHERE status = $1 AND created_at < $2
ORDER BY created_at DESC
LIMIT $3`, "pending", lastCreated, limit)
if err != nil {
return fiber.ErrInternalServerError
}
defer rows.Close()
var list []Order
for rows.Next() {
var o Order
rows.Scan(&o.ID, &o.Status, &o.CreatedAt)
list = append(list, o)
}
return c.JSON(list)
})
Pastikan parameter last_created_at divalidasi agar query tidak menjadi rentan SQL injection dan tambahkan monitoring latency per request.
Kesimpulan
Dengan memetakan query bermasalah, membangun partial index yang sesuai filter, menerapkan pagination berbasis keyset, serta mengukur dampaknya lewat profil database dan metrik latency Fiber, API Go Fiber bisa tetap responsif meski dataset terus tumbuh. Perubahan indeks harus dimigrasikan secara aman untuk menghindari downtime, dan pagination dalam handler harus konsisten agar pertumbuhan data tidak mengganggu pengalaman pengguna.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!