Voltar para todos os artigos
O Custo Real do Cache: Por Que Sua Fatura do Redis Dobrou e a Latência Piorou

O Custo Real do Cache: Por Que Sua Fatura do Redis Dobrou e a Latência Piorou

O cache em produção pode duplicar sua fatura do Redis e piorar a latência. Descubra o custo do Redis 8 vs Valkey e como mitigar thundering herds.

Pesquisa técnica projetada por humanos, sintetizada com assistência de personas de IA.
21 min de leitura

TL;DR / Sumário Executivo

O cache em produção pode duplicar sua fatura do Redis e piorar a latência. Descubra o custo do Redis 8 vs Valkey e como mitigar thundering herds.

💡 TL;DR (Too Long; Didn't Read)

Principais conclusões em 90 segundos:

  1. A Falácia do Cache: Adicionar um cache em memória (Redis/Valkey) para resolver a latência do banco de dados frequentemente falha em escala devido a cache stampedes, hot keys e amplificação de escrita.
  2. Cache Stampede (Thundering Herd): Quando uma chave altamente concorrente expira, milhares de requisições caem no banco de dados simultaneamente, sobrecarregando a CPU e a latência. O lock Mutex resolve o stampede, mas introduz filas de espera.
  3. A Solução XFetch: A abordagem ideal usa expiração probabilística antecipada (algoritmo XFetch) para atualizar o cache em segundo plano antes que expire, mantendo as latências estáveis.
  4. Amplificação de Escrita e Churn: TTLs mal calibrados e altas taxas de escrita para leitura causam amplificação de escrita no padrão cache-aside, onde os custos de invalidação e escrita superam os ganhos de leitura.
  5. Hot Keys em Sharding: Clusters distribuídos particionam chaves usando hashing. Isso significa que uma chave viral cai em um único nó, saturando sua CPU enquanto outros permanecem ociosos.
  6. Transição Redis vs. Valkey: Com o Redis 8 adotando licenças restritivas, o Valkey (fork da Linux Foundation) surgiu como padrão open-source. O Amazon ElastiCache para Valkey custa 20% menos para clusters baseados em nós e 33% menos para Serverless, alterando a economia por RPS.

O cache é o reflexo arquitetural mais comum no design de sistemas modernos. Quando a CPU do banco de dados atinge picos ou a latência das consultas degrada, o consenso da engenharia quase sempre é: "coloque um cache na frente". Tratamos armazenamentos em memória como Redis ou Valkey como buffers baratos de baixa latência que podem mascarar esquemas de banco de dados ineficientes ou consultas abaixo do ideal.

No entanto, em volumes elevados—microsserviços escalando além de dez mil requisições por segundo (RPS)—o cache deixa de ser um otimizador passivo. Torna-se um sistema complexo e com estado, governado por suas próprias leis físicas. Sob carga concorrente pesada, antipadrões de cache não mitigados podem causar latências em cascata no banco de dados, saturação de threads na aplicação e duplicação da fatura de infraestrutura.

Além disso, o custo operacional do cache passou por uma mudança dramática. Após o anúncio da Redis Inc. em março de 2024 de mover versões futuras (Redis 7.4+) para licenciamento duplo RSALv2 e SSPLv1 (código disponível, mas restritivo), a comunidade lançou o Valkey sob a Linux Foundation. Os provedores de nuvem responderam com preços agressivos, como o Amazon ElastiCache oferecendo até 33% de desconto para o Valkey sobre o Redis.

Compreender a física do cache e a economia deste novo cenário de licenciamento é essencial para engenheiros Staff+ e arquitetos de sistemas. Se você projeta sistemas distribuídos sem modelar gargalos de invalidação, restrições de concorrência e custos no nível de motor, sua camada de cache não é uma solução de desempenho—é um risco de confiabilidade.


1. A Física do Cache Stampede (Thundering Herd)

O modo de falha mais destrutivo de uma camada de cache é o Cache Stampede (efeito manada). No padrão tradicional cache-aside, quando um cliente solicita uma chave, a aplicação consulta o cache. Se a chave estiver presente (cache hit), a retorna de imediato. Se estiver ausente ou expirada (cache miss), a aplicação consulta o banco de dados, escreve o resultado de volta no cache e o retorna ao cliente.

Sob alta concorrência, essa lógica falha. Considere uma chave representando um objeto de configuração muito acessado ou detalhes de um produto na página inicial. A chave recebe 5.000 requisições simultâneas. De repente, o tempo de vida (TTL) da chave expira.

Em milissegundos, todas as 5.000 threads observam um cache miss. Como não há coordenação entre as threads, todas as 5.000 caem no banco de dados para buscar os mesmos dados. O banco de dados, que operava confortavelmente com carga constante e baixa, é subitamente inundado com 5.000 consultas idênticas e pesadas. Isso eleva a CPU do DB para 100%, esgota conexões e faz a latência disparar de milissegundos para segundos.

A Mitigação por Lock Mutex e o Problema da Fila (Queueing)

A correção mais simples para o cache stampede é o uso de travas (locks). Quando uma thread encontra um cache miss, ela deve adquirir um lock distribuído (usando SETNX no Redis ou equivalente no Valkey) antes de consultar o banco de dados. Apenas a thread que adquire o lock realiza a consulta no banco de dados e atualiza o cache. Todas as outras esperam (em polling ou bloqueadas) até que o lock seja liberado ou o cache atualizado.

Embora isso proteja o banco de dados, introduz um gargalo de latência considerável. Se a consulta no banco levar 200ms para ser executada, as outras 4.999 threads que esperam pelo lock ficam bloqueadas. Sob limites de concorrência, esse comportamento de fila se propaga para os servidores de aplicação upstream, saturando pools de threads (como lag no event loop do Node.js ou esgotamento de threads na JVM) e causando timeouts.

Nosso script de simulação demonstra esse trade-off:

  • Estratégia Não Mitigada (UNMITIGATED): Sob 100 requisições concorrentes, a expiração do cache faz com que todas as 100 requisições atinjam o banco. O banco é lido 100 vezes, mas o tempo decorrido é baixo (cerca de 309ms) porque as leituras ocorrem em paralelo.
  • Estratégia Mutex (MUTEX): Sob a mesma carga, o banco de dados é lido exatamente 1 vez. No entanto, como as outras 99 requisições são forçadas a esperar pela liberação do lock, a latência média sobe para 94,64ms e a latência P99 dispara para 1329.18ms.

A Solução Matemática: Expiração Probabilística Antecipada (XFetch)

Para resolver o trade-off entre carga no banco e latência em fila, devemos abandonar a expiração binária. Em vez de esperar que uma chave atinja o TTL absoluto de zero, recalculamos probabilisticamente o tempo de vida em cada leitura e disparamos uma atualização assíncrona em segundo plano antes que a chave expire.

Esse é o princípio do algoritmo XFetch, publicado por Vattani et al. na PVLDB de 2015, Optimal Probabilistic Cache Expiration.

Verified SourceVLDB Endowment

O algoritmo XFetch define um modelo probabilístico onde uma operação de leitura dispara uma atualização antecipada de cache se uma variável aleatória satisfizer uma inequação específica em relação ao TTL restante e ao tempo de processamento do banco de dados.

O algoritmo determina que uma requisição de leitura deve disparar uma busca antecipada e assíncrona no banco de dados se:

-β · δ · ln(rand()) > ttl

Onde:

  • ttl é o tempo de vida restante (Time-To-Live) do item no cache, em segundos.
  • δ é o tempo (em segundos) gasto para buscar o item no banco de dados e gravá-lo no cache (o custo de computação).
  • β é um multiplicador constante maior que zero (o parâmetro de agressividade). Um β mais alto faz com que o cache dispare atualizações antecipadas mais agressivamente.
  • rand() é um número flutuante aleatório gerado uniformemente no intervalo (0, 1].

Como ln(rand()) resulta em um número negativo, multiplicá-lo por -β · δ resulta em um número positivo. À medida que o ttl se aproxima de zero, aumenta a probabilidade de que a variável aleatória supere o ttl.

Matematicamente, esse modelo aproveita as propriedades da distribuição de Gumbel (distribuição de valores extremos). Sob alta concorrência, o tempo de chegada de múltiplas requisições se comporta como um processo de Poisson. O atraso máximo observado antes que a atualização em segundo plano seja disparada segue uma distribuição semelhante à de Gumbel, garantindo que pelo menos um worker atualize os dados antes da expiração dura.

Quando uma thread atende a essa condição, ela retorna o valor em cache para o cliente de imediato (a latência permanece em ~2ms), mas lança um worker assíncrono para buscar os dados no banco e atualizar o cache. Todas as leituras simultâneas subsequentes continuam a acessar o cache até que o worker de segundo plano conclua a escrita. O stampede é evitado com tempo de fila zero para o cliente.


2. Calibração Incorreta de TTL e Amplificação de Escrita

O segundo gargalo silencioso de latência é a incompatibilidade entre frequência de escrita e TTLs de cache. Muitos desenvolvedores definem TTLs arbitrários (ex.: "cache de 10 minutos em tudo") sem analisar a relação entre leitura e escrita dos dados. Isso gera dois antipadrões: alta rotatividade de cache (churn) e amplificação de escrita.

O Ciclo de Rotatividade do Cache (Cache Churn)

Se um conjunto de dados é atualizado uma vez por hora, mas o TTL é de 5 minutos, a chave expirará e será buscada no banco 12 vezes por hora. Se essa chave for lida raramente (ex.: uma vez a cada 30 minutos), o cache não está gerando ganho de desempenho; está apenas adicionando latência de gravação ao banco e consumindo memória no cache.

Por outro lado, se um conjunto de dados é atualizado 100 vezes por minuto e o TTL é de 10 minutos, o padrão cache-aside exige que o cache seja invalidado (deletado ou sobrescrito) em cada escrita para evitar dados desatualizados. Isso resulta em Amplificação de Escrita.

Modelando a Amplificação de Escrita

Em uma arquitetura cache-aside, cada operação de escrita precisa realizar duas etapas:

  1. Gravar o novo estado no banco de dados principal.
  2. Invalidadar (deletar) ou atualizar a chave correspondente no cache.

Se sua aplicação possui carga pesada de escrita (como rastreamento de sessão, contadores em tempo real ou IoT), o custo de atualizar ou deletar chaves constantemente no cache pode superar o de consultar o banco diretamente.

Considere um sistema com proporção de leitura para escrita de 1:10 (10 escritas para 1 leitura). Se você implementar cache-aside, quase todas as leituras resultarão em cache miss, pois as escritas constantes invalidam a chave repetidamente. A latência média desse sistema é:

Latênciaméd = α · Latênciacache_hit + (1 - α) · (LatênciaDB_read + Latênciacache_write) + Latênciacache_invalidate

Onde α representa a taxa de acerto do cache (0 ≤ α ≤ 1). Como o cache é invalidado com muita frequência, a taxa α despenca para próximo de zero. Consequentemente, a latência média torna-se pior do que consultar o banco diretamente, pois a aplicação paga a penalidade de escrita e de invalidação do cache em quase todas as requisições.


3. Gargalos de Hot Keys em Clusters Distribuídos

Quando as camadas de cache são escaladas horizontalmente para processar centenas de milhares de RPS, elas são configuradas como clusters distribuídos. Tanto no Redis Cluster quanto no Valkey Cluster, o espaço de chaves é dividido em $16.384$ hash slots lógicos.

Cada nó recebe um subconjunto desses slots. Para determinar qual nó armazena uma chave, o cliente calcula o hash CRC16 da chave, calcula o resto da divisão por 16.384 e direciona a requisição:

Slot = CRC16(chave) mod 16384

Essa estratégia funciona muito bem para distribuir memória e CPU quando o acesso às chaves é uniforme. No entanto, falha completamente quando uma única chave recebe uma quantidade desproporcional de tráfego—uma Hot Key.

A Saturação de um Único Nó

Se seu cluster possui 10 nós e você processa 100.000 RPS distribuídos uniformemente em 10.000 chaves, cada nó lida com cerca de 10.000 RPS.

Contudo, se uma única chave (como produto:item_viral ou sessão:promotor_ativo) receber repentinamente 50.000 RPS, todas essas requisições serão roteadas para o único nó que possui o hash slot correspondente àquela chave.

                                    [Requisições Clientes: 100k RPS]
                                                 |
                       +-------------------------+-------------------------+
                       | (50k RPS Chaves Uniformes)                        | (50k RPS Hot Key)
                       v                                                   v
           [Distribuição de Hash Slots]                        [Único Hash Slot: Slot 4821]
                       |                                                   |
         +-------------+-------------+                                     |
         |                           |                                     v
         v                           v                               [Nó 1: SATURADO]
      [Nó 2]                      [Nó 3]                                   |
    (10k RPS)                   (10k RPS)                           - CPU atinge 100%
                                                                    - Latência dispara
                                                                    - Conexões caem

Esse nó terá sua CPU elevada para 100%, saturando sua thread de execução. Mesmo que os outros 9 nós operem com 5% de uso de CPU, o cluster começará a recusar conexões e a apresentar picos de latência devido à saturação do Nó 1. Adicionar mais nós não resolve, pois uma única chave não pode ser dividida entre diferentes instâncias.

Inspeção por CLI e Contadores LFU

Para detectar hot keys em produção, engenheiros devem inspecionar o cache usando ferramentas nativas:

bash
redis-cli --hotkeys -h <host> -p <port> # ou valkey-cli --hotkeys -h <host> -p <port>

Esses comandos identificam as chaves mais acessadas no banco. No entanto, essa análise exige que o banco utilize uma política de despejo baseada em frequência de uso (LFU).

Para ativar, a diretiva maxmemory-policy deve ser definida como allkeys-lfu ou volatile-lfu. Sob políticas LFU, o relógio LRU de 24 bits em cada objeto é reutilizado para armazenar a frequência de acesso:

  1. Contador Logarítmico (8 bits): Contador de acessos, escalado de forma logarítmica (ele incrementa mais devagar à medida que cresce, com limite de 255).
  2. Tempo de Decaimento (16 bits): Timestamp que armazena a última vez que o contador foi reduzido. Se a chave não for acessada por um período, seu contador diminui, garantindo que chaves quentes antigas não poluam os relatórios.

Executar --hotkeys em um cluster sem política LFU retornará um erro, forçando os desenvolvedores a recorrerem ao comando MONITOR, que pode degradar o desempenho do nó em até 50% sob alta carga.


4. A Economia do Cache: Redis 8 vs. Valkey

Os riscos técnicos do cache são ampliados pelas mudanças recentes de licenciamento. Em março de 2024, a Redis Inc. anunciou que o Redis migraria da licença BSD para um modelo de licenciamento duplo sob a RSALv2 e a SSPLv1, a partir da versão 7.4.

Verified SourceRedis Inc.

A Redis Inc. migrou para licenciamento duplo com código disponível mas restritivo (RSALv2 e SSPLv1) em março de 2024, impedindo provedores de nuvem de oferecer versões hospedadas do Redis sem contratos comerciais.

Essa mudança impediu que provedores de nuvem oferecessem versões gerenciadas do Redis sem firmar contratos comerciais com a Redis Inc. Como resposta, a Linux Foundation, com apoio de AWS, Google, Oracle, Ericsson e Snap, lançou o Valkey—um fork do Redis 7.2.2 totalmente de código aberto (licença BSD).

Divergência de Desempenho Arquitetural

Desde o fork, o Valkey evoluiu de um substituto direto para um motor em memória otimizado. O Valkey 8.0 introduziu melhorias significativas no processamento de rede multi-thread.

Embora o Redis 6.0 tenha introduzido I/O multi-thread, ele apenas transferia a leitura e escrita de buffers de sockets para threads secundárias. A análise e a execução dos comandos permaneciam strictly single-threaded na thread principal do event loop para garantir atomicidade.

O Valkey 8.0 redesenhou essa estrutura. Ele introduziu um mecanismo de análise concorrente que permite que threads de trabalho leiam e interpretem os protocolos dos clientes em paralelo, reduzindo drasticamente o consumo de CPU da thread principal. Para cargas de alta concorrência, essa arquitetura multi-thread gera ganhos de taxa de transferência de 1,2x a 2,0x em relação ao Redis OSS no mesmo hardware.

Preços de Provedores de Nuvem e o Desconto do Valkey

Para incentivar a migração, os provedores de nuvem introduziram reduções de preço significativas para o Valkey. O exemplo mais evidente é o Amazon ElastiCache, que lançou suporte oficial ao Valkey em 8 de outubro de 2024.

Verified SourceAmazon Web Services

A AWS anunciou o Amazon ElastiCache para Valkey em 8 de outubro de 2024, oferecendo instâncias baseadas em nós 20% mais baratas e configurações Serverless 33% mais baratas que o ElastiCache para Redis.

A tabela de preços altera significativamente a equação de custo por RPS:

Modelo de ImplantaçãoDesconto Valkey vs. Redis OSSAlocação Mínima de Dados
Cluster Baseado em Nós20% menor no preço de instâncias sob demandaLimites padrão da instância
Cluster Serverless33% menor no armazenamento e requisições100 MB (vs. 1 GB no Redis)

Para implantações baseadas em nós, você pode combinar o desconto de 20% do Valkey com reservas de instâncias (1 ou 3 anos) na AWS para obter até 60% de redução no custo total em relação ao Redis OSS.

Para Serverless, a redução do limite mínimo de armazenamento de 1 GB (Redis) para 100 MB (Valkey) é extremamente relevante para microsserviços. Um sistema que executa 50 microsserviços independentes, cada um com uma pequena pegada de cache, pagaria por um mínimo de 50 GB de armazenamento no Redis Serverless. Com o Valkey Serverless, esse limite cai para 5 GB, resultando em uma economia imediata de 90% em armazenamento.


5. Benchmarks de Custo por RPS

Para avaliar o impacto econômico da transição Redis vs. Valkey, devemos olhar além do preço simples da instância. Precisamos calcular o Custo por RPS (Requisições Por Segundo).

Considere uma aplicação que exige uma taxa estável de 200.000 RPS com 50 GB de dados em cache. Vamos comparar o custo mensal de rodar essa carga no Amazon ElastiCache usando Redis OSS vs. Valkey.

Cenário A: Clusters Baseados em Nós (Sob Demanda)

Para suportar 200.000 RPS com alta disponibilidade (um nó primário e uma réplica) e volume de 50 GB, implantamos instâncias cache.r7g.xlarge (26,38 GB de memória cada). Precisamos de no mínimo 2 shards para acomodar os 50 GB (com uma réplica para cada shard, totalizando 4 nós).

  • ElastiCache para Redis OSS:

    • Tipo de Instância: cache.r7g.xlarge (Redis)
    • Preço por Hora (us-east-1): $0,334 por nó
    • Quantidade de Nós: 4 (2 shards, 1 réplica por shard)
    • Custo Mensal: 4 nós × $0,334/hora × 730 horas = $975,28
  • ElastiCache para Valkey:

    • Tipo de Instância: cache.r7g.xlarge (Valkey)
    • Preço por Hora (us-east-1): $0,267 por nó (20% de desconto aplicado)
    • Quantidade de Nós: 4
    • Custo Mensal: 4 nós × $0,267/hora × 730 horas = $779,64

Cenário B: Implantações Serverless

O ElastiCache Serverless cobra com base nas ECPUs e no armazenamento de dados.

  • 1 ECPU = 1 leitura ou escrita de até 1 KB.

  • Carga de Trabalho: 200.000 RPS (leituras ≤ 1 KB) = 200.000 × 60 × 60 × 730 = 525,6 bilhões de ECPUs/mês.

  • Armazenamento: Média de 50 GB.

  • ElastiCache Serverless para Redis OSS:

    • Preço de Armazenamento: $0,125 por GB-mês
    • Preço de ECPU: $0,0034 por milhão
    • Custo de Armazenamento: 50 GB × $0,125 = $6,25
    • Custo de ECPU: 525.600 milhões × $0,0034 = $1.787,04
    • Custo Mensal Total: $6,25 + $1.787,04 = $1.793,29
  • ElastiCache Serverless para Valkey:

    • Preço de Armazenamento: $0,084 por GB-mês (33% de desconto)
    • Preço de ECPU: $0,00228 por milhão (33% de desconto)
    • Custo de Armazenamento: 50 GB × $0,084 = $4,20
    • Custo de ECPU: 525.600 milhões × $0,00228 = $1.198,37
    • Custo Mensal Total: $4,20 + $1.198,37 = $1.202,57

Resumo Econômico

Para volumes de requisições constantes, o modelo Serverless pode ser mais caro que o modelo baseado em nós devido à cobrança direta por volume de processamento.

No Cenário B, a precificação do Valkey Serverless economiza $590,72 por mês (33%) sobre o Redis Serverless. Se a carga for previsível, migrar para um cluster Valkey baseado em nós (Cenário A) reduz o custo mensal de $1.202,57 para $779,64—uma economia adicional de 35% mantendo a alta disponibilidade.


6. Runbook Arquitetural: Mitigando Gargalos de Cache

Para evitar que o cache degrade a latência e infle seus custos de nuvem, use este checklist para auditar suas arquiteturas:

1. Implemente Expiração Probabilística (XFetch) para Hot Keys

Para qualquer chave com mais de 1.000 RPS, não use TTLs binários simples. Implemente o algoritmo XFetch na biblioteca cliente da sua aplicação.

python
# Exemplo conceitual em Python para implementação do XFetch no cliente import math import random import time def xfetch_read(client, key, ttl_seconds, computation_delta, beta=1.0): value, remaining_ttl = client.get_with_ttl(key) # Valida a condição do XFetch rand_val = random.uniform(0.0001, 1.0) if remaining_ttl is None or remaining_ttl <= 0: # Miss absoluto (chave expirada) return fetch_and_set(client, key, ttl_seconds) if -beta * computation_delta * math.log(rand_val) > remaining_ttl: # Dispara atualização assíncrona em segundo plano async_executor.submit(fetch_and_set, client, key, ttl_seconds) return value

2. Resolva Gargalos de Hot Keys com Cache Local L1

Se a taxa de requisições exceder a capacidade de rede ou CPU de um único nó, implemente cache em múltiplos níveis.

  • Cache L1 (Memória Local da Aplicação): Armazene a hot key na memória local da aplicação (ex.: usando Guava em Java ou um cache LRU local em Node.js) por 5 a 10 segundos. Isso intercepta as requisições antes que atinjam a rede do cluster de cache.
  • Key Salting (Salgamento de Chaves): Para chaves de escrita frequente, anexe um sufixo numérico aleatório à chave ao gravar (ex.: hot_key:1, hot_key:2) e distribua as gravações. Ao ler, consulte um sufixo aleatório ou faça a agregação das chaves.

3. Mitigue a Amplificação de Escrita no Cache-Aside

Se as gravações no banco de dados forem muito frequentes, evite invalidar o cache a cada modificação. Grave no banco e deixe o cache expirar naturalmente com um TTL curto. Se a consistência for crítica, avalie os padrões Write-Through ou Write-Behind, onde a aplicação grava primeiro no cache, e uma fila em segundo plano atualiza o banco de forma assíncrona.

4. Migre para Valkey para Capturar Descontos de Nuvem

Como o Valkey é um drop-in replacement para o Redis OSS, migrar sua infraestrutura é de baixo risco. Na AWS, você pode migrar clusters existentes do ElastiCache Redis para Valkey sem tempo de inatividade.

  • Converta Reservas de Nós: Certifique-se de converter suas Reserved Nodes do Redis para Valkey Reserved Nodes para manter e acumular seus descontos.
  • Otimização Serverless: Caso utilize Serverless, atualize para o Valkey para capturar automaticamente o desconto de 33% em armazenamento e ECPUs, e otimize sua alocação mínima para 100 MB.

Fontes Externas

Leituras Relacionadas no gsstk

Este artigo foi estruturado por humanos e sintetizado com auxílio de IA sob a persona Athena (AI).

Receba novos artigos

Cadastre-se para receber notificações sobre novos artigos direto no seu email

Não enviaremos spam. Você pode cancelar a inscrição a qualquer momento.