System Design/03 - Qualidade/Performance Antipatterns2 min
Monolithic Persistence
- Que problema isso resolve?
- Quando usar / quando NÃO usar?
- Qual o principal trade-off?
- Como isso falha em produção?
Conceito
Usar um único banco para todos os tipos de dado do sistema, ignorando que perfis de acesso incompatíveis competem entre si pelo mesmo recurso.
Perfis que não deveriam compartilhar o mesmo banco
| Tipo de dado | Perfil | Banco adequado |
|---|---|---|
| Transacional | Escrita pequena, ACID | Relacional |
| Analítico | Varredura de milhões de linhas | Colunar (Redshift, ClickHouse) |
| Log / telemetria | Escrita massiva, leitura por tempo | Série temporal |
| Sessão / cache | Chave, TTL, altíssima frequência | Key-Value Store |
| Busca textual | Índice invertido, relevância | Elasticsearch, OpenSearch |
| Blob | Arquivos grandes | Armazenamento de objetos |
O caso mais destrutivo é analítico junto de transacional: um relatório que varre 50 milhões de linhas satura o buffer pool, expulsa as páginas quentes do transacional e degrada todo o sistema — durante os minutos em que roda.
A resposta é persistência poliglota: cada tipo de dado no armazenamento adequado, que é a mesma ideia de Federation aplicada por perfil em vez de por domínio.
Trade-offs
Poliglota custa caro: mais tecnologias para operar, mais expertise necessária, mais pontos de falha, e o fim das junções e transações entre os armazenamentos. Consistência entre eles passa a ser eventual, mantida por replicação ou eventos.
Por isso o antipadrão oposto é igualmente real — adotar cinco bancos por princípio, num sistema que um Postgres bem ajustado atenderia inteiro. Bancos relacionais modernos cobrem muito mais terreno do que a moda sugere: jsonb cobre documento, extensões cobrem série temporal e busca textual, e a operação continua sendo de um sistema só.
O critério prático é a dor medida. Separe quando um perfil estiver comprovadamente prejudicando outro — não antes. E, quando separar, comece pelo maior ofensor: normalmente é o analítico ou a telemetria, e mover apenas esse já resolve a maior parte do problema.
Vale lembrar que a primeira separação costuma ser a mais barata de todas e nem exige outro banco: mandar relatórios para uma réplica de leitura (Replication).
Exemplo prático
Um único Postgres guardando pedidos, sessões e 200 GB de eventos de telemetria.
O que acontece: a tabela de eventos domina o buffer pool; consultas de pedido passam a ler do disco; o VACUUM da tabela de eventos consome I/O em horário de pico; e o backup, que precisa cobrir tudo, leva horas.
A separação por ordem de retorno:
- Eventos para um banco de série temporal — resolve a maior parte, e a tabela era 90% do volume
- Sessões para Redis — elimina escrita de altíssima frequência e o job de expurgo
- Relatórios para uma réplica — tira a varredura do primário
Pedidos ficam onde estavam, num banco que voltou a caber em memória. Nenhuma linha de lógica de negócio mudou.
Relacionado
Parte de Performance Antipatterns · roadmap.sh/system-design