Request Deduplication — je mechanismus slučování identických paralelních požadavků do jednoho, aby zdroj dat dostával jen jedno volání místo desítek. V mobilních aplikacích je deduplikace obzvláště důležitá: několik obrazovek může současně požadovat stejný uživatelský profil nebo seznam produktů. Podle Square Engineering (2024), zavedení deduplikace snížilo zatížení jejich API o 30% bez změny logiky serveru.
Hlavní body
Request Deduplication — je technika, která zabraňuje provádění několika identických požadavků ke stejnému zdroji dat v rámci jednoho časového okna. Místo odeslání 10 identických HTTP požadavků systém odešle jeden a zbylých 9 čeká na jeho výsledek.
Problém duplicitních požadavků je obzvláště akutní v mobilních aplikacích s architekturou založenou na stavech (MVVM, MVI, Redux). Když se několik pozorovatelů přihlásí ke stejným datům v krátkém časovém intervalu, každý spustí svůj vlastní požadavek, čímž vytváří nadměrnou zátěž. Podle Uber Engineering (2024) je až 18% všech požadavků v mobilních klientech Uber duplicitních a deduplikace na klientovi snížila jejich počet 4krát.
Deduplikace není totéž co ukládání do mezipaměti. Mezipaměť ukládá výsledek požadavku po jeho provedení. Deduplikace zabraňuje nadbytečným požadavkům před a během jejich provádění. Po dokončení požadavku vstupuje do hry mezipaměť a ukládá výsledek.
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()
}
Tato třída Kotlin zaručuje, že pro každý klíč je provedena pouze jedna korutina. Všechna paralelní volání se stejným klíčem čekají na jeden Deferred. Po dokončení je klíč smazán a další požadavek se provádí normálně.
Snižení zátěže serveru — první a nejzřejmější důvod. Každý duplicitní požadavek spotřebovává prostředky serveru: CPU, paměť, připojení k databázi. V měřítku milionů zařízení vytváří i 10–15% duplicitních požadavků významnou zátěž vyžadující další servery.
Úspora baterie a přenosu dat — každý HTTP požadavek na mobilním zařízení spotřebovává energii rádiového modulu. Podle Google I/O (2025) může jeden neúspěšný nebo duplicitní požadavek spotřebovat až 15% energie jedné síťové relace. Deduplikace snižuje počet zapnutí rádiového modulu, čímž prodlužuje dobu provozu zařízení na baterii.
Zabránění konfliktům dat — pokud dva duplicitní požadavky zapisují data do lokálního úložiště, mohou nastat race conditions: druhý požadavek může přepsat výsledek prvního zastaralými daty. Deduplikace zaručuje, že zápis do lokálního úložiště proběhne jednou, čímž eliminuje závodní podmínky.
Zlepšení UX — uživatel nevidí několik indikátorů načítání pro stejná data. Stav UI (loading / success / error) je spravován jediným zdrojem pravdy, nikoli několika konkurenčními požadavky.
Memoization (memorizace) — je ukládání výsledku funkce do mezipaměti po dobu jejího provádění. Pokud se funkce již provádí se stejnými argumenty, nové volání nespouští druhý proces, ale obdrží výsledek prvního. Toto je nejjednodušší forma deduplikace pro scénáře v rámci jednoho procesu.
Typická implementace v mobilních aplikacích — HashMap klíčů v Deferred nebo Promise. Klíčem je obvykle URL řetězec požadavku nebo zřetězení parametrů. Životnost záznamu — od prvního požadavku do dokončení odpovědi. Podle Dropbox Engineering (2024) snížila memorizace v mobilním klientovi Dropbox počet duplicitních požadavků na API o 40%.
Flawed deduplication — nebezpečná chyba: pokud po chybě neodstraníte klíč, všechny následující požadavky budou vracet stejnou chybu. Správná implementace musí zpracovávat chyby a selhání, čistit mezipaměť a umožnit opakování pokusu.
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 používá Result<T> pro správné zpracování chyb: při úspěchu ukládá do mezipaměti, při chybě umožňuje opakování. Tento přístup zaručuje, že dočasné selhání sítě nezablokuje následující požadavky.
Request Merging (slučování požadavků) — technika, při které se několik různých požadavků ke stejnému zdroji shromáždí do skupiny a odešle jako jeden batch požadavek. Na rozdíl od deduplikace nejsou požadavky identické — liší se parametry, ale odkazují se na stejný zdroj.
Typický scénář: 5 obrazovek aplikace požaduje profily různých uživatelů. Místo 5 jednotlivých požadavků na /api/users/1, /api/users/2 atd. systém počká 20 ms, shromáždí všechna ID a odešle jeden požadavek /api/users?ids=1,2,3,4,5. Časový limit okna — klíčový parametr: příliš dlouhé okno zhoršuje UX, příliš krátké nedovoluje shromáždit dostatek požadavků.
Podle Netflix Engineering (2023) v GraphQL BFF (Backend for Frontend) agregátoru slučování požadavků snížilo počet HTTP volání mezi vrstvami o 65% a průměrnou dobu odezvy o 120 ms odstraněním nadbytečných RTT. Asynchronní okno (debounce) — standardní implementace přes korutiny nebo 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()
}
}
Tento mixin používá suspendCoroutine k pozastavení každého požadavku a okno 30 ms ke shromáždění skupiny. Po vypršení časovače jsou všechna shromážděná ID odeslána jedním batch požadavkem a každá korutina obdrží svůj vlastní výsledek.
DataLoader — je knihovna (původně pro JavaScript/GraphQL), která implementuje batching a memorizaci na straně serveru. Seskupuje všechny požadavky ke stejnému zdroji dat během jednoho ticku event loop a provádí je jedním voláním. DataLoader je široce používán s GraphQL, ale lze jej použít v jakékoli REST aplikaci.
Princip fungování: všechna volání loader.load(id) během jednoho mikrotasku se shromáždí do pole ID a předají se batch funkci. Po obdržení výsledků každé ID obdrží svůj vlastní prvek pole. Ukládání do mezipaměti v DataLoader funguje pouze v rámci jednoho HTTP požadavku — při dalším požadavku se mezipaměť resetuje, což zaručuje aktuálnost dat.
Podle Meta Engineering (2024) zavedení DataLoader do GraphQL vrstvy Facebooku odstranilo problém N+1, čímž snížilo počet databázových dotazů z 200 na 10 na typickou stránku. Batch scheduling — klíčová inovace DataLoader — používá process.nextTick (Node.js) nebo DispatchQueue.main (iOS) k optimalizaci seskupování.
Memoization — optimální pro jeden proces (mobilní aplikace, mikroslužba). Jednoduchá na implementaci a účinná pro identická paralelní volání. Nevýhoda — nefunguje mezi procesy nebo zařízeními.
Request Merging — vhodné pro BFF vrstvu nebo agregační službu. Vyžaduje podporu batch endpointů na serveru. Nejlepší volba, když frontend dělá mnoho malých požadavků na různá data stejného typu.
DataLoader — standard deduplikace pro servery GraphQL. Automaticky řeší problém N+1 a nevyžaduje ruční konfiguraci mezipaměti. Doporučeno pro jakýkoli server s GraphQL vrstvou.
HTTP mezipaměť s deduplikací — na úrovni OkHttp (Android) nebo URLSession (iOS) lze nakonfigurovat deduplikaci přes Interceptor nebo delegate. OkHttp CacheInterceptor — vlastní zachycovač, který kontroluje, zda se požadavek se stejnou URL již provádí, a spojuje je. Tato metoda pracuje na úrovni pod obchodní logikou a pokrývá všechny požadavky aplikace bez změny kódu funkcí.
Často kladené otázky
Deduplikace zabraňuje provedení duplicitního požadavku, zatímco první se stále provádí. Ukládání do mezipaměti uchovává výsledek po provedení. Vzájemně se doplňují: deduplikace chrání před opakovanými požadavky během načítání, mezipaměť — před opakovanými požadavky po něm.
Pokud je klíč deduplikace zvolen nesprávně. Například, pokud všichni uživatelé používají jeden klíč, první požadavek zablokuje všechny ostatní. Klíč musí být specifický: zahrnovat URL, parametry, ID uživatele. Také deduplikace může maskovat problémy se serverem tím, že skrývá skutečnou frekvenci požadavků v metrikách.
Optimální okno — 20–50 ms pro uživatelské scénáře. To je dostatečné pro shromáždění skupiny požadavků, ale ne dostatečné pro to, aby uživatel zaznamenal zpoždění. Pro operace na pozadí (logy, analýza) lze okno zvýšit na 200–500 ms. Empirické pravidlo: okno by nemělo přesáhnout 10% doby provedení jednoho požadavku.
Ano, princip je stejný: pokud se několik částí aplikace přihlásí ke stejnému WebSocket kanálu, deduplikátor otevře jedno připojení a rozesílá zprávy všem předplatitelům. RxJava Share nebo Kotlin SharedFlow — ideální nástroje pro deduplikaci WebSocket zpráv na klientovi.
Použijte MockWebServer (OkHttp) pro Android nebo OHHTTPStubs pro iOS. Spusťte 10 paralelních požadavků se stejnými parametry a ověřte, že server obdržel přesně jedno volání. CountDownLatch nebo coroutineScope pomohou synchronizovat paralelní volání v testu.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také