L’overfetching est un problème d’API où les réponses contiennent plus de données que nécessaire ; l’underfetching impose des requêtes en plus pour compléter. Ce sont les deux modes de défaillance jumeaux des formes de réponse figées — l’un gaspille des octets, l’autre des allers-retours — et la plupart des API commettent les deux sur le même écran : chaque réponse trop lourde, et bien trop de réponses.
Points clés
| Question | Réponse |
|---|---|
| Overfetching | Trop par réponse — bande passante, parsing, batterie, exposition |
| Underfetching | Trop peu par réponse — allers-retours en plus, cascades, N+1 |
| La cause profonde | Des formes d’endpoint figées face à des écrans aux besoins différents |
| Les correctifs REST | Sparse fieldsets · paramètres include/expand · pagination · endpoints composites |
| La réponse de GraphQL | Les selection sets — avec des réserves honnêtes au niveau des resolvers |
Un écran, trois façons de le récupérer
Une liste de posts qui affiche chaque titre avec le nom de son auteur :
Overfetching Underfetching
GET /posts GET /posts (IDs d'auteur uniquement)
→ 20 posts × 40 champs GET /users/11 ┐
→ ~160 KB expédiés, GET /users/12 │ 20 appels de plus —
~6 KB affichés (96% de gaspillage) … │ la cascade N+1
GET /users/30 ┘
La requête façonnée
GET /posts?fields=title,summary,author&include=author&limit=20
→ 20 posts × 3 champs + leurs auteurs — un aller-retour, ~7 KB
La même requête façonnée en code SDK — projection et relation en une seule requête :
// JavaScript / Node.js — Back4app JS SDK
// One shaped query: no overfetch, no underfetch
const query = new Parse.Query('Post');
query.select('title', 'summary', 'author'); // only what the screen renders
query.include('author'); // related object, same response
query.limit(20); // bounded page
const posts = await query.find();
// 1 round trip — not 1 list call + 20 author calls (N+1) // Flutter / Dart — Back4app Flutter SDK
// One shaped query: no overfetch, no underfetch
final query = QueryBuilder<ParseObject>(ParseObject('Post'))
..keysToReturn(['title', 'summary', 'author']) // only what the screen renders
..includeObject(['author']) // related object, same response
..setLimit(20); // bounded page
final response = await query.query();
// 1 round trip — not 1 list call + 20 author calls (N+1) // iOS / Swift — Back4app Swift SDK
// One shaped query: no overfetch, no underfetch
let query = Post.query()
.select("title", "summary", "author") // only what the screen renders
.include("author") // related object, same response
.limit(20) // bounded page
query.find { result in
// 1 round trip — not 1 list call + 20 author calls (N+1)
if case .success(let posts) = result { render(posts) }
} // Android / Kotlin — Back4app Android SDK
// One shaped query: no overfetch, no underfetch
val query = ParseQuery.getQuery<ParseObject>("Post")
query.selectKeys(listOf("title", "summary", "author")) // only what the screen renders
query.include("author") // related object, same response
query.limit = 20 // bounded page
query.findInBackground { posts, e -> if (e == null) render(posts) }
// 1 round trip — not 1 list call + 20 author calls (N+1) Qu’est-ce que l’overfetching ?
L’overfetching est la version, au niveau de l’API, du SELECT * : l’endpoint renvoie sa représentation figée complète, quoi que l’appelant affiche. Les coûts s’empilent par couches. Le serveur sérialise des champs que personne ne lit ; le réseau les transporte — et c’est là que le mobile souffre, puisque le temps de transfert croît avec le nombre d’octets sur une bande passante contrainte et que chaque kilo-octet superflu consomme du forfait data et de la batterie radio ; puis le client parse le tout, car la décompression a lieu avant le rendu et une réponse obèse reste un travail de parsing obèse, même quand la compression l’a masquée sur le réseau.
Le coût le plus discret, c’est l’exposition. Un champ de réponse que l’UI n’affiche jamais n’est qu’à un clic des outils de développement — flags internes, adresses e-mail d’autres utilisateurs, données de marge. L’OWASP API Security Top 10 le recense comme exposition excessive de données (broken object property level authorization) : le moindre privilège s’applique aux corps de réponse, et un champ qu’aucun client ne devrait voir ne devrait jamais être sérialisé.
Qu’est-ce que l’underfetching ?
L’underfetching est le défaut inverse : la forme figée de l’endpoint transporte trop peu, si bien que le client devient intégrateur — récupérer la liste, puis l’auteur de chaque élément, puis peut-être l’avatar de chaque auteur. Chaque appel en plus est un aller-retour complet, et les allers-retours sont la monnaie dont les réseaux mobiles sont les plus démunis : à 100 ms par requête, chiffre réaliste, une liste de 20 éléments résolue séquentiellement consomme deux secondes rien qu’en latence, avant même le moindre calcul de payload.
À grande échelle, cette cascade porte un nom — le problème des requêtes N+1 : un appel pour N éléments, N appels pour leurs détails. La forme est fractale ; elle réapparaît partout où une interface figée rencontre des données relationnelles — clients HTTP face aux endpoints REST, resolvers GraphQL face à la base de données, ORM qui enchaînent les lazy loading dans une boucle — et le remède est toujours une forme de regroupement des N en un.
Overfetching vs. underfetching
| Overfetching | Underfetching | |
|---|---|---|
| Symptôme | Des réponses pleines de champs jamais affichés | Des écrans assemblés à partir de nombreux appels |
| Unité de gaspillage | Les octets (et le temps de parsing) | Les allers-retours (et la latence) |
| Pire sur | Réseaux lents, facturés au volume, limités par la batterie | Réseaux à forte latence — les cascades s’additionnent |
| Détection | Comparer champs renvoyés vs. champs affichés | Compter les requêtes par écran dans l’onglet réseau |
| Correctif direct | Sparse fieldsets / projection | Paramètres d’expansion, endpoints composites |
| Escalade | Exposition excessive de données (sécurité) | Tempêtes de requêtes N+1 (échelle) |
Le diagnostic est heureusement mécanique, et aucun article de vulgarisation ne le dit : ouvrez l’onglet réseau sur un écran. Beaucoup de requêtes pour une seule vue, c’est de l’underfetching ; de grosses réponses dont vous ne retrouvez pas les champs dans l’UI, c’est de l’overfetching. L’analytique par endpoint généralise l’audit — taille de payload p95 par endpoint, requêtes par session et par écran.
Corriger les deux sans quitter REST
La migration vers GraphQL n’est pas le premier recours ; les conventions REST matures couvrent l’essentiel de l’écart :
- Sparse fieldsets — un paramètre
fieldsqui projette la représentation : standardisé sous le nom de sparse fieldsets JSON:API, repris par les options de requête de type$selectet les buildersselect()des SDK. Le correctif de l’overfetching à la source. - Paramètres d’expansion —
include=author,commentsembarque les ressources liées dans la même réponse (documents composés), transformant une cascade N+1 en une seule requête. Le correctif de l’underfetching à la source. - Pagination — borne la dimension « liste » de l’overfetching ; les collections non bornées sont des bugs de payload qui grandissent avec l’adoption.
- Endpoints sur mesure et composites — quand un écran a toujours besoin du même agrégat, donnez-lui un endpoint qui renvoie exactement cet agrégat, assemblé côté serveur, là où la latence entre services se compte en microsecondes et non en allers-retours mobiles.
- Un backend-for-frontend — la version architecturale du même geste : une fine couche par client qui dialogue avec des API internes généreuses et sert à chaque frontend exactement sa forme (le pattern BFF de Sam Newman).
- Compression — honnêtement en dernière place : elle réduit ce qui transite, pas le gaspillage ; le coût du parsing et l’exposition lui survivent intacts.
GraphQL règle-t-il le problème ?
En grande partie — et ce « en grande partie » mérite d’être compris. Les selection sets font de la liste de champs du client la requête elle-même, ce qui met à la retraite l’overfetching classique, et les requêtes imbriquées assemblent les données liées en un aller-retour, ce qui met à la retraite l’underfetching classique. C’est exactement pour cela que le débat GraphQL vs. REST commence par ces deux mots.
Les réserves se trouvent une couche plus bas. Les clients qui copient-collent des requêtes généreuses font de l’overfetching par habitude — rien ne garantit qu’une requête corresponde à ce qu’un composant affiche, à moins que l’équipe n’adopte des fragments par composant. Et une chaîne de resolvers naïve fait de l’underfetching face à la base de données : une requête sur 20 posts avec leurs auteurs devient 1 + 20 lectures en base, sauf si les resolvers regroupent via une couche de loading — le même N+1, déplacé. GraphQL déplace le problème vers une couche que vous contrôlez, ce qui est un vrai progrès ; il ne le supprime pas.
Cas d’usage courants
Là où les deux problèmes (et leurs correctifs) apparaissent en premier :
- Écrans de liste sur mobile — l’overfetching canonique : des lignes complètes expédiées pour afficher trois champs par cellule.
- Écrans de détail avec relations — post + auteur + commentaires : cascades d’underfetching, sauf expansion ou endpoint composite.
- Marchés à réseau lent — les deux problèmes taxés au taux maximal ; les requêtes façonnées comme enjeu d’accessibilité.
- Dashboards — des écrans d’agrégats qui font soit de l’overfetching de lignes brutes, soit de l’underfetching à travers cinq services ; le terrain du BFF.
- API publiques aux consommateurs variés — une forme figée ne peut pas convenir à la fois à un cadran de montre et à une console d’administration ; les paramètres de projection et d’expansion laissent chaque appelant ajuster.
Quel problème avez-vous ? Matrice de décision
| Indice dans l’onglet réseau | Diagnostic | Premier correctif |
|---|---|---|
| Une requête, une grosse réponse, peu de champs affichés | Overfetching | Sparse fieldsets / select() |
| Beaucoup de requêtes séquentielles par écran | Underfetching | include / paramètres d’expansion |
| Le nombre de requêtes croît avec la longueur de la liste | N+1 | Regroupement : expansion ou endpoint composite |
| À la fois lourdes et nombreuses | Les deux — fréquent | Requête façonnée ou sélection GraphQL |
| Des champs de réponse que vous préféreriez voir rester sur le serveur | Exposition | Élaguer la sérialisation côté serveur, pas côté client |
Limites et trade-offs
- La projection couple les clients aux listes de champs. Un paramètre
fieldsqui diverge de l’UI provoque des bugs de données manquantes ; des types générés et la revue de code gardent les sélections fidèles. - L’expansion peut surcorriger. Un
include=commentssur une liste très sollicitée peut expédier des mégaoctets de relations embarquées — les réponses étendues ont besoin de leur propre pagination et de limites de profondeur. - Les endpoints sur mesure se multiplient. Les endpoints par écran corrigent la récupération mais créent une facture de maintenance liée à leur prolifération ; les BFF concentrent cette prolifération dans une couche dont quelqu’un est propriétaire, au prix de devoir l’exploiter.
- La flexibilité côté serveur a un prix. Projection et expansion arbitraires compliquent la mise en cache (chaque forme est une clé de cache) et l’autorisation (chaque combinaison doit pouvoir être servie sans risque).
- Ces problèmes sont aussi des signaux de modélisation. Un écran qui exige un élagage profond ou cinq includes vous dit peut-être que les formes des ressources sont mauvaises — parfois, le remède est le modèle de données, pas la récupération.
Overfetching et underfetching sur Back4app
Back4app est une plateforme open-source de Backend as a Service (BaaS) qui combine une base de données gérée, des API REST et GraphQL générées automatiquement, l’authentification, le stockage de fichiers et des fonctions serverless avec Cloud Code. Les deux correctifs sont livrés comme primitives de requête dans chaque SDK — les onglets de code ci-dessus constituent tout le pattern : select() est le sparse fieldset, include() est le paramètre d’expansion, et ensemble ils transforment une cascade 1 + N en un seul aller-retour façonné vers l’API REST générée automatiquement. Quand les clients veulent un contrôle total sur la forme de la réponse, les mêmes données sont interrogeables via les selection sets GraphQL ; quand un écran a besoin d’un agrégat côté serveur, une fonction Cloud Code est un endpoint composite que vous écrivez dans un seul fichier. La récupération épouse l’écran — par l’idiome, pas par la refonte des endpoints.
Questions fréquentes
Qu'est-ce que l'overfetching ?
Une API renvoie plus de données que le client n'en a besoin pour la tâche en cours — un écran de profil qui affiche trois champs en reçoit quarante. Le gaspillage se paie quatre fois : sérialisation côté serveur, transfert sur le réseau, parsing côté client et, sur mobile, batterie et forfait data. Il peut aussi exposer des champs qu'aucun client ne devrait voir.
Qu'est-ce que l'underfetching ?
Un endpoint unique ne renvoie pas assez de données pour afficher l'écran, si bien que le client lance des requêtes supplémentaires pour l'assembler. Chaque appel en plus est un aller-retour réseau complet ; quand les appels sont séquentiels — récupérer la liste, puis les détails élément par élément — la latence s'accumule jusqu'au problème des requêtes N+1.
Quelle est la différence entre l'overfetching et l'underfetching ?
Le sens. L'overfetching signifie que chaque réponse transporte trop — le coût se compte en octets ; l'underfetching signifie que chaque réponse transporte trop peu — le coût se compte en allers-retours. Les deux naissent de la même racine : des formes de réponse figées, conçues une fois, consommées par des écrans aux besoins différents. Beaucoup d'API cumulent les deux, au même moment, sur le même écran.
GraphQL résout-il l'overfetching et l'underfetching ?
En grande partie, au niveau HTTP : les selection sets ne récupèrent que les champs demandés et les requêtes imbriquées rassemblent les données liées en une seule requête. Mais rien n'est automatique — les clients qui demandent des jeux de champs généreux recréent l'overfetching, et des resolvers naïfs recréent l'underfetching face à la base de données sous forme de N+1 au niveau des resolvers, ce que les loaders de regroupement existent précisément pour corriger.
Comment éviter l'overfetching dans une API REST ?
Les sparse fieldsets sont le correctif direct : un paramètre fields (ou select() dans les query builders des SDK) qui ne projette que les colonnes affichées par l'écran. La pagination borne la taille des listes, des endpoints sur mesure alignent les réponses sur les écrans réels, et la compression réduit ce qui reste — même si compresser l'embonpoint est une atténuation, pas un remède.
Comment corriger l'underfetching sans passer à GraphQL ?
Des paramètres d'expansion — include ou expand — qui embarquent les objets liés dans une seule réponse ; des documents composés qui livrent une ressource avec ses associations ; des endpoints composites qui agrègent côté serveur les besoins d'un écran ; et, sur le plan architectural, une couche backend-for-frontend qui fait l'assemblage près des données plutôt qu'à travers un réseau mobile.
Quel est le lien entre le problème N+1 et l'underfetching ?
Le N+1, c'est l'underfetching à grande échelle : une requête pour une liste de N éléments, puis N requêtes de suivi pour les détails de chacun. La même forme se répète à chaque couche — clients HTTP face aux endpoints REST, resolvers GraphQL face à la base de données, ORM qui chargent les relations en lazy loading — et le correctif repose toujours sur la même idée : regrouper les N en un.
Pourquoi l'overfetching est-il un risque de sécurité ?
Les champs qu'un écran n'affiche jamais traversent quand même le réseau — et n'importe qui peut ouvrir les outils de développement pour les lire. Des flags internes, des adresses e-mail et des données de coûts fuient ainsi ; les taxonomies de sécurité classent cela comme une exposition excessive de données, et le principe du moindre privilège s'applique aux corps de réponse tout autant qu'aux permissions.