Memperbaiki Query Lambat di Endpoint SvelteKit

Query lambat sering muncul saat endpoint SvelteKit memanggil database dengan filter atau urutan yang tidak cocok dengan struktur penyimpanan. Untuk tim yang menghadapi masalah tersebut, solusi utamanya adalah mengidentifikasi query bermasalah, memastikan indeks database mendukung pola filter/urut, dan menghindari pagination offset yang mengabaikan seberapa cepat pertumbuhan data. Artikel ini langsung menunjukkan langkah teknis untuk memperbaiki jalur tersebut.

Identifikasi Query Bermasalah

Mulailah dengan mencatat durasi query dari middleware atau observability. Jika Anda menggunakan database seperti PostgreSQL atau MySQL, aktifkan slow query log atau gunakan fitur EXPLAIN ANALYZE untuk melihat rencana eksekusi. Fokus pada endpoint-endpoint SvelteKit yang menggabungkan filter dengan urutan, misalnya API berita yang mengurut berdasarkan created_at.

Langkah praktis:

  • Tambahkan logging di +server.ts atau service database untuk mencatat parameter dan waktu mulai/selesai.
  • Jalankan EXPLAIN pada query yang paling lama responnya dan perhatikan apakah database melakukan table scan.
  • Cek apakah SvelteKit mengirim parameter pagination seperti page atau cursor dengan nilai kosong, karena itu bisa memicu fallback ke full scan.

Catat pola filter dan urutan yang paling sering dipakai. Query yang lambat biasanya dilatarbelakangi oleh kolom filter yang tidak diindeks atau oleh penggunaan pagination offset yang memaksa database menghitung ulang ribuan baris.

Menentukan Index Efektif pada Database Relasional

Setelah mengetahui pola akses, buat index yang mendukung WHERE dan ORDER BY Anda. Di database relasional, index berfungsi sebagai shortcut untuk menemukan baris sesuai kondisi tanpa memindai seluruh tabel.

Pertimbangan saat memilih index

  • Urutan kolom di index: taruh kolom yang ada di WHERE di depan. Jika query Anda adalah WHERE published = true ORDER BY created_at DESC, index yang pas adalah (published, created_at DESC).
  • Index komposit lebih efisien daripada beberapa index terpisah, asalkan urutan kolom mencerminkan pola query.
  • Biaya tulis: setiap index tambahan memperlambat operasi INSERT/UPDATE. Hitung trade-off antara kecepatan baca dan beban tulis.

Gunakan pg_stat_user_indexes (PostgreSQL) atau SHOW INDEX (MySQL) untuk mengecek apakah index digunakan. Jika masih terjadi table scan, periksa apakah query mengekspresikan kondisi tambahan seperti fungsi pada kolom, karena itu bisa mengabaikan index.

Implementasi Cursor Pagination pada Endpoint

Pagination offset menyebabkan database membaca baris hingga offset tertentu, sehingga lambat saat data tumbuh. Cursor pagination menghindari hal ini dengan selalu mengambil data setelah posisi terakhir.

Contoh SvelteKit +server.ts yang menerima cursor dan limit, lalu menjalankan query index-friendly:

import type { RequestHandler } from './$types';
import { db } from '$lib/db';

export const GET: RequestHandler = async ({ url }) => {
  const cursor = url.searchParams.get('cursor');
  const limit = Math.min(Number(url.searchParams.get('limit') ?? 20), 100);

  const params = cursor
    ? cursor.split(',').map((value) => value.trim())
    : [new Date().toISOString(), Number.MAX_SAFE_INTEGER];

  const rows = await db.query(
    `SELECT id, title, updated_at
     FROM articles
     WHERE (updated_at, id) < ($1::timestamp, $2::bigint)
     ORDER BY updated_at DESC, id DESC
     LIMIT $3`,
    [...params, limit]
  );

  const nextCursor = rows.length === limit
    ? `${rows.at(-1)?.updated_at},${rows.at(-1)?.id}`
    : null;

  return new Response(JSON.stringify({ data: rows, nextCursor }), { headers: { 'Content-Type': 'application/json' } });
};

Pastikan tabel memiliki index komposit pada kolom (updated_at DESC, id DESC). Karena cursor mengekspresikan kondisi rangkap, index ini memungkinkan database langsung menempatkan pointer ke posisi yang cocok dan membaca limit baris berikutnya.

Untuk bagian frontend, contoh load function SvelteKit memanfaatkan endpoint di atas:

export const load = async ({ fetch, url }) => {
  const cursor = url.searchParams.get('cursor');
  const params = new URLSearchParams({ limit: '25' });
  if (cursor) params.set('cursor', cursor);

  const res = await fetch(`/api/articles?${params.toString()}`);
  const payload = await res.json();

  return {
    articles: payload.data,
    nextCursor: payload.nextCursor
  };
};

Dengan pola ini, client selalu meminta batch berikutnya berdasarkan cursor terakhir tanpa menyebut nomor halaman. API memberi tahu nextCursor, dan SvelteKit mempertahankan state tersebut di URL atau store.

Metrik Observability untuk Memantau Bottleneck SQL

Observability membantu memastikan perbaikan berjalan sesuai harapan. Catat metrik berikut:

  • Durasi query: rata-rata waktu eksekusi query endpoint yang relevan. Jika terus naik, itu indikator index tidak bekerja.
  • Rows returned vs rows scanned: rasio ini menandakan apakah query membaca jauh lebih banyak baris daripada yang dikembalikan.
  • Hit ratio index: pada PostgreSQL, pg_stat_user_tables menampilkan jumlah scan sequential vs index. Naikkan hit ratio dengan index yang cocok.

Gunakan middleware SvelteKit untuk mencatat durasi fetch, atau pasang setInterval di backend untuk menulis metrik ke observability stack seperti Prometheus. Jika angka durasi meningkat bersamaan dengan pertumbuhan data, telusuri apakah urutan kursornya konsisten dengan index, atau apakah parameter limit dibatasi terlalu tinggi.

Kesimpulan dan Praktik Lanjutan

Memperbaiki query lambat di SvelteKit menuntut pendekatan holistik: identifikasi query bermasalah, pilih index sesuai pola akses, dan gunakan cursor pagination agar pertumbuhan data tidak mengorbankan performa. Jangan lupa memantau metrik SQL untuk menangkap degradasi lebih awal.

Setelah menyelesaikan langkah ini, pertimbangkan untuk menambah caching read-heavy, meninjau strategi vacuum/analyze pada database, dan mengotomatiskan pengecekan index usage. Dengan demikian, endpoint SvelteKit tetap responsif sewaktu beban dan volume data meningkat.