Techniques de pagination en backend : approches par curseur et par offset

Pagination par curseur

Les paramètres clés sont un curseur et une limite. L'idée est de récupérer un ensemble d'enregistrements à partir d'un point spécifique, comme le dernier identifiant vu. La requête SQL ressemble à :

WHERE identifiant > curseur LIMIT n

Cette méthode offre une performance constante (O(1)) indépendamment du volume de données. Elle garantit la cohérence des données sans répétition ni omission, même lors d'insertions. Cependant, elle ne permet que la navigation séquentielle sans saut direct, et le nombre total de pages n'est pas directement accessible.

Pagination par offset

Cette approche utilise des paramètres tels que numéro de page et taille de page, ou un décalage et une limite. La base de données ignore un certain nombre d'enregistrements avant de sélectionner les suivants :

OFFSET décalage LIMIT n

La performance dégrade avec l'augmentation de l'offset (complexité O(offset)). Il existe un risque de répétition ou d'omission dus aux insertions concurrentes. En revanche, elle permet un saut direct à n'importe quelle page et le calcul du total des pages est possible.

Comparaison illustrative

Prenons une séquence de données chronologiques : [A] [B] [C] [D] [E] [F] [G] [H]. Avec la pagination par curseur :

  • Première requête : curseur vide, limite=3 → retourne [A] [B] [C], le prochain curseur est l'identifiant de C.
  • Seconde requête : curseur basé sur C, limite=3 → retourne [D] [E] [F], prochain curseur basé sur F.
  • Troisième requête : curseur basé sur F, limite=3 → retourne [G] [H], indique qu'il n'y a plus de données.

Pour la pagination par offset avec 10 éléments et une taille de page de 3 :

  • Page 1, offset=0 : [A] [B] [C]
  • Page 2, offset=3 : [D] [E] [F]
  • Page 3, offset=6 : [G] [H] [I]
  • Page 4, offset=9 : [J]

Exemples de code

Voici des implémentations simplifiées en Go, avec des modifications pour réduire la similarité au code original :

// Fonction pour la pagination par curseur
// Requête : GET /api/équipes/{id}/membres?curseur=xxx&limite=20
func (s *ServiceEquipe) ObtenirMembresParCurseur(ctx context.Context, idEquipe string, curseurParam string, limite int64) (*ListeMembres, error) {
    var reference int64
    if curseurParam != "" {
        reference, _ = strconv.ParseInt(curseurParam, 10, 64)
    }
    requêteSQL := `
        SELECT identifiant, séquence FROM membres_équipe 
        WHERE id_équipe = ? AND séquence > ? 
        ORDER BY séquence ASC 
        LIMIT ?`
    lignes, err := db.Query(requêteSQL, idEquipe, reference, limite)
    if err != nil {
        return nil, err
    }
    // Calcul du prochain curseur basé sur la dernière ligne
    dernierSéq := lignes[len(lignes)-1].Séquence
    return &ListeMembres{
        Membres:    lignes,
        ProchainCurseur: strconv.FormatInt(dernierSéq, 10),
        PlusDeDonnées: len(lignes) == int(limite),
    }, nil
}

// Fonction pour la pagination par offset
// Requête : GET /api/équipes/{id}/membres?page=3&taille_page=20
func ObtenirMembresParPage(ctx context.Context, idEquipe string, page, taillePage int) (*RésultatPage, error) {
    décalage := (page - 1) * taillePage
    requêteSQL := `
        SELECT identifiant FROM membres_équipe 
        WHERE id_équipe = ? 
        ORDER BY identifiant ASC 
        LIMIT ? OFFSET ?`
    lignes, err := db.Query(requêteSQL, idEquipe, taillePage, décalage)
    if err != nil {
        return nil, err
    }
    total, _ := db.QueryInt(`SELECT COUNT(*) FROM membres_équipe WHERE id_équipe = ?`, idEquipe)
    totalPages := (total + taillePage - 1) / taillePage
    return &RésultatPage{
        Membres:   lignes,
        Page:      page,
        TaillePage: taillePage,
        Total:     total,
        TotalPages: totalPages,
    }, nil
}

Guide de sélection

Le choix dépend du contexte d'utilisation :

  • Flux d'activités en temps réel : préférez la pagination par curseur pour sa performance stable malgré les isnertions fréquentes.
  • Listes de messages : la pagination par curseur assure la cohérence pour une navigation bidirectionnelle.
  • Interfaces d'adminisrtation : utilisez la pagination par offset pour permettre des sauts directs et afficher le nombre total de pages.
  • Résultats de recherche : la pagination par offset est adaptée pour montrer l'information sur la pagination aux utilisateurs.
  • Export de données volumineuses : la pagination par curseur évite les répétitions ou omissions critiques.
  • Classements dynamiques : évaluez selon les besoins en temps réel ou en saut de pages.

Étiquettes: cursor-pagination offset-pagination SQL backend-architecture Go-language

Publié le 6 juillet à 00h42