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-inapp.storage.backfill.enabled=true,@Order(-100)— roda antes doStartupRecoveryService): idempotente, isola falha por linha. - A coluna
arquivo_bytesnão é dropada — o "cleanup" do modoGARAGEé zerar em runtime (reversível: voltar paraPSQL_GARAGEre-popula no backfill). - AWS SDK ≥2.30 liga flexible checksums que o Garage rejeita → o
S3ClientFactoryusaWHEN_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 modoPSQL_GARAGEmitiga durante a transição. - Migração irreversível por engano → mitigado:
arquivo_bytesnão é dropada; o modoGARAGEsó zera em runtime. - Checksum flexível do AWS SDK rejeitado pelo Garage →
WHEN_REQUIREDnoS3ClientFactory(etapa 08).
Correção 2026-09-14: o ponteiro para o
CLAUDE.md/HOWTO-DEPLOY.mdpassou para a página dona do fato; os dois arquivos deixaram de carregar esse conteúdo.