SQL

Anti-pattern: deep OFFSET — switch to keyset

OFFSET 20000 reads and discards 20,000 rows on every page. Keyset (seek) pagination resumes from the last item seen: constant cost regardless of depth.

Prerequisites

Index sur les colonnes de tri (created_at DESC, id DESC)

SQL
-- Anti-pattern : la page 1000 lit 20 020 lignes pour en garder 20
SELECT * FROM products
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 20000;

-- Correctif : keyset — on repart du dernier élément de la page précédente
SELECT * FROM products
WHERE (created_at, id) < ('2026-06-01 12:34:56', 18432)
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- Le tuple (created_at, id) garantit un ordre total même à timestamp égal

Result

  id   |     created_at      |   name
-------+---------------------+-----------
 18431 | 2026-06-01 12:31:08 | Produit A
 18419 | 2026-06-01 12:18:55 | Produit B
 18402 | 2026-06-01 11:59:02 | Produit C
(20 rows)

-- OFFSET 20000 : Execution Time: 412.480 ms (lit 20 020 lignes)
-- Keyset       : Execution Time:   0.524 ms (lit 20 lignes)
SQLPaginationKeysetPerformance

Related snippets

Back to the Data Lab