As três primitivas de armazenamento do Cloudflare — KV, R2 e Cache API — aparecem juntas na documentação e compartilham o mesmo runtime, o que cria a impressão de que são alternativas para o mesmo problema. Não são. Cada uma foi construída com uma arquitetura diferente, para um workload diferente, com um modelo de custo diferente. Usar a errada não é só ineficiente: em alguns casos, simplesmente não funciona.
A confusão é compreensível. As três "armazenam dados". Mas a distinção relevante não é o que elas fazem em abstrato — é como cada uma se comporta sob tráfego real, o que elas custam em escala e quais garantias oferecem.
KV: o store global para dados pequenos e lidos com frequência
O KV é um store de chave-valor com distribuição global. Escritas vão para um store central e propagam para mais de 300 PoPs em até 60 segundos. Leituras chegam de sub-milissegundo se a chave estiver em cache no PoP mais próximo, ou de ~20ms se precisar buscar no store central.
O modelo de custo favorece leituras em volume: $0.50 por milhão de leituras depois dos primeiros 10 milhões gratuitos mensais. Escritas custam o mesmo $0.50 por milhão, mas com apenas 1 milhão gratuito. O limite máximo por valor é 25MB.
KV funciona bem para dados escritos raramente e lidos massivamente: configuração de produto, feature flags, templates, índices de conteúdo, tokens de sessão com TTL. Funciona mal para qualquer coisa que muda com frequência ou exige consistência imediata — a consistência eventual com janela de até 60 segundos e a ausência de operações atômicas são limitações reais, não detalhes de documentação.
R2: armazenamento de objetos sem taxa de egresso
R2 é o armazenamento de objetos da Cloudflare, equivalente funcional ao S3. Foi construído para arquivos grandes — imagens, vídeos, backups, exports de dados, assets estáticos. O diferencial competitivo em relação ao S3 é a ausência de taxa de egresso: você não paga para transferir dados do R2 para a internet, o que no S3 é uma das linhas mais dolorosas do bill.
O custo de storage é $0.015/GB-mês. Cada operação de leitura (GET) no R2 conta como uma request — não há cache automático global como no KV. Se você faz um GET de um objeto R2 em cada request de Worker, você está pagando por cada request mais o tempo de latência de cada GET. Isso torna R2 inadequado para dados de alta frequência de leitura por request.
A combinação correta é usar R2 para o arquivo e KV (ou a Cache API) para o índice ou a versão cacheada. Um Worker que serve imagens pode armazenar o binário no R2 e manter no KV um JSON com URLs assinadas, metadados e headers HTTP — assim a leitura frequente acessa KV em sub-milissegundo, e R2 só é tocado para uploads e para geração das URLs.
O limite por objeto no R2 não é o mesmo que no KV: arquivos de múltiplos GB são suportados. Para o KV com seu máximo de 25MB por valor, R2 é o destino natural de qualquer dado que ultrapasse esse threshold.
Cache API: o cache de resposta HTTP por PoP
A Cache API armazena objetos Response no cache HTTP do PoP atual. É gratuita, sem cotas de operação, e opera como uma camada de cache sobre respostas HTTP — não como um store de estado compartilhado.
O detalhe crítico que a diferencia do KV é o escopo: Cache API é por PoP, não global. Um cache hit no PoP de São Paulo não afeta o PoP de Frankfurt. Se um Worker em Frankfurt nunca recebeu um request para aquela URL, o cache vai estar frio em Frankfurt, independente de quantas vezes São Paulo serviu aquela resposta do cache.
Outro limite: o conteúdo no Cache API pode ser evicted pelo PoP a qualquer momento por pressão de LRU. Não há garantia de persistência entre requests — o próximo request para o mesmo recurso pode encontrar o cache frio, mesmo que o request anterior tenha populado.
A Cache API funciona bem para deduplicação de requests a APIs de terceiros dentro de um curto período de tempo — você faz o fetch uma vez, cacheia a Response por 30 segundos, e requests subsequentes no mesmo PoP reutilizam a resposta sem chamar a API upstream. Também serve para caching de respostas computacionalmente caras que são requisitadas em rajadas no mesmo PoP.
O que não funciona: usar Cache API como estado compartilhado entre Workers ou entre regiões. Duas instâncias de Worker em PoPs diferentes não vão ver o mesmo estado de cache. Para estado compartilhado, KV é o caminho.
A matriz de anti-padrões
Usar R2 para configuração de aplicação é o erro mais comum entre times que chegam do S3. No S3, é comum armazenar config.json em um bucket e lê-lo na inicialização da aplicação — o servidor dura horas ou dias, então um GET a cada reinicialização é barato. No Workers, cada isolate pode ser criado e destruído com frequência. Cada GET ao R2 tem a latência de uma request de rede e conta como operação cobrada. Para configuração, KV com module-level caching é o modelo correto.
Usar KV para arquivos de vídeo ou datasets grandes é o outro extremo. O limite de 25MB por valor já cria problemas imediatos para qualquer asset de tamanho real. Mas além do limite, o custo de escrita no KV é proibitivo para arquivos que chegam por upload de usuário com frequência. R2 a $0.015/GB-mês é várias ordens de grandeza mais barato para storage de arquivos grandes.
Usar Cache API para qualquer tipo de estado que precise ser consistente entre PoPs é uma fonte garantida de comportamento errático. O sintoma típico é um bug que aparece "às vezes" — porque o PoP que serviu o request anterior tinha o cache populado, e o PoP que serviu este não tinha. Cache API não substitui KV para dados globais.
Como escolher
A decisão começa pelo tipo de dado e pela frequência de acesso. Dado pequeno, lido muitas vezes por segundo, precisa de distribuição global: KV. Arquivo grande, escrito por upload e lido com frequência baixa a moderada: R2. Resposta HTTP que muda raramente e pode ser local ao PoP: Cache API.
O custo confirma ou descarta a escolha. Se o volume de escritas for alto, KV fica caro. Se o volume de GETs individuais por arquivo for alto, R2 fica caro e lento. Se você precisa de consistência cross-PoP, Cache API não serve.
Combinar as três é o modelo correto
O padrão que mais aparece em arquiteturas maduras com Workers é a combinação deliberada. R2 guarda o binário. KV guarda o índice, o metadata e a URL assinada de curta duração. Cache API deduplicar requests em rajada para o mesmo PoP. Cada camada faz o que foi projetada para fazer, e o resultado é uma stack de storage que performa bem e custa o que deveria custar.
A armadilha é tentar simplificar para uma única primitiva. O Cloudflare oferece as três porque cada uma resolve um problema diferente. Entender a fronteira entre elas é o que separa uma implementação que funciona em desenvolvimento de uma que sobrevive ao tráfego real.
Leia também
- Cloudflare KV: o que globalmente distribuído significa quando você precisa escrever
- Invalidação de cache no KV: o problema que ninguém resolve com elegância
- Cloudflare Load Balancing e Geo Steering: quando o DNS vira camada de tráfego inteligente
- KV em produção: os padrões que funcionam e os que enganam no início
- KV para rate limiting, feature flags e configuração distribuída: onde funciona e onde quebra
- Workers + D1 + KV + R2: compondo bindings num mesmo serviço