Request Deduplication: o que é, métodos e mecanismos de funcionamento

Autor: IT Sectr Publicado: 2026-06-13 Tempo de leitura: 9 min

Request Deduplication é um mecanismo que combina requisições paralelas idênticas em uma única, para que a fonte de dados receba apenas uma chamada em vez de dezenas. Em aplicativos móveis, a desduplicação é especialmente importante: várias telas podem solicitar simultaneamente o mesmo perfil de usuário ou lista de produtos. De acordo com a Square Engineering (2024), a implementação da desduplicação reduziu a carga da API em 30% sem alterar a lógica do servidor.

Principais Conclusões

  • Request Deduplication — técnica na qual requisições duplicadas são mescladas em uma, e o resultado é enviado a todos os solicitantes.
  • Memoization — armazenamento em cache do resultado da requisição durante a execução; chamadas subsequentes recebem o objeto pronto.
  • Request Merging — combinação de várias requisições de dados diferentes em uma única requisição em lote ao servidor.
  • DataLoader — biblioteca do GraphQL que implementa desduplicação de requisições em lote no servidor.
  • Tempo limite de janela — um pequeno atraso (10–50 ms) para coletar um grupo de requisições duplicadas antes do envio.

O que é desduplicação de requisições?

Request Deduplication é uma técnica que impede a execução de múltiplas requisições idênticas a uma mesma fonte de dados dentro da mesma janela de tempo. Em vez de enviar 10 requisições HTTP idênticas, o sistema envia uma, enquanto as outras 9 aguardam seu resultado.

O problema de requisições duplicadas é especialmente grave em aplicativos móveis com arquitetura baseada em estados (MVVM, MVI, Redux). Quando vários observadores se inscrevem nos mesmos dados em um curto período, cada um aciona sua própria requisição, criando carga redundante. De acordo com a Uber Engineering (2024), até 18% de todas as requisições nos clientes móveis da Uber são duplicadas, e a desduplicação no cliente reduziu esse número em 4 vezes.

Desduplicação não é a mesma coisa que cache. O cache armazena o resultado da requisição após sua execução. A desduplicação evita requisições redundantes antes e durante sua execução. Após a conclusão da requisição, o cache entra em ação.

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

Esta classe Kotlin garante que apenas uma corrotina seja executada por chave. Todas as chamadas concorrentes com a mesma chave aguardam um único Deferred. Após a conclusão, a chave é removida e a próxima requisição é executada normalmente.

Por que a desduplicação é necessária em aplicativos móveis

Redução da carga no servidor é a primeira e mais óbvia razão. Cada requisição duplicada consome recursos do servidor: CPU, memória, conexões com o banco de dados. Na escala de milhões de dispositivos, mesmo 10–15% de requisições duplicadas criam uma carga significativa, exigindo servidores adicionais.

Redução do consumo de bateria e dados — cada requisição HTTP em um dispositivo móvel consome energia do módulo de rádio. De acordo com o Google I/O (2025), uma única requisição falha ou duplicada pode consumir até 15% da energia de uma sessão de rede. A desduplicação reduz o número de ativações do módulo de rádio, prolongando a vida útil da bateria.

Evitar conflitos de dados — se duas requisições duplicadas escrevem dados no armazenamento local, podem ocorrer condições de corrida: a segunda requisição pode sobrescrever o resultado da primeira com dados desatualizados. A desduplicação garante que a escrita no armazenamento local ocorra apenas uma vez, eliminando corridas.

UX melhorada — o usuário não vê múltiplos indicadores de carregamento para os mesmos dados. O estado da UI (carregando / sucesso / erro) é gerenciado por uma única fonte de verdade, em vez de múltiplas requisições concorrentes.

Memoization — cache em memória

Memoization é o armazenamento em cache do resultado de uma função durante sua execução. Se uma função já está em execução com os mesmos argumentos, uma nova chamada não inicia um segundo processo, mas recebe o resultado da primeira. Esta é a forma mais simples de desduplicação para cenários dentro de um processo.

Uma implementação típica em aplicativos móveis é um HashMap de chaves para Deferred ou Promise. A chave geralmente é a string URL da requisição ou uma concatenação de parâmetros. O tempo de vida da entrada é desde a primeira requisição até a conclusão da resposta. De acordo com a Dropbox Engineering (2024), a memoização no cliente móvel do Dropbox reduziu as requisições duplicadas à API em 40%.

Desduplicação defeituosa — um erro perigoso: se a chave não for removida após um erro, todas as requisições subsequentes retornarão o mesmo erro para sempre. Uma implementação correta deve tratar Error e Failure, limpando o cache e permitindo nova tentativa.

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 usa Result<T> para tratamento correto de erros: em caso de sucesso — armazena em cache, em caso de erro — permite nova tentativa. Esta abordagem garante que uma falha temporária de rede não bloqueie requisições subsequentes.

Request Merging — combinação em lote

Request Merging é uma técnica na qual várias requisições diferentes para a mesma fonte são agrupadas e enviadas como uma única requisição em lote. Ao contrário da desduplicação, aqui as requisições não são idênticas — diferem em parâmetros mas abordam o mesmo recurso.

Um cenário típico: 5 telas do aplicativo solicitam perfis de usuários diferentes. Em vez de 5 requisições individuais para /api/users/1, /api/users/2, etc., o sistema aguarda 20 ms, coleta todos os IDs e envia uma requisição /api/users?ids=1,2,3,4,5. O tempo limite de janela é o parâmetro chave: uma janela muito longa prejudica a UX, muito curta — não consegue coletar requisições suficientes.

De acordo com a Netflix Engineering (2023), no agregador GraphQL BFF (Backend for Frontend), a combinação de requisições reduziu o número de chamadas HTTP entre camadas em 65% e o tempo médio de resposta em 120 ms ao eliminar RTTs extras. A janela assíncrona (debounce) é a implementação padrão via corrotinas ou 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()
    }
}

Este mixin usa suspendCoroutine para suspender cada requisição e uma janela de 30 ms para coletar o grupo. Após o temporizador expirar, todos os IDs coletados são enviados em uma única requisição em lote, e cada corrotina recebe seu resultado.

Desduplicação no servidor via DataLoader

DataLoader é uma biblioteca (originalmente para JavaScript/GraphQL) que implementa processamento em lote e memoização no lado do servidor. Ela agrupa todas as requisições para a mesma fonte de dados dentro de um único tick do loop de eventos e as executa com uma chamada. DataLoader é amplamente usado com GraphQL, mas pode ser aplicado em qualquer aplicação REST.

Como funciona: todas as chamadas loader.load(id) dentro de uma única microtarefa são coletadas em um array de IDs e passadas para a função de lote. Após receber os resultados, cada ID recebe seu elemento do array. O cache no DataLoader funciona apenas dentro de uma única requisição HTTP — na requisição seguinte, o cache é limpo, garantindo a atualidade dos dados.

De acordo com a Meta Engineering (2024), a implementação do DataLoader na camada GraphQL do Facebook eliminou o problema N+1, reduzindo as consultas ao banco de dados de 200 para 10 por página típica. O agendamento em lote — inovação chave do DataLoader — usa process.nextTick (Node.js) ou DispatchQueue.main (iOS) para otimizar o agrupamento.

Qual estratégia de desduplicação escolher

Memoization é ideal para um único processo (aplicativo móvel, microsserviço). Simples de implementar e eficaz para chamadas paralelas idênticas. A desvantagem é que não funciona entre processos ou dispositivos.

Request Merging é adequada para a camada BFF ou serviço agregador. Requer suporte a endpoints em lote no servidor. Melhor escolha quando o frontend faz muitas requisições pequenas para dados diferentes do mesmo tipo.

DataLoader é o padrão para servidores GraphQL. Resolve automaticamente o problema N+1 e não requer configuração manual de cache. Recomendado para qualquer servidor com uma camada GraphQL.

Cache HTTP com desduplicação — no nível OkHttp (Android) ou URLSession (iOS), a desduplicação pode ser configurada via Interceptor ou delegate. OkHttp CacheInterceptor é um interceptor personalizado que verifica se uma requisição com a mesma URL já está em execução e as mescla. Este método opera abaixo do nível da lógica de negócios e cobre todas as requisições do aplicativo sem alterar o código das funcionalidades.

Perguntas Frequentes

Como a desduplicação difere do cache?

A desduplicação impede a execução de uma requisição duplicada enquanto a primeira ainda está em execução. O cache salva o resultado após a execução. Eles se complementam: a desduplicação protege contra requisições repetidas durante o carregamento, o cache protege contra requisições repetidas depois.

Quando a desduplicação pode ser prejudicial?

Se a chave de desduplicação for escolhida incorretamente. Por exemplo, se todos os usuários usam a mesma chave, a primeira requisição bloqueará todas as outras. A chave deve ser específica: incluir URL, parâmetros e ID do usuário. A desduplicação também pode mascarar problemas do servidor ao ocultar a frequência real de requisições nas métricas.

Como escolher o tempo limite de janela para Request Merging?

A janela ideal é de 20–50 ms para cenários voltados ao usuário. Isso é suficiente para coletar um grupo de requisições, mas não o suficiente para o usuário notar um atraso. Para operações em segundo plano (logs, análises), a janela pode ser aumentada para 200–500 ms. Regra prática: a janela não deve exceder 10% do tempo de execução de uma única requisição.

A desduplicação funciona com WebSocket?

Sim, o mesmo princípio se aplica: se várias partes do aplicativo se inscrevem no mesmo canal WebSocket, o desduplicador abre uma única conexão e distribui as mensagens a todos os assinantes. RxJava Share ou Kotlin SharedFlow são ferramentas ideais para desduplicar mensagens WebSocket no cliente.

Como testar a desduplicação?

Use MockWebServer (OkHttp) para Android ou OHHTTPStubs para iOS. Execute 10 requisições paralelas com parâmetros idênticos e verifique se o servidor recebeu exatamente uma chamada. CountDownLatch ou coroutineScope ajudam a sincronizar chamadas paralelas no teste.

Resumo

  • Request Deduplication — mesclagem de requisições paralelas idênticas em uma com distribuição do resultado a todos os solicitantes.
  • Memoization — armazenamento em cache do resultado durante a execução; um método simples e eficaz para um único processo.
  • Request Merging — coleta de um grupo de diferentes requisições em um lote; requer suporte do servidor e tempo limite de janela.
  • DataLoader — o padrão de desduplicação para GraphQL; resolve o problema N+1 no nível do servidor.
  • Até 18% das requisições em aplicativos móveis são duplicadas; a desduplicação reduz a carga no servidor e na bateria.
  • A chave de desduplicação deve ser específica: incluir URL, parâmetros e contexto do usuário.
  • Melhor prática — combinação de desduplicação no cliente (OkHttp Interceptor / URLSession) e no servidor (DataLoader).

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também