Request Deduplication — är en mekanism för att slå samman identiska parallella förfrågningar till en, så att datakällan endast får ett anrop istället för dussintals. I mobilapplikationer är deduplicering särskilt viktig: flera skärmar kan samtidigt begära samma användarprofil eller produktlista. Enligt Square Engineering (2024) minskade införandet av deduplicering belastningen på deras API med 30% utan att ändra serverlogiken.
Huvudsakliga
Request Deduplication — är en teknik som förhindrar att flera identiska förfrågningar till samma datakälla utförs inom ett tidsfönster. Istället för att skicka 10 identiska HTTP-förfrågningar skickar systemet en och de övriga 9 väntar på dess resultat.
Problemet med dubblettförfrågningar är särskilt akut i mobilappar med tillståndsbaserad arkitektur (MVVM, MVI, Redux). När flera observatörer prenumererar på samma data inom en kort tidsperiod startar var och en sin egen förfrågan, vilket skapar överbelastning. Enligt Uber Engineering (2024) är upp till 18% av alla förfrågningar i Ubers mobilklienter dubbletter, och deduplicering på klienten minskade deras antal med 4 gånger.
Deduplicering är inte samma sak som cachning. Cachen lagrar resultatet av förfrågan efter dess utförande. Deduplicering förhindrar överflödiga förfrågningar före och under deras utförande. Efter att förfrågan slutförts träder cachen i kraft och lagrar resultatet.
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()
}
Denna Kotlin-klass garanterar att endast en korutin körs för varje nyckel. Alla parallella anrop med samma nyckel väntar på en Deferred. Efter slutförande tas nyckeln bort och nästa förfrågan körs normalt.
Minskning av serverbelastning — den första och mest uppenbara anledningen. Varje dubblettförfrågan förbrukar serverresurser: CPU, minne, databasanslutningar. I miljonskala skapar även 10–15% dubblettförfrågningar betydande belastning som kräver ytterligare servrar.
Besparing av batteri och trafik — varje HTTP-förfrågan på en mobil enhet förbrukar energi från radiomodulen. Enligt Google I/O (2025) kan en misslyckad eller dubblettförfrågan förbruka upp till 15% av energin i en nätverkssession. Deduplicering minskar antalet gånger radiomodulen aktiveras, vilket förlänger enhetens batteritid.
Undvikande av datakonflikter — om två dubblettförfrågningar skriver data till lokal lagring kan race conditions uppstå: den andra förfrågan kan skriva över resultatet av den första med inaktuella data. Deduplicering garanterar att skrivning till lokal lagring sker en gång, vilket eliminerar tävlingsförhållanden.
Förbättring av UX — användaren ser inte flera laddningsindikatorer för samma data. UI-status (loading / success / error) hanteras av en enda sanningskälla, inte av flera konkurrerande förfrågningar.
Memoization (memorering) — är cachning av en funktions resultat under dess körning. Om funktionen redan körs med samma argument startar ett nytt anrop inte en andra process utan får resultatet av den första. Detta är den enklaste formen av deduplicering för processinterna scenarier.
Typisk implementering i mobilappar — HashMap av nycklar i Deferred eller Promise. Nyckeln är vanligtvis förfrågans URL-sträng eller en sammanslagning av parametrar. Postens livslängd — från den första förfrågan till slutförandet av svaret. Enligt Dropbox Engineering (2024) minskade memorering i Dropbox mobilklient antalet dubblettförfrågningar till API med 40%.
Flawed deduplication — ett farligt misstag: om du inte tar bort nyckeln efter ett fel kommer alla efterföljande förfrågningar alltid att returnera samma fel. En korrekt implementering måste hantera fel och misslyckanden, rensa cachen och tillåta omförsök.
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 använder Result<T> för korrekt felhantering: vid framgång cachas det, vid fel tillåts omförsök. Denna metod garanterar att ett tillfälligt nätverksfel inte blockerar efterföljande förfrågningar.
Request Merging (sammanslagning av förfrågningar) — teknik där flera olika förfrågningar till samma källa samlas i en grupp och skickas som en batch-förfrågan. Till skillnad från deduplicering är förfrågningarna inte identiska — de skiljer sig i parametrar men hänvisar till samma resurs.
Typiskt scenario: 5 skärmar i appen begär profiler för olika användare. Istället för 5 enskilda förfrågningar till /api/users/1, /api/users/2 etc., väntar systemet 20 ms, samlar alla ID och skickar en förfrågan /api/users?ids=1,2,3,4,5. Fönstertidsgräns — nyckelparameter: ett för långt fönster försämrar UX, ett för kort fönster tillåter inte insamling av tillräckligt många förfrågningar.
Enligt Netflix Engineering (2023) minskade sammanslagning av förfrågningar i GraphQL BFF (Backend for Frontend) aggregatorn antalet HTTP-anrop mellan lager med 65% och genomsnittlig svarstid med 120 ms genom att eliminera överflödiga RTT. Asynkront fönster (debounce) — standardimplementering via korutiner eller 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()
}
}
Denna mixin använder suspendCoroutine för att pausa varje förfrågan och ett fönster på 30 ms för att samla gruppen. Efter att timern löpt ut skickas alla insamlade ID med en batch-förfrågan och varje korutin får sitt eget resultat.
DataLoader — är ett bibliotek (ursprungligen för JavaScript/GraphQL) som implementerar batching och memorering på serversidan. Det grupperar alla förfrågningar till samma datakälla inom en tick av event loop och kör dem med ett anrop. DataLoader används flitigt med GraphQL men kan tillämpas i vilken REST-applikation som helst.
Funktionsprincip: alla anrop till loader.load(id) inom en mikrouppgift samlas i en array av ID och skickas till batch-funktionen. Efter mottagning av resultaten får varje ID sitt eget arrayelement. Cachning i DataLoader fungerar endast inom en enda HTTP-förfrågan — vid nästa förfrågan återställs cachen, vilket garanterar datans aktualitet.
Enligt Meta Engineering (2024) eliminerade införandet av DataLoader i Facebooks GraphQL-lager N+1-problemet, vilket minskade antalet databasfrågor från 200 till 10 per typisk sida. Batch scheduling — DataLoaders viktigaste innovation — använder process.nextTick (Node.js) eller DispatchQueue.main (iOS) för att optimera grupperingen.
Memoization — optimal för en process (mobilapp, mikrotjänst). Enkel att implementera och effektiv för identiska parallella anrop. Nackdel — fungerar inte mellan processer eller enheter.
Request Merging — lämplig för BFF-lager eller en aggregeringstjänst. Kräver stöd för batch-endpoints på servern. Bästa valet när frontend gör många små förfrågningar till olika data av samma typ.
DataLoader — dedupliceringsstandard för GraphQL-servrar. Löser automatiskt N+1-problemet och kräver ingen manell konfiguration av cache. Rekommenderas för alla servrar med ett GraphQL-lager.
HTTP-cache med deduplicering — på nivån OkHttp (Android) eller URLSession (iOS) kan deduplicering konfigureras via en Interceptor eller delegate. OkHttp CacheInterceptor — en anpassad avlyssnare som kontrollerar om en förfrågan med samma URL redan körs och slår samman dem. Denna metod fungerar på en nivå under affärslogiken och täcker alla applikationens förfrågningar utan att ändra funktionalitetskoden.
Vanliga frågor
Deduplicering förhindrar utförandet av en dubblettförfrågan medan den första fortfarande körs. Cachning lagrar resultatet efter utförande. De kompletterar varandra: deduplicering skyddar mot upprepade förfrågningar under laddning, cache — mot upprepade förfrågningar efteråt.
Om dedupliceringsnyckeln är felaktigt vald. Till exempel, om alla användare använder samma nyckel kommer den första förfrågan att blockera alla andra. Nyckeln måste vara specifik: inkludera URL, parametrar, användar-ID. Deduplicering kan också dölja serverproblem genom att gömma den verkliga frekvensen av förfrågningar i mätvärden.
Optimalt fönster — 20–50 ms för användarscenarier. Detta är tillräckligt för att samla en grupp förfrågningar men inte tillräckligt för att användaren ska märka fördröjningen. För bakgrundsoperationer (loggar, analys) kan fönstret ökas till 200–500 ms. Empirisk regel: fönstret bör inte överstiga 10% av exekveringstiden för en enskild förfrågan.
Ja, principen är densamma: om flera delar av appen prenumererar på samma WebSocket-kanal öppnar deduplicatorn en anslutning och skickar meddelanden till alla prenumeranter. RxJava Share eller Kotlin SharedFlow — idealiska verktyg för deduplicering av WebSocket-meddelanden på klienten.
Använd MockWebServer (OkHttp) för Android eller OHHTTPStubs för iOS. Starta 10 parallella förfrågningar med samma parametrar och kontrollera att servern fick exakt ett anrop. CountDownLatch eller coroutineScope hjälper till att synkronisera parallella anrop i testet.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också