Gestão Bem · Infra · Laudo de incidente

O disco não era o vilão. O LIST era.

A fatura de S3 em outubro já ia a US$ 4,23 em três dias — 84% da conta AWS do mês. Não era foto de cardápio. Era o sidecar Litestream perguntando ao bucket, milhares de vezes por hora, se o WAL ainda estava lá.

ID INC-2026-10-03-S3-LIST Severidade custo · sem outage Host gestaobem-cx33 Conta AWS 840298254452 Unidade cleat-litestream
ListBucket / dia
360 mil
~4 req/s contínuos
Ritmo S3
US$ 54
só LIST, por mês
Guardado de verdade
62 MB
4 GB na fatura (versões)
Depois do B2
~ US$ 0
Class A/B/C grátis

01 O que estava acontecendo

Em 29 de setembro de 2026 nasceu o bucket cleat-litestream-840298254452, região us-east-1, com versioning ligado no create. No mesmo dia o Cost Explorer deixa de ser US$ 0,09 (só storage) e vira tempestade de ListBucket.

O processo cleat-litestream (Litestream 0.5.17) replica 27 arquivos SQLite de apps no cx33 para S3, sync-interval: 1s. Cada ciclo lista gerações e WAL. No S3, LIST custa o mesmo que PUT: US$ 0,005 por mil. Disco quase não pesava. API, sim.

Fatura diária de S3

20/set
0,09
21/set
0,09
22/set
0,09
23/set
0,09
24/set
4,35
25–28
0,09
29/set
0,85
30/set
9,19
01/out
1,96
02/out
1,89
03/out
0,39*

Escala relativa a 30/set = US$ 9,19. *3/out ainda parcial no Cost Explorer, cutover ~19:00 UTC.

Outubro 1–3 por operação

OperaçãoQuantidadeCusto
ListBucket800.498US$ 4,00
PutObject27.238US$ 0,14
GetObject6.958~ US$ 0,00
StandardStorage ~4 GB—US$ 0,09
Transferência saída25,6 GBUS$ 0,00 (free tier)
Cost Explorer, dimensão OPERATION, 2026-10-01 → 2026-10-04. S3 total US$ 4,23 — 84% da conta AWS do mês até então.

O que o bucket tinha

  • 26 prefixos de app (cleat, cardápio, OpenDrive, PratoAI, Purple Stock…)
  • cleat_deploy sozinho: ~5.664 objetos, ~133 WAL/hora
  • 6.020 objetos atuais · 62 MB
  • 51.410 versões + 45.398 delete markers · 288 MB
  • Fatura via TimedStorage ~4 GB — versões mortas, não payload vivo

O que não era

  • Cardápio prod: 56 arquivos, 7 MB
  • Loja / Catálogo: 77 arquivos, 4 MB
  • Purplestock prod: 52 arquivos, 5,5 MB
  • GET de imagem e presign quase não aparecem na fatura
  • 24/set (136 GB de GET, US$ 4,25) é outro evento, antes do bucket Litestream

360 mil LIST/dia × US$ 0,005 / mil = US$ 1,80/dia. Em 30 dias, US$ 54 só para o sidecar perguntar “tem arquivo aí?”.

02 O que foi feito

Decisão: o workload é LIST-pesado. No Backblaze B2, desde 1º de maio de 2026, Class A/B/C (PUT/LIST/GET) é grátis no pay-as-you-go. Storage US$ 6,95/TB, 10 GB free. A conta B2 da casa já existia (OpenDrive / rclone b2stash). Bucket próprio — não misturar réplica de SQLite com arquivo de cliente.

Estado depois

PeçaAntesDepois
Réplica Litestream S3 cleat-litestream-840298254452 B2 cleat-litestream-phoenix
Endpoint s3.us-east-1.amazonaws.com s3.us-east-005.backblazeb2.com · force-path-style
Versioning ligado no create desligado (default B2)
Custo de LIST US$ 0,005 / mil US$ 0
Apps (cardápio, loja…) Continuam no S3 de mídia. Fora deste incidente.

Rollback no host: /etc/cleat_deploy/litestream.yml.bak-s3-* e litestream.env.bak-s3-*. O exemplo em cleat-web/deploy/litestream.yml.example agora documenta B2 e o custo de LIST no S3.

03 Post-mortem

Causa raiz

Um sidecar único replicando 27 SQLite para S3 com sync de 1 segundo, num bucket versionado. A API ListBucket é Tier-1. Retention do Litestream apaga WAL; o versioning do S3 transforma isso em dezenas de milhares de versões e delete markers. A fatura cobra a pergunta, não o arquivo.

Cinco porquês

  1. Por que a fatura S3 subiu? Porque ListBucket foi a ~360 mil/dia a partir de 29/set.
  2. Por que tantos LIST? Porque o Litestream 0.5 lista gerações/WAL a cada sync, em 27 bancos.
  3. Por que no S3? Porque o replica remoto foi apontado para um bucket AWS no cutover de 29/set, sem modelo de custo de request.
  4. Por que o storage também inchou? Porque versioning foi ligado no create; delete de WAL virou versão morta.
  5. Por que ninguém viu no dia 1? Não havia alarme de Requests-Tier1 / ListBucket. O sinal foi a fatura humana.

ok O que funcionou

  • Cost Explorer por OPERATION isolou ListBucket numa query
  • Série diária casou o pico com o aniversário do bucket
  • Conta B2 e rclone b2stash já no host
  • Apps continuaram no ar durante o restart do sidecar
  • S3 antigo congelou 14s antes do cutover — rollback possível, depois esvaziado

gap O que falhou

  • Dez DBs precisaram de litestream reset (LTX local vs replica nova)
  • Erro 500 B2 num UploadPart do cleat.db no restart — recuperou no reset
  • IAM uploader não põe lifecycle; esvaziar versões foi script, não política
  • Zero alerta de request rate; descoberta foi “a fatura está feia”
  • O YAML de exemplo ainda vendia S3 como destino óbvio

O que não fazer de novo

Ações

AçãoDonoEstado
Réplicas no B2 cleat-litestream-phoenixinfra / cx33feito
Esvaziar S3 antigo (96.946 versões)infrafeito
Documentar B2 no litestream.yml.examplecleat-webfeito
Alarme Cost Explorer / budget em S3 Requests-Tier1infraaberto
Apagar o bucket S3 vazio quando não precisar mais do nomeinfraopcional
Subir sync-interval mesmo no B2 (ruído / ToS)cleat-litestreamaberto

Lição numa linha: backup contínuo de SQLite é um gerador de LIST. Escolha um store que não cobre pergunta — ou pergunte bem menos.