Durable Objects ganham destaque por resolverem algo difícil — estado consistente com acesso serializado no edge — e isso cria um viés: engenheiros que acabaram de aprender a ferramenta tendem a aplicá-la em problemas que ela não precisa resolver. O resultado não é código errado. É código correto, funcionando, mais caro e mais complexo do que precisa ser. Para um CRUD simples construído sobre DOs quando D1 bastaria, a conta pode ser entre dez e cinquenta vezes maior, com nenhum benefício de consistência que o caso de uso exigisse.
A pergunta que determina se você precisa de DO
Antes de decidir por Durable Objects, há uma pergunta concreta: o estado que você precisa gerenciar requer acesso serializado de múltiplos clientes concorrentes?
Serializado significa que a ordem das operações importa e que duas operações simultâneas no mesmo dado precisam ser mutuamente exclusivas. Múltiplos clientes concorrentes significa que mais de um agente pode tentar modificar o mesmo dado ao mesmo tempo, e ambos precisam ver um resultado consistente.
Se a resposta for não — se o dado for lido por muitos mas escrito raramente, ou se escritas concorrentes no mesmo registro são improváveis ou tratadas por outra camada — um DO adiciona complexidade e custo sem adicionar garantia útil.
Onde D1 é a resposta certa
Para dados relacionais, D1 é o lugar natural dentro da pilha Cloudflare. Tem SQL completo, suporta JOINs, índices, transações, queries ad hoc — tudo que D1 tem e DO não tem. Um DO é um armazenamento de chave-valor linearizável com API procedural. Ele não tem como responder a "liste todos os usuários que fizeram login nos últimos 7 dias e ainda têm saldo positivo" sem que você tenha pré-computado e armazenado esse dado explicitamente.
D1 tem custo muito inferior para cargas de leitura: $0.001/milhão de linhas lidas, $1.00/milhão de linhas escritas. A latência de escrita adicional para a região primária do banco, que é a desvantagem mais citada do D1, afeta apenas operações de escrita. Para aplicações onde leituras dominam, esse tradeoff é amplamente favorável ao D1.
O caso que confunde: "mas eu preciso que as escritas sejam atômicas". D1 tem transações. BEGIN; UPDATE a...; UPDATE b...; COMMIT; garante atomicidade. A diferença é que D1 usa locks de banco de dados — com potencial de contenção em alta concorrência — enquanto um DO usa serialização por design. Para a esmagadora maioria das aplicações com cargas normais de escrita, D1 com transações é suficiente e muito mais barato.
Onde KV é a resposta certa
KV é otimizado para o padrão que domina a maioria dos casos de uso de edge: muitas leituras, escritas ocasionais, sem requisito de consistência forte entre PoPs diferentes. Cache de configuração, resultados de computação pre-calculados, sessões de usuário onde a consistência eventual é aceitável — KV responde leituras de cache local no PoP com latência sub-milissegundo e um modelo de preço que favorece leitura pesada.
O equívoco é assumir que porque KV não tem operações atômicas de leitura-modificação-escrita, você precisa de DO para qualquer coisa que mude. A grande maioria dos dados em aplicações web muda com padrões que não requerem atomicidade estrita: o perfil de usuário que é atualizado quando o usuário edita suas preferências não precisa de exclusão mútua com outra escrita concorrente — porque ninguém mais está editando o mesmo perfil ao mesmo tempo.
DO começa a fazer sentido quando a frequência de escritas concorrentes ao mesmo dado é alta o suficiente para que a consistência eventual do KV produza resultados incorretos visíveis para o usuário.
Rate limiting não é caso de uso de DO
Rate limiting é o exemplo mais comum de "parece que precisa de DO mas não precisa". A intuição é correta: rate limiting requer um contador por usuário que seja incrementado atomicamente a cada requisição. Se dois Workers incrementam o mesmo contador ao mesmo tempo, pode haver uma condição de corrida que deixa um usuário fazer mais requisições do que o limite.
A Cloudflare Rate Limiting API resolve isso nativamente, sem código, sem DO, sem custo adicional além do plano. Está disponível em todos os planos pagos, suporta limites por IP, por usuário autenticado, por rota, por header, e com períodos configuráveis. Para rate limiting no edge, usar a API nativa é mais simples, mais barato e mais confiável do que implementar um DO de contador.
O caso legítimo de DO para rate limiting é quando você tem requisitos que a API nativa não cobre: lógica custom de sliding window com precisão exata, limites que dependem de dados da sessão do usuário que não estão em headers, ou rate limiting que precisa ser consistente com outros dados que já vivem num DO da aplicação.
Workers Queues para processamento assíncrono
Outro padrão que às vezes aparece como candidato a DO: uma fila de trabalho onde múltiplos produtores enfileiram tarefas e múltiplos consumidores processam. DO poderia modelar isso como um objeto com uma lista de tarefas e um loop de processamento.
Workers Queues resolve isso nativamente: producers fazem env.QUEUE.send(message), consumers recebem batches via handler, com retry automático, dead letter queue, e cobrança separada de $0.40/milhão de mensagens. Para processamento assíncrono com retries e backoff, Queues é mais adequado, mais barato, e não tem o teto de throughput serial de um DO.
DO para processamento assíncrono tem sentido quando o consumo precisa ser serializado por identidade — processar todas as ações de um usuário em ordem, sem paralelismo — e quando esse processamento precisa acessar estado que é local ao DO de qualquer forma.
Storage de arquivos é R2, não DO storage
DO storage é um armazenamento de chave-valor otimizado para objetos pequenos — configurações, contadores, estado de sessão, mensagens. Não tem limites documentados por valor, mas é projetado para dados que cabem razoavelmente numa requisição. Para arquivos, imagens, vídeos, qualquer blob maior, R2 é o lugar correto: $0.015/GB-mês de storage (menos de um décimo do custo de DO storage), sem custo de egress para Workers, com API S3-compatible.
DO storage não é um substituto para object storage — é um armazenamento de estado transacional para o DO em si.
O diagnóstico antes de escolher
Quatro casos de uso onde DO resolve algo que as alternativas não resolvem com as mesmas garantias: edição colaborativa com múltiplos clientes modificando o mesmo documento simultâneamente, coordenação de presença em tempo real onde a lista de quem está online precisa ser consistente, locks distribuídos com timeout onde a expiração precisa ser garantida mesmo se o holder crashar, e logs de eventos ordenados por chegada onde a ordem de inserção é semanticamente importante.
Para tudo que não se encaixa nessa lista, existe uma alternativa dentro da plataforma que é mais barata, mais simples ou ambos. A decisão de usar DO começa com a pergunta sobre serialização. Se você não consegue articular por que a serialização de acesso ao dado é necessária para o caso de uso, DO provavelmente não é a ferramenta.
O risco de usar DO onde não é necessário não é de quebrar nada — o código vai funcionar. O custo é de carregar complexidade desnecessária: uma abstração que impõe limite de throughput serial, requer Workers Paid plan como pré-requisito, tem uma curva de aprendizado distinta, e custa mais do que as alternativas para os casos que as alternativas resolvem bem. Para uma equipe que já usa DOs em outros lugares do sistema, o custo marginal de mais um DO é menor. Para uma equipe começando no Cloudflare com um CRUD simples, começar com D1 e KV e adicionar DOs onde a serialização for genuinamente necessária é a ordem de complexidade certa.
Leia também
- Cloudflare Durable Objects: estado consistente no edge — o que realmente muda
- Durable Objects em produção: o que a conta vai parecer e os limites que surpreendem
- O modelo de programação dos Durable Objects: o que é diferente de tudo que você já usou
- Durable Objects e WebSockets: multiplayer sem servidor dedicado
- Migrar de Pages para Workers: quando faz sentido e o custo real da mudança
- WebAssembly para líderes: quando vale a decisão de arquitetura