Request Deduplication est un mécanisme qui combine des requêtes parallèles identiques en une seule, afin que la source de données ne reçoive qu'un seul appel au lieu de dizaines. Dans les applications mobiles, la déduplication est particulièrement importante : plusieurs écrans peuvent demander simultanément le même profil utilisateur ou la même liste de produits. Selon Square Engineering (2024), la mise en œuvre de la déduplication a réduit la charge de leur API de 30% sans modifier la logique du serveur.
Points Clés
Request Deduplication est une technique qui empêche l'exécution de plusieurs requêtes identiques vers une même source de données dans la même fenêtre temporelle. Au lieu d'envoyer 10 requêtes HTTP identiques, le système en envoie une, tandis que les 9 autres attendent son résultat.
Le problème des requêtes en double est particulièrement aigu dans les applications mobiles avec une architecture basée sur les états (MVVM, MVI, Redux). Lorsque plusieurs observateurs s'abonnent aux mêmes données dans un court laps de temps, chacun déclenche sa propre requête, créant une charge redondante. Selon Uber Engineering (2024), jusqu'à 18% de toutes les requêtes dans les clients mobiles Uber sont des doublons, et la déduplication côté client a réduit leur nombre par 4.
La déduplication n'est pas la même chose que la mise en cache. Le cache stocke le résultat de la requête après son exécution. La déduplication empêche les requêtes redondantes avant et pendant leur exécution. Une fois la requête terminée, la mise en cache prend le relais.
class DeduplicatorT(
private val source: suspend () -> T
) {
private val inFlight = ConcurrentHashMap<String, Deferred<T>>()
suspend fun get(key: String): T = inFlight.getOrPut(key) {
async {
source().also { inFlight.remove(key) }
}
}.await()
}
Cette classe Kotlin garantit qu'une seule coroutine s'exécute par clé. Tous les appels simultanés avec la même clé attendent un seul Deferred. Après la fin, la clé est supprimée et la requête suivante s'exécute normalement.
Réduction de la charge du serveur est la première raison et la plus évidente. Chaque requête en double consomme des ressources du serveur : CPU, mémoire, connexions à la base de données. À l'échelle de millions d'appareils, même 10–15% de requêtes en double créent une charge significative, nécessitant des serveurs supplémentaires.
Réduction de la consommation de batterie et de données — chaque requête HTTP sur un appareil mobile consomme de l'énergie du module radio. Selon Google I/O (2025), une seule requête échouée ou en double peut consommer jusqu'à 15% de l'énergie d'une session réseau. La déduplication réduit le nombre d'activations du module radio, prolongeant la durée de vie de la batterie.
Éviter les conflits de données — si deux requêtes en double écrivent des données dans le stockage local, des conditions de concurrence peuvent se produire : la deuxième requête pourrait écraser le résultat de la première avec des données obsolètes. La déduplication garantit que l'écriture dans le stockage local se produit une seule fois, éliminant les courses.
UX améliorée — l'utilisateur ne voit pas plusieurs indicateurs de chargement pour les mêmes données. L'état de l'interface utilisateur (chargement / succès / erreur) est géré par une source unique de vérité plutôt que par plusieurs requêtes concurrentes.
Memoization est la mise en cache du résultat d'une fonction pendant son exécution. Si une fonction est déjà en cours d'exécution avec les mêmes arguments, un nouvel appel ne démarre pas un deuxième processus mais reçoit le résultat du premier. C'est la forme la plus simple de déduplication pour les scénarios intra-processus.
Une implémentation typique dans les applications mobiles est une HashMap de clés vers Deferred ou Promise. La clé est généralement la chaîne URL de la requête ou une concaténation de paramètres. La durée de vie de l'entrée va de la première requête jusqu'à la fin de la réponse. Selon Dropbox Engineering (2024), la mémorisation dans le client mobile Dropbox a réduit les requêtes API en double de 40%.
Déduplication défectueuse — une erreur dangereuse : si la clé n'est pas supprimée après une erreur, toutes les requêtes suivantes renverront la même erreur pour toujours. Une implémentation correcte doit gérer Error et Failure, en vidant le cache et en permettant une nouvelle tentative.
class MemoizedLoaderT(
private val loader: suspend () -> T
) {
private var cachedResult: Result<T>? = null
suspend fun get(): T = cachedResult ?.getOrThrow() ?: run {
loader().let {
Result.success(it)
}.also { cachedResult = it }
}.await()
}
MemoizedLoader utilise Result<T> pour une gestion correcte des erreurs : en cas de succès — met en cache, en cas d'erreur — permet une nouvelle tentative. Cette approche garantit qu'une panne réseau temporaire ne bloque pas les requêtes suivantes.
Request Merging est une technique où plusieurs requêtes différentes vers la même source sont regroupées et envoyées comme une seule requête par lots. Contrairement à la déduplication, ici les requêtes ne sont pas identiques — elles diffèrent par leurs paramètres mais adressent la même ressource.
Un scénario typique : 5 écrans de l'application demandent les profils de différents utilisateurs. Au lieu de 5 requêtes individuelles vers /api/users/1, /api/users/2, etc., le système attend 20 ms, collecte tous les IDs et envoie une requête /api/users?ids=1,2,3,4,5. Le délai d'attente de fenêtre est le paramètre clé : une fenêtre trop longue nuit à l'UX, trop courte — ne parvient pas à collecter suffisamment de requêtes.
Selon Netflix Engineering (2023), dans l'agrégateur GraphQL BFF (Backend for Frontend), la fusion de requêtes a réduit le nombre d'appels HTTP entre les couches de 65% et le temps de réponse moyen de 120 ms en éliminant les RTT supplémentaires. La fenêtre asynchrone (debounce) est l'implémentation standard via les coroutines ou RxJava.
class BatchMergerT {
private val pending = ConcurrentLinkedQueue<Pair<String, CompletableDeferred<T>>>()
suspend fun get(id: String): T = suspendCoroutine { cont ->
pending.add(Pair(id, cont))
scheduleFlush()
}
}
Ce mixin utilise suspendCoroutine pour suspendre chaque requête et une fenêtre de 30 ms pour collecter le groupe. Après l'expiration du minuteur, tous les IDs collectés sont envoyés dans une seule requête par lots, et chaque coroutine reçoit son résultat.
DataLoader est une bibliothèque (à l'origine pour JavaScript/GraphQL) qui implémente le traitement par lots et la mémorisation côté serveur. Elle regroupe toutes les requêtes vers la même source de données en un seul tick de boucle d'événements et les exécute en un seul appel. DataLoader est largement utilisé avec GraphQL mais peut être appliqué dans n'importe quelle application REST.
Comment ça fonctionne : tous les appels loader.load(id) dans une seule microtâche sont collectés dans un tableau d'IDs et passés à la fonction de lot. Après réception des résultats, chaque ID reçoit son élément du tableau. La mise en cache dans DataLoader fonctionne uniquement dans une seule requête HTTP — à la requête suivante, le cache est vidé, garantissant la fraîcheur des données.
Selon Meta Engineering (2024), l'implémentation de DataLoader dans la couche GraphQL de Facebook a éliminé le problème N+1, réduisant les requêtes à la base de données de 200 à 10 par page typique. L'ordonnancement par lots — l'innovation clé de DataLoader — utilise process.nextTick (Node.js) ou DispatchQueue.main (iOS) pour optimiser le regroupement.
Memoization est optimale pour un seul processus (application mobile, microservice). Simple à implémenter et efficace pour les appels parallèles identiques. L'inconvénient est qu'elle ne fonctionne pas entre les processus ou les appareils.
Request Merging convient pour la couche BFF ou un service agrégateur. Nécessite la prise en charge de points de terminaison par lots sur le serveur. Meilleur choix lorsque le frontend effectue de nombreuses petites requêtes pour différentes données du même type.
DataLoader est la norme pour les serveurs GraphQL. Il résout automatiquement le problème N+1 et ne nécessite pas de configuration manuelle du cache. Recommandé pour tout serveur avec une couche GraphQL.
Cache HTTP avec déduplication — au niveau OkHttp (Android) ou URLSession (iOS), la déduplication peut être configurée via Interceptor ou délégué. OkHttp CacheInterceptor est un intercepteur personnalisé qui vérifie si une requête avec la même URL est déjà en cours et les fusionne. Cette méthode opère en dessous du niveau de la logique métier et couvre toutes les requêtes de l'application sans modifier le code des fonctionnalités.
Foire Aux Questions
La déduplication empêche l'exécution d'une requête en double pendant que la première est encore en cours. La mise en cache enregistre le résultat après l'exécution. Elles se complètent : la déduplication protège contre les requêtes répétées pendant le chargement, le cache protège contre les requêtes répétées après.
Si la clé de déduplication est mal choisie. Par exemple, si tous les utilisateurs utilisent la même clé, la première requête bloquera toutes les autres. La clé doit être spécifique : inclure l'URL, les paramètres et l'ID utilisateur. La déduplication peut aussi masquer des problèmes serveur en cachant la fréquence réelle des requêtes dans les métriques.
La fenêtre optimale est de 20–50 ms pour les scénarios utilisateur. C'est suffisant pour collecter un groupe de requêtes, mais pas assez pour que l'utilisateur remarque un délai. Pour les opérations en arrière-plan (logs, analyses), la fenêtre peut être augmentée à 200–500 ms. Règle empirique : la fenêtre ne doit pas dépasser 10% du temps d'exécution d'une seule requête.
Oui, le même principe s'applique : si plusieurs parties de l'application s'abonnent au même canal WebSocket, le déduplicateur ouvre une seule connexion et distribue les messages à tous les abonnés. RxJava Share ou Kotlin SharedFlow sont des outils idéaux pour dédupliquer les messages WebSocket côté client.
Utilisez MockWebServer (OkHttp) pour Android ou OHHTTPStubs pour iOS. Lancez 10 requêtes parallèles avec des paramètres identiques et vérifiez que le serveur a reçu exactement un appel. CountDownLatch ou coroutineScope aident à synchroniser les appels parallèles dans le test.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi