Aplikasi media sosial dan platform feed meme mengandalkan fitur infinite scroll yang menuntut latency query konsisten. Saat tabel feed mencapai jutaan baris, implementasi pagination konvensional menggunakan sintaks OFFSET dan LIMIT menyebabkan degradasi performa drastis dan membebani I/O database.
Masalah Kinerja OFFSET pada Volume Data Masif
Pola pagination standar umumnya ditulis seperti berikut:
SELECT id, title, image_url, created_at
FROM memes
ORDER BY created_at DESC
LIMIT 20 OFFSET 200000;Database tidak dapat langsung melompat ke baris ke-200.001. Mesin SQL (seperti PostgreSQL) membaca indeks atau memindai tabel untuk 200.020 baris pertama, mengurutkannya di memori atau disk, membuang 200.000 baris pertama, dan hanya mengembalikan 20 baris terakhir. Kompleksitas operasi ini berskala O(N) terhadap nilai offset. Akibatnya, latensi meningkat tajam seiring pengguna melakukan scroll ke halaman yang lebih dalam (deep pagination).
Solusi: Keyset Pagination (Seek Method)
Keyset pagination (cursor-based pagination) menghilangkan OFFSET dengan menyaring baris menggunakan nilai baris terakhir dari halaman sebelumnya. Pendekatan ini memerlukan penanda posisi (cursor) deterministik dan indeks komposit yang selaras dengan klausa WHERE serta ORDER BY.
Karena kolom created_at dapat memiliki nilai duplikat (beberapa meme diunggah pada detik yang sama), tambahkan kolom id yang bersifat unik dan monotonik sebagai pemutus seri (tie-breaker).
1. Membuat Composite Index
CREATE INDEX idx_memes_created_at_id ON memes (created_at DESC, id DESC);2. Menjalankan Query Keyset
Ketika client mengambil halaman berikutnya, kirimkan nilai created_at dan id dari item terakhir halaman aktif:
-- Halaman pertama
SELECT id, title, image_url, created_at
FROM memes
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- Halaman berikutnya (menggunakan cursor: $last_created_at, $last_id)
SELECT id, title, image_url, created_at
FROM memes
WHERE (created_at, id) < ($last_created_at, $last_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;Penggunaan row-value comparison (created_at, id) < (...) memungkinkan perencana query menggunakan operator seek langsung pada B-Tree index, menjadikan kompleksitas query stabil pada O(log N) tanpa mempedulikan seberapa dalam feed diakses.
Analisis EXPLAIN ANALYZE PostgreSQL
Berikut perbandingan rencana eksekusi pada tabel memes berisi 5.000.000 baris.
Eksekusi dengan OFFSET
EXPLAIN ANALYZE
SELECT id, title, image_url, created_at
FROM memes
ORDER BY created_at DESC
LIMIT 20 OFFSET 200000;
-- Execution plan:
-- Limit (cost=30542.12..30545.17 rows=20 width=128) (actual time=142.311..142.325 rows=20 loops=1)
-- -> Index Scan using idx_memes_created_at on memes (cost=0.43..763541.20 rows=5000000 width=128) (actual time=0.082..128.450 rows=200020 loops=1)
-- Execution Time: 142.360 msEksekusi dengan Keyset Pagination
EXPLAIN ANALYZE
SELECT id, title, image_url, created_at
FROM memes
WHERE (created_at, id) < ('2026-03-20 10:15:30.123456+00', 3241501)
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- Execution plan:
-- Limit (cost=0.43..1.22 rows=20 width=128) (actual time=0.041..0.065 rows=20 loops=1)
-- -> Index Scan using idx_memes_created_at_id on memes (cost=0.43..198234.11 rows=5000000 width=128) (actual time=0.039..0.059 rows=20 loops=1)
-- Index Cond: (ROW(created_at, id) < ROW('2026-03-20 10:15:30.123456+00'::timestamp with time zone, 3241501))
-- Execution Time: 0.088 msKeyset pagination mengeliminasi pembacaan 200.000 baris yang tidak dibutuhkan, menurunkan waktu eksekusi dari 142 ms menjadi kurang dari 1 ms.
Batasan dan Tantangan Keyset Pagination
- Tidak Ada Navigasi Halaman Acak: Klien tidak dapat melompat langsung ke halaman acak (misalnya: halaman 42). Pola ini cocok khusus untuk alur infinite scroll atau antarmuka "Next / Previous".
- Sorting Multi-arah (Mixed Direction Sort): Jika query memerlukan pengurutan kolom dengan arah berlawanan, misalnya
ORDER BY likes_count DESC, created_at ASC, perbandingan tuple(likes_count, created_at) < (...)tidak dapat langsung digunakan di sebagian mesin database. Solusinya adalah memecah logika filter menggunakan klausaWHEREekspansif atau menyusun composite index yang secara eksplisit merefleksikan arah masing-masing kolom:CREATE INDEX idx_mixed ON memes (likes_count DESC, created_at ASC);. - Konsistensi Token Cursor: Serialisasi cursor ke format base64 aman di URL (misal: encoding nilai timestamp dan ID) diperlukan untuk menjaga enkapsulasi state pada payload API REST atau GraphQL.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!