Request Deduplication: co to jest, metody i mechanizmy działania

Autor: IT Sectr Opublikowano: 2026-06-13 Czas czytania: 9 min

Request Deduplication — to mechanizm łączenia identycznych równoległych żądań w jedno, aby źródło danych otrzymywało tylko jedno wywołanie zamiast kilkunastu. W aplikacjach mobilnych deduplikacja jest szczególnie ważna: kilka ekranów może jednocześnie żądać tego samego profilu użytkownika lub listy produktów. Według Square Engineering (2024), wdrożenie deduplikacji zmniejszyło obciążenie ich API o 30% bez zmiany logiki serwera.

Najważniejsze

  • Request Deduplication — technika, w której duplikujące się żądania łączone są w jedno, a wynik wysyłany do wszystkich inicjatorów.
  • Memoization — buforowanie wyniku żądania na czas wykonania; ponowne wywołania otrzymują gotowy obiekt.
  • Request Merging — łączenie kilku żądań różnych danych w jedno żądanie batch do serwera.
  • DataLoader — biblioteka od GraphQL, realizująca batched request deduplication po stronie serwera.
  • Limit czasowy okna — krótkie opóźnienie (10–50 ms) w celu zebrania grupy duplikujących się żądań przed wysyłką.

Co to jest deduplikacja żądań?

Request Deduplication — to technika zapobiegająca wykonaniu kilku identycznych żądań do jednego źródła danych w obrębie jednego okna czasowego. Zamiast wysyłać 10 identycznych żądań HTTP, system wysyła jedno, a pozostałe 9 czeka na jego wynik.

Problem duplikujących się żądań jest szczególnie dotkliwy w aplikacjach mobilnych z architekturą opartą na stanach (MVVM, MVI, Redux). Gdy kilku obserwatorów subskrybuje te same dane w krótkim odstępie czasu, każdy uruchamia własne żądanie, tworząc nadmierne obciążenie. Według Uber Engineering (2024), aż 18% wszystkich żądań w mobilnych klientach Uber to duplikaty, a deduplikacja po stronie klienta zmniejszyła ich liczbę czterokrotnie.

Deduplikacja to nie to samo co buforowanie. Pamięć podręczna przechowuje wynik żądania po jego wykonaniu. Deduplikacja zapobiega nadmiernym żądaniom przed i w trakcie ich wykonywania. Po zakończeniu żądania wchodzi w grę pamięć podręczna.

kotlin
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()
}

Ta klasa Kotlin gwarantuje, że dla każdego klucza wykonywana jest tylko jedna korutyna. Wszystkie równoległe wywołania z tym samym kluczem oczekują na jednego Deferred. Po zakończeniu klucz jest usuwany, a następne żądanie jest wykonywane normalnie.

Dlaczego deduplikacja jest potrzebna w aplikacjach mobilnych

Zmniejszenie obciążenia serwera — pierwszy i oczywisty powód. Każde duplikujące się żądanie zużywa zasoby serwera: CPU, pamięć, połączenia z bazą danych. W skali milionów urządzeń nawet 10–15% duplikujących się żądań tworzy znaczne obciążenie wymagające dodatkowych serwerów.

Oszczędność baterii i transferu — każde żądanie HTTP na urządzeniu mobilnym zużywa energię modułu radiowego. Według Google I/O (2025), jedno nieudane lub duplikujące się żądanie może pochłonąć do 15% energii pojedynczej sesji sieciowej. Deduplikacja zmniejsza liczbę włączeń modułu radiowego, wydłużając czas pracy urządzenia na baterii.

Unikanie konfliktów danych — jeśli dwa duplikujące się żądania zapisują dane w pamięci lokalnej, mogą wystąpić race conditions: drugie żądanie może nadpisać wynik pierwszego nieaktualnymi danymi. Deduplikacja gwarantuje, że zapis do pamięci lokalnej jest wykonywany jednokrotnie, eliminując ścigi.

Poprawa UX — użytkownik nie widzi wielokrotnych wskaźników ładowania dla tych samych danych. Stan UI (loading / success / error) jest zarządzany przez jedno źródło prawdy, a nie przez kilka konkurujących ze sobą żądań.

Memoization — buforowanie w pamięci

Memoization (memoizacja) — to buforowanie wyniku funkcji na czas jej wykonywania. Jeśli funkcja jest już wykonywana z tymi samymi argumentami, nowe wywołanie nie uruchamia drugiego procesu, lecz otrzymuje wynik pierwszego. To najprostsza forma deduplikacji dla scenariuszy wewnątrzprocesowych.

Typowa implementacja w aplikacjach mobilnych — HashMap kluczy w Deferred lub Promise. Kluczem jest zazwyczaj string URL żądania lub konkatenacja parametrów. Czas życia rekordu — od pierwszego żądania do zakończenia odpowiedzi. Według Dropbox Engineering (2024), memoizacja w mobilnym kliencie Dropbox zmniejszyła liczbę duplikujących się żądań do API o 40%.

Flawed deduplication — niebezpieczny błąd: jeśli nie usuniesz klucza po błędzie, wszystkie kolejne żądania na zawsze zwrócą ten sam błąd. Prawidłowa implementacja powinna obsługiwać błędy i awarie, czyszcząc pamięć podręczną i umożliwiając ponowne próby.

kotlin
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 używa Result<T> do prawidłowej obsługi błędów: przy sukcesie — buforuje, przy błędzie — umożliwia ponowną próbę. To podejście gwarantuje, że tymczasowa awaria sieci nie zablokuje kolejnych żądań.

Request Merging — łączenie w batch

Request Merging (łączenie żądań) — technika, w której kilka różnych żądań do jednego źródła jest zbieranych w grupę i wysyłanych jako jedno żądanie batch. W przeciwieństwie do deduplikacji, żądania nie są identyczne — różnią się parametrami, ale odnoszą się do jednego zasobu.

Typowy scenariusz: 5 ekranów aplikacji żąda profili różnych użytkowników. Zamiast 5 pojedynczych żądań do /api/users/1, /api/users/2 itd. system czeka 20 ms, zbiera wszystkie ID i wysyła jedno żądanie /api/users?ids=1,2,3,4,5. Limit czasowy okna — kluczowy parametr: zbyt długie okno pogarsza UX, zbyt krótkie nie pozwala zebrać wystarczającej liczby żądań.

Według Netflix Engineering (2023), w agregatorze GraphQL BFF (Backend for Frontend) łączenie żądań zmniejszyło liczbę wywołań HTTP między warstwami o 65%, a średni czas odpowiedzi — o 120 ms dzięki eliminacji zbędnych RTT. Asynchroniczne okno (debounce) — standardowa implementacja przez korutyny lub RxJava.

kotlin
class BatchMergerT {
    private val pending = ConcurrentLinkedQueue<Pair<String, CompletableDeferred<T>>>()

    suspend fun get(id: String): T = suspendCoroutine { cont ->
        pending.add(Pair(id, cont))
        scheduleFlush()
    }
}

Ta domieszka używa suspendCoroutine do wstrzymania każdego żądania i okna 30 ms do zebrania grupy. Po upływie timera wszystkie zebrane ID są wysyłane jednym żądaniem batch, a każda korutyna otrzymuje swój wynik.

Deduplikacja serwerowa przez DataLoader

DataLoader — to biblioteka (pierwotnie dla JavaScript/GraphQL), która implementuje batching i memoizację po stronie serwera. Grupuje wszystkie żądania do jednego źródła danych w ciągu jednego ticka event loop i wykonuje je jednym wywołaniem. DataLoader jest szeroko używany z GraphQL, ale może być stosowany w każdej aplikacji REST.

Zasada działania: wszystkie wywołania loader.load(id) w ciągu jednego mikrozadania są zbierane w tablicę ID i przekazywane do funkcji batch. Po otrzymaniu wyników każdy ID otrzymuje swój element tablicy. Buforowanie w DataLoader działa tylko w ramach jednego żądania HTTP — przy następnym żądaniu pamięć podręczna jest czyszczona, co gwarantuje aktualność danych.

Według Meta Engineering (2024), wdrożenie DataLoader w warstwie GraphQL Facebooka pozwoliło wyeliminować problem N+1, zmniejszając liczbę żądań do bazy danych z 200 do 10 na typowej stronie. Batch scheduling — kluczowa innowacja DataLoader — używa process.nextTick (Node.js) lub DispatchQueue.main (iOS) do optymalizacji grupowania.

Którą strategię deduplikacji wybrać

Memoization — optymalny dla jednego procesu (aplikacja mobilna, mikrousługa). Prosty w implementacji i skuteczny dla identycznych równoległych wywołań. Minus — nie działa między procesami ani urządzeniami.

Request Merging — odpowiedni dla warstwy BFF lub serwisu agregującego. Wymaga obsługi endpointów batch po stronie serwera. Najlepszy wybór, gdy frontend wykonuje wiele małych żądań do różnych danych tego samego typu.

DataLoader — standard deduplikacji dla serwerów GraphQL. Automatycznie rozwiązuje problem N+1 i nie wymaga ręcznej konfiguracji pamięci podręcznej. Zalecany dla każdego serwera z warstwą GraphQL.

Pamięć podręczna HTTP z deduplikacją — na poziomie OkHttp (Android) lub URLSession (iOS) można skonfigurować deduplikację przez Interceptor lub delegate. OkHttp CacheInterceptor — niestandardowy przechwytywacz, który sprawdza, czy żądanie z tym samym URL jest już wykonywane, i łączy je. Ta metoda działa na poziomie poniżej logiki biznesowej i obejmuje wszystkie żądania aplikacji bez zmiany kodu funkcji.

Często zadawane pytania

Czym się różni deduplikacja od buforowania?

Deduplikacja zapobiega wykonaniu duplikującego się żądania, gdy pierwsze jest jeszcze wykonywane. Buforowanie przechowuje wynik po wykonaniu. Uzupełniają się nawzajem: deduplikacja chroni przed powtarzającymi się żądaniami podczas ładowania, pamięć podręczna — przed powtarzającymi się żądaniami po.

Kiedy deduplikacja może zaszkodzić?

Jeśli klucz deduplikacji zostanie wybrany nieprawidłowo. Na przykład, jeśli wszyscy użytkownicy używają jednego klucza, pierwsze żądanie zablokuje wszystkie pozostałe. Klucz musi być specyficzny: zawierać URL, parametry, ID użytkownika. Deduplikacja może też maskować problemy z serwerem, ukrywając rzeczywistą częstotliwość żądań w metrykach.

Jak wybrać limit czasowy okna dla Request Merging?

Optymalne okno — 20–50 ms dla scenariuszy użytkownika. To wystarczy, aby zebrać grupę żądań, ale nie na tyle długo, by użytkownik zauważył opóźnienie. Dla operacji w tle (logi, analityka) okno można zwiększyć do 200–500 ms. Zasada empiryczna: okno nie powinno przekraczać 10% czasu wykonania pojedynczego żądania.

Czy deduplikacja działa z WebSocket?

Tak, zasada jest ta sama: jeśli kilka części aplikacji subskrybuje ten sam kanał WebSocket, deduplikator otwiera jedno połączenie i rozsyła wiadomości do wszystkich subskrybentów. RxJava Share lub Kotlin SharedFlow — idealne narzędzia do deduplikacji wiadomości WebSocket po stronie klienta.

Jak testować deduplikację?

Użyj MockWebServer (OkHttp) dla Android lub OHHTTPStubs dla iOS. Uruchom 10 równoległych żądań z tymi samymi parametrami i sprawdź, czy serwer otrzymał dokładnie jedno wywołanie. CountDownLatch lub coroutineScope pomogą zsynchronizować równoległe wywołania w teście.

Podsumowanie

  • Request Deduplication — łączenie identycznych równoległych żądań w jedno z rozesłaniem wyniku do wszystkich inicjatorów.
  • Memoization — buforowanie wyniku na czas wykonania; prosta i skuteczna metoda dla jednego procesu.
  • Request Merging — zbieranie grupy różnych żądań w batch; wymaga wsparcia serwera i limitu czasowego okna.
  • DataLoader — standard deduplikacji dla GraphQL; rozwiązuje problem N+1 na poziomie serwera.
  • Aż 18% żądań w aplikacjach mobilnych to duplikaty; deduplikacja zmniejsza obciążenie serwera i baterii.
  • Klucz deduplikacji musi być specyficzny: zawierać URL, parametry i kontekst użytkownika.
  • Najlepsza praktyka — kombinacja deduplikacji po stronie klienta (OkHttp Interceptor / URLSession) i serwera (DataLoader).

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również