Hoje dá para entregar serviços inteiros com um único binário stateless e um bucket. Sem banco de dados. Sem fila. Sem volumes em bloco. Só armazenamento de objetos. Nas últimas semanas, posts no Show HN como servidores Git leves em cima de armazenamentos do tipo S3, streams duráveis sobre HTTP apoiados em object storage e bibliotecas self-hosted que rodam inteiramente em buckets ganharam tração. Isso não é uma curiosidade. É um padrão de arquitetura que finalmente tem os primitivos para ser nível produção — se você aplicar onde ele se encaixa.
Se você é um CTO buscando reduzir blast radius de infra e risco de fornecedor, vale olhar esse padrão com seriedade. Mas não é religião. É um trade deliberado: você troca latências abaixo de 10 ms e transações ACID por durabilidade extrema, portabilidade e simplicidade operacional. Abaixo vai um framework de decisão e alguns números concretos para você decidir onde “um binário, um bucket” pertence na sua stack.
Por que isso é viável agora
- Chegou a consistência forte. Desde 2020, object stores importantes como Amazon S3 oferecem consistência forte de leitura após escrita (read-after-write) para PUT, DELETE e LIST. Você não precisa mais contornar consistência eventual para CRUD básico.
- Custo e portabilidade melhoraram. Requisições ao S3 são baratas por milhão, o Cloudflare R2 removeu pedágios de egress, e APIs compatíveis com S3 (MinIO, R2, Wasabi) tornam “mover seu bucket para outro lugar” um hedge real.
- Edge e serverless amadureceram. Endpoints de VPC, caches de borda regionais e compute por requisição permitem manter latência razoável sem um farm de servidores com estado.
O que “um binário, um bucket” realmente significa
Padrões que você vai ver:
- Logs append-only. Eventos são gravados como objetos imutáveis particionados por tempo ou shard. Consumidores fazem listagens e leem faixas; compactações periódicas mesclam arquivos pequenos.
- Blobs endereçados por conteúdo. Artefatos, imagens, pesos de modelos ou packfiles endereçados por hash; índices são pequenos manifestos.
- Ponteiros de snapshot. Um pequeno manifesto “latest” aponta para objetos imutáveis do estado atual; atualizações são serializadas por um único escritor ou via lease.
- Formatos legíveis por intervalo. Armazene Parquet, packfiles ou bundles tipo tar para servir fatias pequenas via HTTP Range em vez de criar milhões de objetos minúsculos.
O fio condutor é: imutabilidade primeiro, compactação depois, ponteiros por último.
Onde brilha
- Trilhas de auditoria reguladas e requisitos WORM. Object Lock e versionamento oferecem semântica de write-once, retain-for-N-years de fábrica. Se você vive sob retenção nos moldes da SEC 17a-4 ou precisa de imutabilidade comprovável, isso é seu amigo.
- Registros de artefatos e bibliotecas de mídia. Blobs grandes e imutáveis com atualizações ocasionais de metadados são exatamente o caso de uso de buckets.
- Taxas de escrita baixas a moderadas com leituras em rajadas. 5–50 escritas/seg por serviço com fan-out de leitura de 10–100x é confortável, especialmente se você agrupar escritas e fazer cache de leituras.
- Hedge cross-cloud. Quer rodar o mesmo serviço em AWS, GCP ou on-prem? APIs compatíveis com S3 mais um binário stateless tornam a portabilidade real.
Onde dói
- Contadores quentes e estado de alta contenção. Você não tem incrementos atômicos nem transações multi-row. Se precisa de ACID, ainda precisa de um banco.
- Cauda de latência abaixo de 20 ms. GETs típicos p50 de objetos ficam em dezenas de milissegundos; p99 pode ir de 100–400 ms. Com cache dá para esconder parte, mas não tudo.
- Taxas muito altas de escrita de objetos minúsculos. Custos por requisição e sobrecarga de metadados podem superar os custos de armazenamento se você bombardear o bucket com milhares de objetos por segundo.
A matemática de custos que a maioria das equipes ignora
Vamos modelar as versões ingênua e a sensata de um serviço tipo fila apoiado em bucket.
Ingênuo: um objeto por mensagem
- Taxa de escrita: 200 mensagens/seg (moderada).
- Taxa de leitura: 1000 leituras/seg (fan-out 5x).
- Operações mensais: ~518 milhões de escritas, ~2,6 bilhões de leituras.
- Usando preços típicos de requisição do S3: $0,005 por 1000 PUT; $0,0004 por 1000 GET.
Custo: escritas ≈ 518,400k / 1k × $0.005 = $2,592/mês; leituras ≈ 2,592,000k / 1k × $0.0004 = $1,037/mês. Total ≈ $3,629/mês só em cobranças por requisição, antes do armazenamento. Não é catastrófico, mas é desperdício — porque você está pagando por objeto minúsculo.
Sensato: agrupar e empacotar
- Agrupe escritas: 100 mensagens por objeto → 2 escritas/seg em vez de 200.
- Leituras por intervalo: Leia mensagens específicas por offsets em um pacote de 8–64 MB; faça cache dos pacotes na borda.
- Operações mensais: ~5,18 milhões de escritas; leituras caem 10–100x com cache.
Custo: escritas ≈ 5,184k / 1k × $0.005 = ~$26/mês; leituras talvez ~$100–$300/mês dependendo do hit ratio. Resultado: 10–50x mais barato em requisições. Custos de armazenamento mal mudam.
A lição: com armazenamento de objetos, granularidade é o seu orçamento. Se você não conseguir fazer batching, vai pagar por isso.
Falando sério sobre latência
Assuma acesso em região “quente” com um endpoint de VPC:
- GET p50: 10–30 ms é típico.
- GET p95–p99: picos de 100–400 ms acontecem. Projete para isso.
- PUT p50: 10–50 ms; p99 pode flertar com algumas centenas de ms.
Se você precisa de cauda de um dígito de milissegundos em caminhos críticos, esta não é a ferramenta certa. Se dá para esconder as caudas atrás de caches e trabalho assíncrono, tudo bem.
Modelo de consistência e concorrência
Os buckets modernos oferecem leitura após escrita forte para novos objetos e sobrescritas. Você pode confiar em um LIST após um PUT. Você não pode confiar no bucket como seu gerenciador de locks. Padrões que vimos funcionar de forma confiável:
- Um único escritor por shard com um lease armazenado em um KV rápido (DynamoDB/etcd). O bucket guarda objetos imutáveis; o KV protege o pequeno ponteiro mutável.
- Commits de manifesto. Grave seus dados em um novo objeto endereçado por conteúdo, depois atualize um pequeno manifesto que aponta para ele. Serialize atualizações do manifesto por um único escritor ou lease.
- Escritas idempotentes via hashing. Use o SHA-256 do payload como chave para evitar duplicatas em retries.
Poderia tentar compare-and-swap em um objeto-ponteiro no bucket? Não de forma portátil. Mantenha seus locks em um KV projetado para isso e mantenha seus dados imutáveis no bucket.
Padrões de design que sobrevivem em produção
1) Layout para batching e compactação
- Particione por tempo e shard: por exemplo,
topic=orders/partition=03/dt=2026-08-25/0001.parquet. - Escreva pequeno, compacte depois: Emita objetos de 1–8 MB durante a ingestão para reduzir latência e depois compacte de hora em hora em objetos de 64–256 MB para reduzir drasticamente a sobrecarga de requisição e listagem.
- Use formatos amigáveis a Range: Parquet, packfiles, bundles tipo tar com um rodapé de índice para que leituras busquem só os intervalos necessários.
2) Um KV minúsculo para o plano de controle
- Armazene ponteiros, leases e números de sequência em um KV com escritas condicionais. Mantenha isso em algumas centenas de escritas/seg.
- Todo o resto é imutável e vive no bucket. Se algo se move ou se altera, você está fazendo errado.
3) Cache agressivo
- CDN de borda para leituras com TTLs longos e cache-busting via chaves versionadas.
- Cache NVMe local no binário para bundles quentes e entradas de compactação.
- URLs pré-assinadas para que clientes baixem blobs grandes diretamente do bucket, sem passar pelo seu binário.
4) Guardrails de custo
- Alertas de orçamento com base em contagem de requisições, não só transferência e armazenamento.
- Aborte uploads multipart incompletos automaticamente; caso contrário você vai vazar armazenamento e dinheiro.
- Rotacione prefixos para limitar o custo de listagem de objetos em diretórios enormes; evite listar o mundo a cada requisição.
5) Segurança e conformidade
- Buckets privados com endpoints de VPC para evitar egress público e reduzir superfície de ataque.
- SSE-KMS para criptografia com chaves por tenant se você for multi-tenant.
- Object Lock (modo governance ou compliance) para datasets com retenção crítica.
- Access logs e CloudTrail habilitados; mantenha esses logs em um bucket separado e travado.
Casos de uso que realmente funcionam
- Repositórios de conteúdo tipo Git. Coloque packfiles e refs no bucket; serialize atualizações de refs em um KV. Um pequeno binário traduz o protocolo wire do Git para operações no bucket. É exatamente o que vários novos servidores Git leves no HN estão fazendo.
- Streams de eventos duráveis sobre HTTP. Mensagens são armazenadas em bundles particionados por tempo; consumidores percorrem os bundles via requisições Range. A latência fica em dezenas a centenas de milissegundos, mas a durabilidade é extrema e a operação é simples.
- Mídia, modelos e artefatos. Abençoe a imutabilidade. Use endereços por conteúdo; nunca sobrescreva dados reais, apenas manifestos.
- Snapshots de feature flags e configuração. Um único ponteiro para a configuração ativa, servido via CDN; bundles de config no bucket; um KV coordena a troca do ponteiro.
Onde isso dá errado no mundo real
- Tratar buckets como POSIX. Sem semântica de rename atômico, sem locks de diretório, e listar é lento se você despejar 100 milhões de objetos em um único prefixo.
- Tempestades de pequenos objetos. Se você estiver abaixo de ~1 MB por objeto em média e empurrando centenas de escritas/seg, os custos de requisição vão te devorar.
- LISTs sem limite em caminhos quentes. Sempre carregue tokens de continuação; nunca faça LIST mais do que precisa; melhor ainda, leia de um manifesto ou índice.
- Ilusões cross-region. A defasagem de replicação é real. Nunca construa coordenação cross-region em cima de replicação “alcançando”.
Portabilidade e realidades de fornecedores
A API do S3 é o padrão de fato, mas as semânticas diferem nas bordas:
- Consistência: S3 tem leitura após escrita forte; alguns provedores historicamente não tinham para LIST. Verifique em staging.
- Preços de egress e requisições: Cloudflare R2 removeu pedágios de egress, mas classes de requisição são precificadas de forma diferente do S3. Faça as contas antes de perseguir “sem egress”.
- On-prem: MinIO é excelente para semântica S3 no seu próprio DC ou em regiões no país quando a LGPD ou contratos exigem residência de dados no Brasil.
Um framework de decisão que você pode aplicar esta semana
Pontue seu workload candidato em cada eixo de 1 (ajuste ruim) a 5 (ótimo ajuste):
- Taxa e granularidade de escrita. Abaixo de 10 escritas/seg em média e agrupável em objetos de 1–8 MB? Pontuação 5. Centenas de pequenas escritas/seg que você não consegue agrupar? Pontuação 1.
- Tolerância a latência. Tudo bem com leituras de 50–150 ms e caudas ocasionais de 200–400 ms? Pontuação 5. Precisa de p99 abaixo de 20 ms? Pontuação 1.
- Imutabilidade primeiro. 95% dos seus dados são append-only? Pontuação 5. A maior parte é conjunto de linhas quentes e mutáveis que precisam de transações? Pontuação 1.
- Retenção regulatória. Precisa de WORM ou retenção comprovável? Pontuação 5. Sem pressão de compliance? Pontuação 3. Precisa de deletes/updates frequentes? Pontuação 1.
- Valor da portabilidade. Hedge cross-cloud ou on-prem importa? Pontuação 5. Você já está all-in em um serviço gerenciado mesmo? Pontuação 2.
Se der 18–25: Forte candidato a um serviço apoiado em bucket. 12–17: Considere um híbrido: dados imutáveis em buckets, ponteiros/locks em um KV/DB. Abaixo de 12: Fique com bancos de dados e filas.
Um plano piloto de 30–60–90 dias
30 dias: Comprove o núcleo
- Escolha um workload: store de artefatos, audit log ou um serviço de configuração predominantemente de leitura.
- Construa um único binário stateless em Go ou Rust com uma API mínima: PUT de bundle, GET por chave ou Range, LIST por manifesto.
- Dê backend com um store compatível com S3 em staging (AWS S3 ou MinIO), mais um KV pequeno (DynamoDB/etcd) para leases e ponteiros.
- Instrumente contagens de requisições, latências p50/p95/p99 e tamanhos de bundles.
60 dias: Fortalecer
- Adicione compactação e índices de bundles para limitar custos de requisição.
- Coloque uma CDN na frente dos GETs; mude para URLs pré-assinadas para downloads grandes.
- Habilite versionamento e Object Lock em modo governance para datasets de auditoria.
- Execute testes de consistência sob falhas: derrube o processo no meio do commit 1000 vezes; assegure que nenhum estado parcial escape.
90 dias: Decidir
- Compare custo/latência observados versus um design equivalente apoiado em DB.
- Decida o escopo: expandir para mais serviços de leitura pesada ou manter apenas para artefatos e logs.
- Escreva o runbook: SLOs de compactação, políticas de ciclo de vida do bucket, rotinas de aborto de multipart e alertas de orçamento.
Como montaríamos a equipe com um pod nearshore
Para startups dos EUA, um pod nearshore de duas a três pessoas a partir do Brasil pode assumir isso end-to-end:
- 1 engenheiro sênior de Go ou Rust para implementar o binário, I/O por intervalo e compactação.
- 1 engenheiro de plataforma para configurar políticas do S3, endpoints de VPC, KMS e CDN.
- Opcional: um engenheiro de dados se você precisar de formatos colunares/Parquet e analytics downstream.
Espere 6–8 horas de sobreposição com os fusos dos EUA e uma janela total de 8–12 semanas para ir do POC a um serviço endurecido com SLOs e guardrails de custo.
Em resumo
“Um binário, um bucket” não é um truque. É uma alavanca operacional: entregue sistemas mais simples ao apostar em imutabilidade, batching e nas garantias de durabilidade que o armazenamento de objetos já oferece. Use quando a física do seu workload combinar com a física do bucket. Quando o ajuste é bom, você economiza 20–50% em infra, reduz a superfície operacional e ganha um hedge multi-cloud real. Quando o ajuste é ruim, você constrói um cosplay de sistema de arquivos lento e caro. Escolha com cuidado.
Principais pontos
- Serviços apoiados em armazenamento de objetos são viáveis em produção agora graças à consistência forte e ao amadurecimento de edge/serverless.
- Ótimos ajustes: dados imutáveis, auditoria/WORM, artefatos e taxas de escrita baixas a moderadas que podem ser agrupadas.
- Ajustes ruins: estado transacional quente e requisitos de cauda abaixo de 20 ms.
- Granularidade dita o custo — agrupe em bundles de 1–64 MB e compacte para evitar explosões de taxas por requisição.
- Mantenha locks e ponteiros em um KV de verdade; mantenha os dados imutáveis no bucket.
- Use versionamento, Object Lock, endpoints de VPC, KMS e CDN para reforçar segurança e performance.
- Rode um piloto de 30–60–90 dias com SLOs firmes e alertas de orçamento antes de se comprometer.