Test de charge d'une API GraphQL : guide pratique
7 min de lecture
Pour un test de charge d’une API GraphQL, scriptez les opérations que vos clients envoient vraiment, donnez à chacune son propre nom dans l’outil de test et mélangez-les dans les proportions observées en production, avec des variables variées. Vérifiez dans chaque corps de réponse la présence d’un tableau errors, car un serveur GraphQL répond le plus souvent avec un statut HTTP 200 même quand une partie de l’opération a échoué. Augmentez ensuite la charge et jugez chaque opération sur ses propres percentiles.
Sources : graphql.org, le brouillon de la spécification GraphQL over HTTP et la documentation de chaque outil, le tout vérifié le 9 octobre 2026.
| Outil | Nommer chaque opération avec | Faire échouer une réponse qui contient errors | Alimenter les variables depuis |
|---|---|---|---|
| k6 | tags: { name: "..." } | Un check() sur le corps analysé | SharedArray |
| JMeter | Le nom du sampler | Une JSON Assertion sur $.errors, inversée | CSV Data Set Config |
| Gatling | http("...") ou le plugin GraphQL | jsonPath("$.errors").notExists() | Les feeders |
| Artillery | Un name sur la requête | notHasProperty: "errors" | config.payload |
Pourquoi une API GraphQL demande-t-elle un test de charge différent ?
Parce que toutes les opérations passent par une seule URL et que le travail derrière dépend du document GraphQL et de ses variables. Un rapport groupé par URL met dans le même panier une recherche coûteuse et une simple lecture.
Le guide Serving over HTTP de graphql.org indique qu’un serveur GraphQL fonctionne sur un seul endpoint, en général /graphql. Les opérations y arrivent en POST, avec un corps JSON qui contient query, un operationName facultatif et variables.
Comment obtenir des résultats par opération ?
Nommez chaque requête d’après son opération GraphQL et faites regrouper les statistiques par ce nom. Chaque outil a son propre réglage pour cela (vérifié le 9 octobre 2026).
- k6 : le guide sur les tags et les groupes conseille de définir
tags.namepour donner un nom parlant à une requête, et les thresholds peuvent cibler un seul tag, par exemplehttp_req_duration{name:GetProduct}. - JMeter : l’Aggregate Report crée une ligne par requête portant un nom différent. Depuis JMeter 5.4, le sampler GraphQL HTTP Request a des champs Query, Variables et Operation Name.
- Gatling : le nom de la requête sert de clé à ses statistiques, et le plugin GraphQL nomme les requêtes d’après l’opération, par exemple
query GetUser. - Artillery : le plugin metrics-by-endpoint rapporte les résultats par URL et ajoute à chaque entrée le
namede la requête, si bien que les opérations nommées apparaissent séparément ;useOnlyRequestNames: trueretire l’URL et ne garde que le nom.
Quelles opérations le test doit-il envoyer ?
Celles que reçoit la production, dans les mêmes proportions. Relevez le nombre de chaque opération dans vos logs serveur ou votre monitoring GraphQL et transformez-le en un mélange pondéré de lectures et de mutations.
Utilisez les documents que vos clients embarquent, avec leur vraie profondeur, plutôt qu’un seul document simplifié. Alimentez les variables depuis un fichier, pour que le serveur cherche beaucoup d’enregistrements différents au lieu de renvoyer un seul résultat en cache. Gardez aussi un nombre réaliste d’opérations distinctes : la note technique d’Apollo sur le test de charge GraphQL, un document du fournisseur, explique que chaque nouvelle opération est analysée et validée avant d’être exécutée, et déconseille de générer bien plus d’opérations uniques que n’en envoient les vrais clients.
Les mutations modifient les données : lancez-les là où vous pouvez les réinitialiser. Le guide pas à pas du test de charge couvre les objectifs et la montée en charge.
Pourquoi une réponse HTTP 200 peut-elle quand même être un échec ?
Parce que GraphQL signale les erreurs dans le corps de la réponse. Une requête peut renvoyer le statut 200 avec un tableau errors et des données partielles, et un contrôle du statut la laisse passer.
La page Response de graphql.org explique que, quand un resolver échoue, l’exécution continue et la réponse contient data à côté de errors. Son guide HTTP ajoute qu’une telle réponse doit tout de même recevoir un statut 2xx, faute de code HTTP pour un succès partiel, et que certains anciens serveurs répondent 2xx même pour des erreurs de validation. Le brouillon GraphQL over HTTP propose un statut spécifique 294 pour le succès partiel avec le type de média plus récent application/graphql-response+json.
Dans un test de charge, c’est doublement trompeur. Les erreurs ne sont pas comptées, et les requêtes qui échouent vite, comme des opérations refusées par une limite de profondeur, tirent les temps de réponse vers le bas.
Comment vérifier le tableau errors dans chaque outil ?
Analysez le corps JSON et faites échouer la requête quand errors est présent. Chaque outil a un moyen intégré de le faire.
Dans k6, un check sur le corps analysé s’en charge, et un threshold sur la métrique checks fait échouer l’exécution, car les checks seuls ne changent pas le code de sortie de k6 :
import http from "k6/http";
import { check, sleep } from "k6";
import { SharedArray } from "k6/data";
const URL = "https://staging.example.com/graphql";
const ids = new SharedArray("ids", () =>
JSON.parse(open("./product-ids.json")));
const QUERY = `query GetProduct($id: ID!) {
product(id: $id) { id name price }
}`;
export const options = {
vus: 20,
duration: "5m",
thresholds: {
checks: ["rate>0.99"],
"http_req_duration{name:GetProduct}": ["p(95)<400"],
},
};
function parse(res) {
try { return res.json(); } catch (e) { return null; }
}
export default function () {
const id = ids[Math.floor(Math.random() * ids.length)];
const payload = JSON.stringify({
query: QUERY,
operationName: "GetProduct",
variables: { id: id },
});
const res = http.post(URL, payload, {
headers: { "Content-Type": "application/json" },
tags: { name: "GetProduct" },
});
const body = parse(res);
check(res, {
"status is 2xx": (r) => r.status >= 200 && r.status < 300,
"no GraphQL errors": () => body !== null && !body.errors,
});
sleep(1);
}
SharedArray charge les identifiants une seule fois pour tous les utilisateurs virtuels. Le try évite qu’un corps qui n’est pas du JSON, comme la page d’erreur d’un proxy, interrompe l’itération.
Dans JMeter, ajoutez une JSON Assertion avec le chemin $.errors et cochez “Invert assertion”, pour que l’échantillon échoue quand le chemin existe. Un JSON Extractor copie des identifiants dans des variables pour l’opération suivante.
Dans Gatling, ajoutez .check(jsonPath("$.errors").notExists()), deux éléments tirés de sa référence des checks. Le plugin GraphQL vérifie le statut et les erreurs par défaut, mais sa page d’installation limite les exécutions avec la Community Edition à 5 utilisateurs et 5 minutes (vérifié le 9 octobre 2026). Pour des tests plus gros sans Gatling Enterprise, utilisez des requêtes HTTP classiques avec ce check.
Dans Artillery, le plugin expect propose notHasProperty: "errors", et reportFailuresAsErrors: true fait apparaître les attentes non remplies dans le rapport. Un capture avec un chemin json transmet une valeur à la requête suivante.
Que surveiller en plus des temps de réponse ?
Le cache, les requêtes persistées et la charge de la base de données par requête HTTP. Chacun peut rendre un test plus rapide ou plus lent que la production pour des raisons sans rapport avec la capacité.
Le guide de performance de graphql.org note que les opérations de type query envoyées en GET peuvent profiter du cache HTTP et des CDN, et que les requêtes persistées permettent à un client d’envoyer un hash au lieu du document complet. Si vos clients le font, le test doit le faire aussi. S’ils ne le font pas, un test qui répète la même query avec les mêmes variables mesure un cache que le trafic réel ne sollicite pas. La note technique d’Apollo ajoute qu’Apollo Server, Gateway et le GraphOS Router activent par défaut les requêtes persistées automatiques.
Le même guide décrit le problème N+1 : une liste dont le resolver charge chaque élément séparément transforme une requête en de nombreuses requêtes à la base, ce que l’on corrige en général en les regroupant avec un outil comme DataLoader. Suivez les requêtes à la base par seconde à côté des requêtes HTTP par seconde ; si elles grimpent beaucoup plus vite quand la charge monte, soupçonnez un resolver. Les limites de profondeur, de complexité et de débit peuvent aussi se déclencher pendant un test.
D’où doit venir la charge ?
D’une seule machine tant que vous écrivez le script et faites un smoke test, puis du cloud quand il vous faut plus de charge ou du trafic venant de plusieurs régions.
LoadFocus exécute des scripts k6 depuis 25 régions cloud. Le nombre d’utilisateurs et la durée y viennent des paramètres du test et non des options du script, et les scénarios à taux d’arrivée tournent avec un nombre fixe d’utilisateurs au lieu d’une cadence de requêtes fixe, la différence expliquée dans l’article sur le modèle de charge ouvert ou fermé. LoadFocus publie aussi un modèle pour les endpoints GraphQL avec des requêtes complexes (en anglais).
FAQ
Un test de charge GraphQL doit-il envoyer les queries en GET ou en POST ? Comme le font vos clients. graphql.org n’autorise GET que pour les opérations de type query, et les mutations doivent passer par POST. Si la production envoie des requêtes persistées en GET via un CDN, testez aussi ce chemin.
Le même script peut-il tester la charge des subscriptions GraphQL ? En général non. graphql.org rappelle que les subscriptions durent longtemps et passent souvent par des WebSockets ou des server-sent events : elles demandent leur propre scénario, dans un outil qui gère ce transport.
Une réponse partielle doit-elle compter comme une requête en échec ? Dans la plupart des tests, oui, car le client a reçu une erreur pour une partie de sa demande. Si certaines erreurs sont attendues, comme des résultats introuvables pour des enregistrements supprimés, ciblez ces codes d’erreur et faites échouer le reste.
À lire aussi
- Comment faire un test de charge : le guide pas à pas
- Test de charge dans GitHub Actions : guide pratique
- Outils de test de charge WebSocket : six options
- Test de charge gRPC : outils, mise en place et mesures
Les affirmations techniques de cet article n'ont pas été relues de façon indépendante.