Comment concevoir la pagination d'API pour des millions d'enregistrements ?

Ashley Innocent

Ashley Innocent

13 March 2026

Comment concevoir la pagination d'API pour des millions d'enregistrements ?

enterprise.banner.title

enterprise.banner.feature1

enterprise.banner.feature2

enterprise.banner.feature3

enterprise.banner.ctaB

TL;DR

Pour les grands ensembles de données, utilisez la pagination basée sur un curseur ou sur un jeu de clés au lieu de la pagination basée sur un décalage. La pagination par décalage (?page=1&limit=20) est peu performante avec des millions d'enregistrements et peut entraîner des incohérences de données. La Modern PetstoreAPI implémente une pagination basée sur un curseur avec des jetons opaques et des liens HATEOAS pour des résultats efficaces et cohérents.

Introduction

Votre API renvoie une liste d'animaux de compagnie. Vous avez 10 millions d'animaux de compagnie dans la base de données. Un client demande GET /pets?page=500000&limit=20. Votre base de données exécute OFFSET 10000000 LIMIT 20. La requête prend 30 secondes. Votre API expire.

C'est le problème de la pagination par décalage. Elle fonctionne bien pour les petits ensembles de données mais échoue à grande échelle. La base de données doit parcourir des millions de lignes pour atteindre le décalage, même si vous ne renvoyez que 20 résultats.

L'ancien Swagger Petstore n'aborde pas du tout la pagination. La Modern PetstoreAPI implémente une pagination basée sur un curseur qui s'adapte à des millions d'enregistrements avec des performances constantes.

💡
Si vous construisez ou testez des API REST, Apidog vous aide à tester le comportement de la pagination, à valider les formats de réponse et à garantir que votre API gère correctement les grands ensembles de données. Vous pouvez simuler des scénarios de pagination, tester des cas limites et vérifier les performances.
bouton

Dans ce guide, vous apprendrez pourquoi la pagination par décalage échoue, comment fonctionne la pagination basée sur un curseur et comment la Modern PetstoreAPI implémente une pagination efficace.

Pourquoi la pagination par décalage échoue à grande échelle

La pagination par décalage est l'approche la plus courante, mais elle présente de sérieux problèmes.

Comment fonctionne la pagination par décalage

GET /pets?page=1&limit=20    → OFFSET 0 LIMIT 20
GET /pets?page=2&limit=20    → OFFSET 20 LIMIT 20
GET /pets?page=3&limit=20    → OFFSET 40 LIMIT 20

La base de données ignore les lignes offset et renvoie les lignes limit.

Problème 1 : Les performances se dégradent avec le numéro de page

Page 1 :

SELECT * FROM pets OFFSET 0 LIMIT 20;
-- Rapide : scanne 20 lignes

Page 1000 :

SELECT * FROM pets OFFSET 20000 LIMIT 20;
-- Lent : scanne 20 020 lignes, renvoie 20

Page 500 000 :

SELECT * FROM pets OFFSET 10000000 LIMIT 20;
-- Très lent : scanne 10 000 020 lignes, renvoie 20

La base de données doit scanner toutes les lignes jusqu'au décalage, même si elle les ignore. Les performances se dégradent linéairement avec le numéro de page.

Problème 2 : Résultats incohérents

Pendant qu'un client parcourt les résultats, les données changent :

Requête 1 :

GET /pets?page=1&limit=2

Renvoie : [Animal A, Animal B]

Quelqu'un ajoute l'Animal Z (se classe en premier par ordre alphabétique)

Requête 2 :

GET /pets?page=2&limit=2

Renvoie : [Animal B, Animal C] ← L'Animal B apparaît deux fois !

L'Animal B est apparu sur les deux pages parce qu'un nouvel animal a été inséré. Inversement, des animaux peuvent être ignorés si des suppressions ont lieu.

Problème 3 : La pagination profonde est coûteuse

Les utilisateurs dépassent rarement la page 10. Mais si votre API autorise ?page=1000000, vous devez la gérer. Les requêtes de pagination profonde sont coûteuses et peuvent être utilisées pour des attaques par déni de service.

Quand la pagination par décalage est acceptable

La pagination par décalage convient pour :

Pour les API publiques ou les grands ensembles de données, utilisez la pagination basée sur un curseur.

Explication de la pagination basée sur un curseur

La pagination basée sur un curseur utilise un jeton opaque pour marquer la position dans l'ensemble de résultats.

Comment ça marche

Requête 1 :

GET /pets?limit=20

Réponse 1 :

{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9",
    "hasMore": true
  }
}

Requête 2 :

GET /pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20

Le curseur est un jeton opaque (généralement encodé en base64) qui encode la position. Le client ne le parse pas, il le renvoie simplement.

Avantages

1. Performances constantes

La base de données utilise un index pour trouver directement la position du curseur :

SELECT * FROM pets
WHERE id > '019b4132-70aa-764f-b315-e2803d882a24'
ORDER BY id
LIMIT 20;

Cette requête est rapide quelle que soit la position dans l'ensemble de données. Elle utilise une recherche d'index, pas un balayage.

2. Résultats cohérents

Les curseurs sont stables. Si les données changent entre les requêtes, vous obtenez toujours des résultats cohérents. Les nouveaux enregistrements ne provoquent pas de doublons ni d'omissions.

3. Pas d'attaques de pagination profonde

Les clients ne peuvent pas sauter à des positions arbitraires. Ils doivent paginer séquentiellement, ce qui limite les abus.

Format du curseur

Les curseurs sont généralement des JSON encodés en base64 :

// Curseur décodé
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "createdAt": "2026-03-13T10:30:00Z"
}

Le curseur contient suffisamment d'informations pour reprendre la pagination. Pour la Modern PetstoreAPI, cela inclut l'ID de la ressource et le champ de tri.

Pagination par jeu de clés pour les données triées

La pagination par jeu de clés est une variante de la pagination basée sur un curseur pour les données triées.

Comment ça marche

Au lieu d'un curseur opaque, vous utilisez la dernière valeur de la page précédente :

Requête 1 :

GET /pets?limit=20&sortBy=createdAt

Réponse 1 :

{
  "data": [
    {"id": "...", "createdAt": "2026-03-13T10:00:00Z"},
    ...
    {"id": "...", "createdAt": "2026-03-13T10:30:00Z"}
  ]
}

Requête 2 :

GET /pets?limit=20&sortBy=createdAt&after=2026-03-13T10:30:00Z

Le paramètre after utilise la dernière valeur de createdAt de la page précédente.

Requête SQL

SELECT * FROM pets
WHERE created_at > '2026-03-13T10:30:00Z'
ORDER BY created_at
LIMIT 20;

Ceci est efficace car cela utilise un index sur created_at.

Quand utiliser la pagination par jeu de clés

La Modern PetstoreAPI utilise la pagination basée sur un curseur par défaut mais prend en charge la pagination par jeu de clés pour les données de séries temporelles.

Comment la Modern PetstoreAPI implémente la pagination

La Modern PetstoreAPI utilise la pagination basée sur un curseur avec des liens HATEOAS.

Format de la requête

GET /pets?limit=20
GET /pets?cursor={token}&limit=20

Paramètres :

Format de la réponse

{
  "data": [
    {
      "id": "019b4132-70aa-764f-b315-e2803d882a24",
      "name": "Fluffy",
      "species": "CAT"
    }
  ],
  "pagination": {
    "limit": 20,
    "hasMore": true,
    "nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9"
  },
  "links": {
    "self": "https://petstoreapi.com/pets?limit=20",
    "next": "https://petstoreapi.com/pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20"
  }
}

Fonctionnalités clés

1. Curseurs opaques

Les curseurs sont encodés en base64. Les clients ne les analysent pas.

2. Liens HATEOAS

L'objet links fournit des URL prêtes à l'emploi. Les clients n'ont pas besoin de construire les URL de pagination.

3. Indicateur hasMore

Indique si d'autres résultats existent. Les clients savent quand arrêter de paginer.

4. Validation de la limite

La limite maximale est de 100. Empêche les clients de demander des pages trop volumineuses.

Consultez la documentation de la pagination de la Modern PetstoreAPI pour des détails complets.

Format de la réponse de pagination

La Modern PetstoreAPI encapsule les réponses paginées dans une structure cohérente.

Enveloppe de collection

{
  "data": [...],
  "pagination": {...},
  "links": {...}
}

Pourquoi envelopper les collections ?

  1. Extensibilité - Peut ajouter des métadonnées sans casser les clients
  2. Cohérence - Tous les points de terminaison paginés utilisent le même format
  3. HATEOAS - Les liens guident les clients à travers la pagination

Métadonnées de pagination

"pagination": {
  "limit": 20,
  "hasMore": true,
  "nextCursor": "...",
  "totalCount": 1000  // Optionnel, coûteux à calculer
}

totalCount est optionnel car son calcul est coûteux pour les grands ensembles de données. La plupart des clients n'en ont pas besoin.

Tester la pagination avec Apidog

Apidog vous aide à tester le comportement de la pagination de manière exhaustive.

Scénarios de test

1. Première page

GET /pets?limit=20

Attendre : 20 résultats, hasMore=true, nextCursor présent

2. Pages suivantes

GET /pets?cursor={token}&limit=20

Attendre : 20 résultats, hasMore=true/false, nextCursor présent/absent

3. Dernière page

GET /pets?cursor={lastToken}&limit=20

Attendre : < 20 résultats, hasMore=false, pas de nextCursor

4. Résultats vides

GET /pets?status=NONEXISTENT&limit=20

Attendre : 0 résultats, hasMore=false, pas de nextCursor

5. Validation de la limite

GET /pets?limit=1000

Attendre : 400 Bad Request (dépasse la limite maximale)

Configuration de test Apidog

// Test : Structure de pagination
pm.test("Response has pagination", () => {
  pm.expect(pm.response.json()).to.have.property('pagination');
  pm.expect(pm.response.json().pagination).to.have.property('hasMore');
});

// Test : Liens HATEOAS
pm.test("Response has links", () => {
  const links = pm.response.json().links;
  pm.expect(links).to.have.property('self');
  if (pm.response.json().pagination.hasMore) {
    pm.expect(links).to.have.property('next');
  }
});

Choisir la bonne stratégie de pagination

Différentes stratégies conviennent à différents cas d'utilisation.

Pagination par décalage

Utiliser quand :

Ne pas utiliser quand :

Pagination basée sur un curseur

Utiliser quand :

Ne pas utiliser quand :

Pagination par jeu de clés

Utiliser quand :

Ne pas utiliser quand :

Recommandation de la Modern PetstoreAPI : Utilisez la pagination basée sur un curseur pour les API publiques et les grands ensembles de données.

Conclusion

La pagination est essentielle pour les API qui renvoient de grands ensembles de données. La pagination par décalage est simple mais ne s'adapte pas. La pagination basée sur un curseur offre des performances constantes et des résultats fiables pour des millions d'enregistrements.

La Modern PetstoreAPI implémente une pagination basée sur un curseur avec des jetons opaques, des liens HATEOAS et des métadonnées appropriées. Cette conception s'adapte efficacement et offre une excellente expérience aux développeurs.

Testez votre implémentation de pagination avec Apidog pour vous assurer qu'elle gère les cas limites, valide les limites et renvoie des résultats cohérents.

Points clés à retenir :

bouton

FAQ

Pourquoi ne pas simplement renvoyer tous les résultats sans pagination ?

Renvoyer des millions d'enregistrements en une seule réponse provoque des problèmes de mémoire, un transfert réseau lent et une mauvaise expérience utilisateur. La pagination est essentielle pour les grands ensembles de données.

Les clients peuvent-ils sauter à une page spécifique avec la pagination par curseur ?

Non, la pagination par curseur nécessite un accès séquentiel. Si un accès aléatoire est nécessaire, envisagez la pagination par décalage pour les petits ensembles de données ou implémentez la recherche/le filtrage à la place.

Comment gérer la pagination avec le filtrage ?

Incluez les paramètres de filtre dans les requêtes de pagination : GET /pets?status=AVAILABLE&cursor={token}&limit=20. Le curseur encode à la fois la position et l'état du filtre.

Dois-je inclure le nombre total dans les réponses de pagination ?

Seulement si les clients en ont besoin et si votre ensemble de données est petit. Le calcul du nombre total est coûteux pour les grands ensembles de données (nécessite une requête COUNT séparée).

Comment implémenter la pagination par curseur en SQL ?

Utilisez une clause WHERE avec la valeur du curseur : SELECT * FROM pets WHERE id > ? ORDER BY id LIMIT 20. Assurez-vous d'avoir un index sur la colonne de tri.

Que se passe-t-il si mes jetons de curseur deviennent invalides ?

Renvoie 400 Bad Request avec un message d'erreur. Les curseurs peuvent devenir invalides si des données sont supprimées ou si l'état de la pagination expire.

Combien de temps les curseurs doivent-ils rester valides ?

Les curseurs de la Modern PetstoreAPI sont valides indéfiniment tant que la ressource référencée existe. Certaines API expirent les curseurs après 24 heures.

Puis-je utiliser la pagination par curseur avec plusieurs champs de tri ?

Oui, mais le curseur doit encoder tous les champs de tri. Cela rend les curseurs plus complexes. Envisagez d'utiliser une seule clé de tri composite à la place.

Pratiquez le Design-first d'API dans Apidog

Découvrez une manière plus simple de créer et utiliser des API

Comment concevoir la pagination d'API pour des millions d'enregistrements ?