Pular para conteúdo

Etapa 04 — Object storage do XLS recebido (Garage)

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-06

Estende a recepção da etapa 01: onde o binário do .xlsx é gravado e lido.

1. Contexto e escopo

O binário do .xlsx recebido era guardado em BYTEA no Postgres, o que infla o banco com o volume de uploads. O objetivo é mover o binário para object storage S3-compatível (Garage) sem perder a rede de segurança durante a transição.

Dentro do escopo: seam de storage; 3 modos de operação; chave content-addressed; backfill no startup; tratamento de falha de I/O no upload.

Fora do escopo: CDN/cache de leitura; versionamento de objeto.

2. Componentes e modelo de dados

flowchart TB
    rec[receber upload] --> coord[ArmazenamentoArquivoService]
    coord -->|seam ArquivoXlsStorage| s3[S3ArquivoXlsStorage<br/>AWS SDK v2]
    s3 --> factory[S3ClientFactory]
    factory --> garage[(Garage bucket: onpetro)]
    coord -.->|modo PSQL / PSQL_GARAGE| bytea[(xls_processamento.arquivo_bytes)]

Único ponto que conhece o modo: service/ArmazenamentoArquivoService. O seam é storage/ArquivoXlsStorage (impl S3ArquivoXlsStorage, cliente via S3ClientFactory). As referências do objeto vivem em xls_processamento (V11): objeto_bucket (VARCHAR(63)), objeto_chave (VARCHAR(512)), objeto_tamanho_bytes (BIGINT); o binário no Postgres fica em arquivo_bytes (BYTEA). Chave content-addressed {aaaa}/{mm}/{checksum_sha256}.xlsx, particionada por periodoInicial quando disponível, senão por recebidoEm.toLocalDate() (no receber() ainda não há período — só após o parse async).

3. Fluxos e estados

app.storage.modo / env S3_FILE_STORAGE (sempre em MAIÚSCULAS; enum storage/ArmazenamentoModo):

Modo Grava Lê (processamento) Backfill no startup
PSQL só Postgres (arquivo_bytes) Postgres no-op
PSQL_GARAGE (default) Postgres e Garage Garage copia legados p/ Garage, mantém arquivo_bytes
GARAGE só Garage Garage migra legados e zera arquivo_bytes
  • Falha de I/O no upload → StorageIndisponivelException → HTTP 503 (sem linha persistida; o cliente reenvia).
  • Backfill (StorageBackfillService, opt-in app.storage.backfill.enabled=true, @Order(-100) — roda antes do StartupRecoveryService): idempotente, isola falha por linha.
  • A coluna arquivo_bytes não é dropada — o "cleanup" do modo GARAGE é zerar em runtime (reversível: voltar para PSQL_GARAGE re-popula no backfill).
  • AWS SDK ≥2.30 liga flexible checksums que o Garage rejeita → o S3ClientFactory usa WHEN_REQUIRED (ver etapa 08).

4. Configuração

Env Property Default
S3_FILE_STORAGE app.storage.modo PSQL_GARAGE
— app.storage.backfill.enabled false
GARAGE_ENDPOINT garage.endpoint http://localhost:3900
GARAGE_REGION garage.region garage
GARAGE_ACCESS_KEY / GARAGE_SECRET_KEY garage.access-key/garage.secret-key dev keys
GARAGE_BUCKET garage.bucket onpetro

Bucket de produção: onpetro. Endpoint compartilhado: arquivo-api.xadm.biz (provisionado no Coolify). Em testes que não exercitam storage, fixar app.storage.modo=PSQL evita depender do Garage (GarageTestResource sobe um container dxflrs/garage singleton por JVM quando necessário). Envs completas: o runbook de deploy (variáveis do Garage).

5. Decisões

Nº Decisão
0005 Object storage Garage com 3 modos
0006 Checksum SHA-256 UNIQUE (identidade do upload)

6. Riscos

  • Garage indisponível no modo GARAGE → uploads recusados (503) até voltar; o modo PSQL_GARAGE mitiga durante a transição.
  • Migração irreversível por engano → mitigado: arquivo_bytes não é dropada; o modo GARAGE só zera em runtime.
  • Checksum flexível do AWS SDK rejeitado pelo Garage → WHEN_REQUIRED no S3ClientFactory (etapa 08).

Correção 2026-09-14: o ponteiro para o CLAUDE.md/HOWTO-DEPLOY.md passou para a página dona do fato; os dois arquivos deixaram de carregar esse conteúdo.