Arquitetura de Dados

Arquitetura de Banco de Dados

Um guia completo e agnóstico de fornecedor: modelagem, indexação, transações, alta disponibilidade, escalabilidade e segurança — os fundamentos que sustentam qualquer banco de dados, relacional ou não-relacional. Cada tópico traz teoria, boas práticas e links para referências de qualidade. Estudos aprofundados de bancos específicos (relacionais e não-relacionais) vivem em páginas próprias, ligadas a partir daqui conforme forem publicadas.

36 tópicos
Conceitos + boas práticas
Agnóstico de fornecedor
Recursos Gerais
01Comece pelos fundamentos (Fase 1) antes de pular para escalabilidade e alta disponibilidade — os trade-offs avançados só fazem sentido com a base sólida.
02Nenhum tópico aqui é específico de um produto — os conceitos se aplicam tanto a bancos relacionais quanto não-relacionais, com peso diferente em cada família.
03Segurança (Fase 7) não é opcional nem "para depois" — trate como parte do desenho da arquitetura desde o primeiro rascunho do schema.
04Leia "Designing Data-Intensive Applications" na ordem: é a referência mais completa e atualizada sobre os temas cobertos neste guia.
Bancos de Dados Específicos
01Os fundamentos acima são a base teórica; os estudos de bancos específicos aplicam esses conceitos em um produto real, com exemplos práticos e particularidades de implementação.
01

Fundamentos

O vocabulário e os trade-offs que sustentam todo o resto

O que é um SGBDBásico
As camadas internas de um sistema gerenciador de banco de dados

Um Sistema Gerenciador de Banco de Dados (SGBD) é o software que fica entre a aplicação e os dados em disco, garantindo que leituras e escritas sejam corretas, duráveis e (na maioria dos casos) simultâneas para múltiplos clientes. Entender suas camadas internas ajuda a raciocinar sobre qualquer decisão de arquitetura mais adiante.

TeoriaPor baixo de qualquer SGBD existem camadas reconhecíveis: um storage engine que decide como as páginas de dados são organizadas em disco (row-oriented ou column-oriented); um buffer manager que mantém páginas quentes em memória para evitar I/O de disco a cada leitura; um write-ahead log (WAL) que registra toda mudança antes de aplicá-la nas páginas de dados, garantindo durabilidade mesmo após uma queda de energia; e um query processor que traduz uma consulta declarativa em um plano de execução concreto. Essas camadas existem tanto em bancos relacionais quanto em bancos de documentos, chave-valor ou colunares — o que muda é como cada uma é implementada.
Modelos de dadosBásico
Relacional, documento, chave-valor, colunar e grafo

Não existe um "melhor" modelo de dados — existe o modelo mais adequado ao formato dos dados e ao padrão de acesso da aplicação. Conhecer as cinco famílias principais evita forçar um problema no modelo errado.

TeoriaO modelo relacional organiza dados em tabelas com schema fixo e relacionamentos explícitos via chaves — forte em integridade e consultas ad-hoc complexas (joins). O modelo de documento guarda registros semi-estruturados (tipicamente JSON/BSON) que agregam dados relacionados num único objeto, reduzindo a necessidade de joins para o padrão de acesso mais comum. O modelo chave-valor é o mais simples: uma chave aponta para um valor opaco, otimizado para leitura e escrita de latência mínima. O modelo colunar guarda cada coluna contígua em disco, ideal para agregações sobre poucas colunas em muitas linhas (analytics). O modelo de grafo representa entidades como nós e relacionamentos como arestas de primeira classe, brilhando em consultas de múltiplos saltos (redes sociais, detecção de fraude, recomendação).
💡Regra prática: comece perguntando "como os dados serão lidos?", não "como os dados se parecem?" — o padrão de acesso deveria guiar a escolha do modelo, não a estrutura natural do dado.
ACID vs BASEBásico
Duas filosofias de garantia de consistência

ACID e BASE são dois conjuntos de garantias opostos que um banco pode oferecer. Nenhum dos dois é "certo" — são pontos diferentes no mesmo espectro entre consistência imediata e disponibilidade/desempenho sob carga distribuída.

TeoriaACID (Atomicidade, Consistência, Isolamento, Durabilidade) é o conjunto clássico de garantias transacionais: uma transação acontece por inteiro ou não acontece, o banco nunca fica num estado inválido, transações concorrentes não interferem uma na outra, e uma vez confirmada, a mudança sobrevive a falhas. BASE (Basically Available, Soft state, Eventually consistent) é o modelo tipicamente adotado por bancos distribuídos que priorizam disponibilidade: o sistema aceita escritas mesmo sob partição de rede, o estado pode estar temporariamente inconsistente entre réplicas, e a consistência é alcançada eventualmente, não imediatamente. A escolha entre os dois é, no fundo, uma escolha sobre qual dos lados do teorema CAP (próximo tópico) o sistema prioriza.
Teorema CAPIntermediário
Consistência, Disponibilidade e Tolerância a Partição

O teorema CAP diz que um sistema distribuído não pode garantir simultaneamente Consistência, Disponibilidade e Tolerância a Partição de rede — quando a rede falha (e ela vai falhar), é preciso escolher entre consistência e disponibilidade.

TeoriaComo partições de rede eventualmente acontecem em qualquer sistema distribuído real, a escolha prática é entre CP (o sistema rejeita requisições em vez de arriscar inconsistência durante a partição) e AP (o sistema continua respondendo, aceitando que réplicas isoladas possam divergir temporariamente). É importante notar que o CAP se aplica apenas durante uma partição — fora desse cenário, um sistema bem projetado busca as três propriedades ao máximo. Uma crítica comum e válida ao teorema é que ele simplifica demais: na prática, a decisão real do dia a dia é sobre latência vs consistência (o teorema PACELC, uma extensão do CAP), já que partições completas são raras, mas trade-offs de latência entre réplicas são constantes.
💡PACELC resume bem a decisão prática: Partição → escolha entre Availability e Consistency; Else (operação normal) → escolha entre Latency e Consistency.
02

Modelagem de Dados

Desenhar o schema para o problema certo

NormalizaçãoBásico
1FN, 2FN, 3FN — eliminar redundância

Normalização é o processo de organizar colunas e tabelas para reduzir redundância e evitar anomalias de atualização — quando o mesmo dado precisa ser alterado em vários lugares ao mesmo tempo para continuar correto.

TeoriaCada forma normal resolve um tipo específico de anomalia. A 1ª Forma Normal (1FN) exige que cada célula guarde um valor atômico (sem listas ou grupos repetidos numa coluna). A 2ª Forma Normal (2FN) exige que todo atributo não-chave dependa da chave primária inteira, não de parte dela — relevante em chaves compostas. A 3ª Forma Normal (3FN) elimina dependências transitivas: um atributo não pode depender de outro atributo não-chave. Na prática, a maioria dos schemas transacionais para em 3FN, que já elimina a grande maioria das anomalias de atualização, inserção e exclusão sem exigir o rigor acadêmico de formas normais mais altas (BCNF, 4FN, 5FN).
sql
-- Não normalizado: nome e telefone do cliente repetidos em cada pedido
-- pedido(id, cliente_nome, cliente_telefone, produto, valor)

-- Normalizado (3FN): cliente e pedido separados, sem redundância
CREATE TABLE cliente (id INTEGER PRIMARY KEY, nome TEXT, telefone TEXT);
CREATE TABLE pedido (id INTEGER PRIMARY KEY, cliente_id INTEGER REFERENCES cliente(id), produto TEXT, valor NUMERIC);
Chaves e integridade referencialBásico
Chave primária, chave estrangeira, cardinalidade

Uma chave primária identifica cada registro de forma única; uma chave estrangeira referencia a chave primária de outra tabela, formando o relacionamento entre entidades e a base da integridade referencial.

TeoriaIntegridade referencial garante que uma chave estrangeira sempre aponte para um registro que realmente existe — o banco recusa inserir um pedido para um cliente inexistente, ou trata explicitamente o que acontece quando o cliente referenciado é apagado (CASCADE, RESTRICT, SET NULL). A cardinalidade de um relacionamento (1:1, 1:N, N:N) determina onde a chave estrangeira vive: numa relação 1:N a chave fica do lado "N"; numa relação N:N é preciso uma tabela de associação intermediária guardando um par de chaves estrangeiras. Escolher mal a cardinalidade no desenho inicial é uma das causas mais comuns de retrabalho de schema mais tarde.
Desnormalização deliberadaIntermediário
Quando duplicar dados é a decisão certa

Desnormalizar é aceitar redundância controlada em troca de menos joins e leituras mais rápidas. É uma escolha deliberada — nunca o resultado de simplesmente não ter modelado o schema.

TeoriaToda desnormalização troca custo de leitura por custo de escrita: duplicar um dado (por exemplo, guardar o nome do produto dentro do registro do pedido, além da tabela de produtos) evita um join na leitura, mas exige atualizar todas as cópias quando o dado original muda — ou aceitar que as cópias fiquem levemente desatualizadas. Essa troca só vale a pena quando o padrão de leitura é muito mais frequente que o de escrita, e quando o dado duplicado muda raramente (nome de produto muda pouco; estoque muda o tempo todo — bom candidato a não duplicar).
⚠️Desnormalizar sem um motivo de performance medido é a origem clássica de bugs de inconsistência. Normalize primeiro, meça, desnormalize pontos específicos depois.
Modelagem orientada a acessoIntermediário
Desenhar o schema a partir das queries, não do dado

Modelagem orientada a acesso (access pattern modeling) inverte a ordem tradicional: em vez de modelar entidades e depois escrever queries, você lista as queries que a aplicação vai fazer e desenha o schema para atendê-las bem.

TeoriaO processo típico tem três passos: (1) liste todas as operações de leitura e escrita que a aplicação precisa fazer, com sua frequência relativa; (2) para cada uma, identifique a chave de acesso — o que a query usa para filtrar ou ordenar; (3) desenhe o schema (tabelas, índices ou estrutura de documento) de forma que a query mais frequente seja a mais barata de executar, mesmo que isso signifique duplicar dados ou pré-computar agregações. Essa abordagem é essencial em bancos não-relacionais que não têm joins baratos, mas também melhora o desenho de schemas relacionais de alto volume — pensar nas queries cedo evita descobrir tarde demais que o schema exige um join caro no caminho mais quente da aplicação.
💡A modelagem é a decisão de performance mais barata e mais impactante de toda a arquitetura: quanto melhor o schema reflete o padrão de acesso real, mais rápidas as consultas são por construção — sem precisar de índice extra, cache ou hardware mais forte para compensar um design ruim. Um índice conserta uma query lenta; uma boa modelagem evita que ela nasça lenta.
03

Indexação e Performance

Fazer o banco encontrar dados sem varrer tudo

Como funciona um índiceIntermediário
B-tree, a estrutura por trás da maioria dos índices

Um índice é uma estrutura de dados extra, guardada ao lado da tabela, que permite encontrar registros por um valor de coluna sem varrer a tabela inteira — na prática, o mesmo papel do índice remissivo de um livro.

TeoriaA estrutura mais comum é a B-tree (e sua variante B+tree): uma árvore balanceada onde cada nó guarda várias chaves ordenadas, mantendo a profundidade da árvore baixa mesmo com milhões de registros — em geral 3 ou 4 níveis bastam para bilhões de linhas. Isso torna busca, inserção e remoção operações de custo logarítmico. A alternativa é a hash table, mais rápida para igualdade exata mas incapaz de responder buscas por intervalo (BETWEEN, >) porque não preserva ordem. Todo índice tem um custo: cada escrita na tabela precisa também atualizar cada índice que cobre a coluna alterada — mais índices significa leituras mais rápidas e escritas mais lentas.
sql
-- Sem índice: varredura completa da tabela (custo O(n))
SELECT * FROM pedido WHERE cliente_id = 12345;

-- Com índice na coluna de busca: custo O(log n)
CREATE INDEX idx_pedido_cliente ON pedido (cliente_id);
Seletividade e cardinalidadeIntermediário
Por que a coluna certa importa mais do que só existir um índice

Nem toda coluna se beneficia de um índice. Quanto mais uma coluna diferencia linhas entre si (alta seletividade/cardinalidade), mais útil é indexá-la — colunas com poucos valores distintos raramente compensam o custo do índice.

TeoriaSeletividade é a proporção de linhas de uma tabela que uma condição de filtro retorna. Quanto menor essa proporção, mais valioso é o índice, porque o otimizador só prefere buscar pelo índice quando isso evita ler a maior parte da tabela; acima de um certo percentual de linhas retornadas — tipicamente entre 10% e 30%, variando de banco para banco — o otimizador costuma decidir que uma varredura sequencial é mais barata que milhares de buscas aleatórias via índice, mesmo que o índice exista e esteja saudável. Cardinalidade é o número de valores distintos numa coluna: uma coluna booleana ou um campo "status" com três estados tem cardinalidade baixíssima, e um índice só nela raramente ajuda sozinho; uma coluna como e-mail ou CPF tem cardinalidade quase igual ao número de linhas da tabela — candidata natural a índice.
💡Estatísticas desatualizadas do otimizador são uma causa comum e silenciosa de planos de execução ruins — rodar o comando de atualização de estatísticas do banco (ex.: ANALYZE) após grandes cargas de dados devolve ao otimizador uma visão real da distribuição de valores.
Planos de execução (EXPLAIN)Intermediário
Ler o que o banco realmente vai fazer antes de otimizar

O comando EXPLAIN (com variações de nome entre bancos) mostra o plano que o otimizador de query escolheu para executar uma consulta — a única forma confiável de saber se um índice está realmente sendo usado.

TeoriaO otimizador de query gera vários planos de execução candidatos para a mesma consulta (por exemplo, varredura completa vs. busca por índice, ou diferentes ordens de join) e escolhe um pelo custo estimado, baseado em estatísticas sobre o tamanho das tabelas e a distribuição dos valores. Ler um plano de execução significa identificar operações caras: uma varredura completa (full scan) onde se esperava uma busca por índice, um join que processa muito mais linhas que o esperado, ou uma ordenação que não conseguiu usar um índice já ordenado. A maioria dos bancos também oferece uma variante que executa a query de verdade e mostra custo estimado vs. custo real — a diferença entre os dois é o primeiro lugar a olhar quando as estatísticas do otimizador estão desatualizadas.
💡Nunca otimize uma query "no escuro" — meça primeiro com o plano de execução, mude uma coisa por vez, meça de novo.
Anti-patterns comuns de performanceIntermediário
N+1, SELECT *, paginação por OFFSET

A maior parte dos problemas de performance em produção não vem de configuração de servidor — vem de um punhado de padrões de acesso conhecidos e evitáveis desde o design.

Problema N+1: uma query para buscar N registros, seguida de N queries individuais para os dados relacionados de cada um — resolvido com JOIN ou carregamento em lote.
SELECT *: busca colunas que a aplicação não usa, aumenta I/O e impede covering indexes — sempre liste as colunas necessárias.
Paginação por OFFSET: fica progressivamente mais lenta em páginas altas, porque o banco ainda processa e descarta todas as linhas anteriores — prefira paginação por cursor/chave (keyset pagination).
Função sobre coluna indexada: um filtro como WHERE YEAR(data) = 2026 impede o uso do índice em data — reescreva como um intervalo (data >= '2026-01-01').
Índices compostos e covering indexAvançado
Ordem das colunas importa

Um índice composto guarda mais de uma coluna, na ordem em que foi definido — e essa ordem determina quais queries conseguem realmente usar o índice.

TeoriaUm índice composto em (a, b, c) funciona como um dicionário ordenado primeiro por a, depois por b dentro de cada valor de a, e assim por diante — por isso ele acelera buscas por a, por a+b ou por a+b+c, mas não uma busca só por b ou só por c. Um covering index é um índice que inclui todas as colunas que a query pede, permitindo que o banco responda inteiramente pelo índice, sem tocar a tabela — a otimização de leitura mais poderosa disponível, porque elimina o segundo acesso a disco (o chamado "table lookup").
⚠️Regra prática de ordenação: coloque primeiro as colunas usadas em igualdade (=), depois as usadas em intervalo (>, BETWEEN), e por último as usadas só em ORDER BY.
Índices parciais e funcionaisAvançado
Indexar só o que importa, ou o resultado de uma expressão

Além de indexar uma coluna inteira, é possível indexar apenas um subconjunto de linhas (índice parcial) ou o resultado de uma expressão computada sobre a coluna (índice funcional).

TeoriaUm índice parcial inclui uma condição de filtro na própria definição do índice, guardando entradas só para as linhas que satisfazem essa condição — por exemplo, indexar apenas pedidos com status "pendente" quando 95% dos pedidos já estão concluídos: o índice fica uma fração do tamanho e mais barato de manter, porque ignora as linhas que a aplicação quase nunca consulta. Um índice funcional (ou baseado em expressão) indexa o resultado de uma função aplicada à coluna, não a coluna crua — resolve exatamente o anti-pattern de "função sobre coluna indexada" mencionado anteriormente: se a aplicação sempre filtra por uma versão normalizada de um campo de texto, um índice funcional sobre essa expressão permite que a busca continue usando o índice em vez de cair numa varredura completa.
sql
-- Índice parcial: só cobre as linhas realmente consultadas com frequência
CREATE INDEX idx_pedido_pendente ON pedido (cliente_id) WHERE status = 'pendente';

-- Índice funcional: indexa o resultado da expressão, não a coluna crua
CREATE INDEX idx_usuario_email_lower ON usuario (LOWER(email));
Tipos de índice especializadosAvançado
Hash, bitmap, texto completo e espacial — além da B-tree

A B-tree cobre a maioria dos casos de uso, mas alguns padrões de consulta têm estruturas de índice dedicadas, mais eficientes para o problema específico que resolvem.

TeoriaÍndice hash acelera igualdade exata a custo praticamente constante, mas não serve para buscas por intervalo — útil quando toda consulta relevante à coluna é por igualdade. Índice bitmap representa cada valor distinto como um vetor de bits, um bit por linha, e é eficiente para colunas de baixíssima cardinalidade combinadas com múltiplos filtros por AND/OR — comum em cargas analíticas, raro em bancos transacionais de alta escrita, porque atualizar um bitmap é caro. Índice de texto completo (full-text) tokeniza o conteúdo textual e mantém uma estrutura invertida (palavra → lista de registros que a contêm), permitindo busca por relevância em vez de correspondência exata de substring. Índice espacial (como R-tree) organiza dados geométricos por proximidade, acelerando consultas do tipo "o que está dentro deste raio" — impossível de responder eficientemente com uma B-tree comum, que só entende ordem linear, não posição no espaço.
Manutenção e custo de índicesAvançado
Todo índice tem um dono e um custo contínuo

Índices não são "configure e esqueça": eles fragmentam com o tempo, ocupam espaço em disco e desaceleram toda escrita na tabela que cobrem.

TeoriaCada INSERT, UPDATE ou DELETE precisa atualizar não só a tabela, mas cada índice que cobre uma coluna alterada — esse custo extra, chamado write amplification, cresce linearmente com o número de índices na tabela. Índices também fragmentam ao longo do tempo, especialmente após muitas exclusões e atualizações, degradando a eficiência da B-tree até uma reconstrução periódica ser necessária. Um problema comum e silencioso é o "índice não usado": criado para uma query que não existe mais, ou redundante com outro índice composto que já cobre o mesmo caso — sem revisão periódica das estatísticas de uso de índice, esses índices acumulam custo de escrita sem nenhum benefício de leitura em troca.
⚠️Antes de adicionar um índice "só por garantia", meça: um índice não usado é puro custo. A maioria dos bancos expõe estatísticas de uso de índice — revisá-las periodicamente é tão importante quanto criar os índices certos.
04

Transações e Consistência

O que acontece quando dois clientes escrevem ao mesmo tempo

Propriedades ACID na práticaIntermediário
O que cada letra garante de verdade

Uma transação agrupa várias operações num bloco que deve ser tratado como uma unidade única — ou tudo é aplicado, ou nada é.

TeoriaAtomicidade garante que uma transação com múltiplas escritas nunca fique "pela metade" visível para outros — se uma parte falha, tudo é desfeito (rollback). Consistência aqui significa que a transação leva o banco de um estado válido para outro estado válido, respeitando toda regra de integridade declarada (chaves, constraints). Isolamento controla o quanto transações concorrentes enxergam umas das outras antes de confirmar (tema do próximo tópico). Durabilidade garante que, uma vez confirmada (COMMIT), a mudança sobrevive a uma queda do processo ou do servidor — tipicamente porque já foi escrita no write-ahead log antes da confirmação retornar para o cliente.
sql
BEGIN;
UPDATE conta SET saldo = saldo - 100 WHERE id = 1;
UPDATE conta SET saldo = saldo + 100 WHERE id = 2;
-- se qualquer UPDATE falhar, nenhum dos dois é aplicado
COMMIT;
Níveis de isolamentoAvançado
Dirty read, non-repeatable read, phantom read

O nível de isolamento define quanto uma transação pode "ver" do trabalho não confirmado de outra — quanto mais isolamento, mais correção, mas menos concorrência e desempenho.

TeoriaDo mais fraco ao mais forte: Read Uncommitted permite dirty reads (ler dado ainda não confirmado, que pode ser desfeito). Read Committed — o padrão na maioria dos bancos — só lê dado já confirmado, mas permite non-repeatable reads (ler o mesmo registro duas vezes na mesma transação e obter valores diferentes, porque outra transação confirmou uma mudança no meio do caminho). Repeatable Read garante que releituras do mesmo registro sejam sempre iguais, mas ainda permite phantom reads (uma segunda execução da mesma query de intervalo retorna linhas novas inseridas por outra transação). Serializable é o mais forte: o resultado é sempre equivalente a alguma execução serial das transações, uma de cada vez — elimina todos os fenômenos, ao custo de mais bloqueio ou mais abortos de transação por conflito.
sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- operações que exigem a garantia mais forte de isolamento
COMMIT;
Locking vs MVCCAvançado
Duas formas de controlar concorrência

Controle de concorrência é o mecanismo interno que implementa os níveis de isolamento. As duas abordagens dominantes são bloqueio pessimista e controle multiversão (MVCC).

TeoriaNo locking pessimista, uma transação adquire um bloqueio (lock) sobre o dado antes de lê-lo ou escrevê-lo, forçando outras transações a esperar — simples de raciocinar, mas reduz concorrência e pode causar deadlocks. O MVCC (Multiversion Concurrency Control), usado pela maioria dos bancos modernos, mantém múltiplas versões de cada registro: uma leitura nunca bloqueia uma escrita e vice-versa, porque cada transação enxerga uma "fotografia" consistente dos dados no momento em que começou, mesmo que outras transações estejam escrevendo simultaneamente. O custo do MVCC é o acúmulo de versões antigas, que precisam ser recolhidas periodicamente (processo de garbage collection/vacuum) para não inflar o armazenamento indefinidamente.
🔒Deadlock: ocorre quando duas transações esperam, cada uma, por um lock que a outra já segura. A maioria dos bancos detecta o ciclo automaticamente e aborta uma das transações — a aplicação deve estar preparada para repetir a operação.
Consistência forte vs eventualAvançado
A decisão central de qualquer sistema distribuído

Quando dados são replicados entre múltiplos nós, é preciso decidir se toda leitura reflete a escrita mais recente (consistência forte) ou se réplicas podem ficar temporariamente desatualizadas (consistência eventual).

TeoriaA consistência forte (ou linearizabilidade) garante que, uma vez que uma escrita é confirmada, qualquer leitura subsequente — em qualquer réplica — reflete essa escrita ou uma mais recente; isso normalmente exige coordenação síncrona entre réplicas, aumentando latência. A consistência eventual permite que uma leitura logo após a escrita retorne um valor desatualizado, com a garantia de que, se não houver novas escritas, todas as réplicas eventualmente convergem para o mesmo valor. Entre esses dois extremos existem modelos intermediários (consistência causal, "read-your-writes", "monotonic reads") que dão garantias mais fortes que eventual sem pagar o custo total da consistência forte — escolher o modelo certo por caso de uso, em vez de aplicar o mesmo padrão a todo o sistema, é a marca de uma arquitetura distribuída madura.
05

Alta Disponibilidade

Continuar operando quando uma parte falha

Replicação líder-seguidorIntermediário
O modelo de replicação mais comum

Na replicação líder-seguidor (leader-follower, também chamada primário-réplica), todas as escritas passam por um único nó líder, que propaga as mudanças para um ou mais seguidores.

TeoriaA propagação pode ser síncrona (o líder espera confirmação de pelo menos um seguidor antes de confirmar a escrita para o cliente — mais seguro, mais lento) ou assíncrona (o líder confirma imediatamente e propaga em segundo plano — mais rápido, com risco de perda de dados se o líder cair antes de replicar). A maioria dos sistemas de produção usa um meio-termo: um seguidor síncrono ("semi-síncrono") para garantir que ao menos uma cópia extra sempre exista, com os demais seguidores assíncronos para escalar leituras sem impactar a latência de escrita.
RPO, RTO e estratégias de backupIntermediário
Definir metas antes de desenhar o backup

Antes de escolher uma estratégia de backup, é preciso definir quanto dado a empresa tolera perder e quanto tempo tolera ficar fora do ar — sem essas duas metas, qualquer estratégia é um chute.

TeoriaRPO (Recovery Point Objective) é a quantidade máxima de dados aceitável perder, medida em tempo — um RPO de 15 minutos significa que, na pior falha, no máximo 15 minutos de escritas podem ser perdidos. RTO (Recovery Time Objective) é o tempo máximo aceitável até o sistema voltar a operar após uma falha. Backups completos copiam todo o banco periodicamente; backups incrementais copiam só o que mudou desde o último backup, reduzindo custo mas complicando a restauração (é preciso reaplicar a cadeia inteira). O Point-in-Time Recovery (PITR) combina um backup completo com o replay do write-ahead log, permitindo restaurar o banco para qualquer segundo específico — a técnica que normalmente entrega o melhor RPO.
Replicação multi-líder e sem líderAvançado
Alternativas quando um único líder não basta

Quando um único líder vira gargalo geográfico ou de escrita, existem duas alternativas — cada uma trocando simplicidade por mais disponibilidade de escrita.

TeoriaNa replicação multi-líder, mais de um nó aceita escritas (tipicamente um líder por região geográfica, para reduzir latência local), e o sistema precisa resolver conflitos quando o mesmo dado é alterado em dois líderes ao mesmo tempo — usando estratégias como "o último a escrever vence" (com risco de perda silenciosa de dados) ou merge automático baseado em tipos de dados especiais (CRDTs). Na replicação sem líder (leaderless), qualquer nó aceita leituras e escritas, e a consistência é garantida por quóruns: uma escrita é considerada bem-sucedida se confirmada por W de N réplicas, e uma leitura consulta R réplicas — escolhendo W e R de forma que W + R > N, garante-se que toda leitura enxergue ao menos uma cópia da escrita mais recente.
Failover e split-brainAvançado
Trocar de líder sem causar mais dano que a falha

Failover é o processo de promover um seguidor a novo líder quando o líder atual falha. Parece simples, mas é uma das operações mais arriscadas de um sistema distribuído.

TeoriaUm failover mal coordenado pode causar split-brain: o líder antigo, que na verdade só ficou temporariamente inacessível (por exemplo, uma partição de rede), volta a responder como líder ao mesmo tempo que o novo líder já está aceitando escritas — dois líderes ativos, divergindo silenciosamente. Sistemas de produção evitam isso com mecanismos de consenso (algoritmos como Raft ou Paxos) que exigem maioria de votos antes de eleger um novo líder, e com "fencing tokens" que invalidam qualquer operação vinda do líder antigo assim que um novo é eleito. Failover automático também precisa de um tempo de detecção bem calibrado: rápido demais promove um novo líder por uma falha transitória (gerando churn); lento demais prolonga a indisponibilidade.
🔒Nunca projete failover automático sem um mecanismo de fencing — sem ele, split-brain é uma questão de quando, não de se.
06

Escalabilidade e Distribuição

Crescer além do que uma máquina aguenta

Escala vertical vs horizontalBásico
Máquina maior vs mais máquinas

Escalar verticalmente significa dar mais CPU, memória e I/O a um único servidor. Escalar horizontalmente significa distribuir carga entre múltiplos servidores. Um sempre tem um teto; o outro tem mais complexidade.

TeoriaEscala vertical é simples — sem mudança de arquitetura, sem coordenação distribuída — mas esbarra num teto físico e de custo: em algum ponto, dobrar a capacidade de uma única máquina passa a custar exponencialmente mais que linearmente, e sempre existe um limite físico de hardware. Escala horizontal remove esse teto ao custo de introduzir problemas que sistemas de um único nó não têm: consistência entre nós, roteamento de dados, rebalanceamento e failover parcial. A prática comum é escalar verticalmente primeiro — é a solução mais barata e simples — e só migrar para escala horizontal quando o teto vertical for de fato alcançado ou estiver visível no horizonte de capacidade.
Read replicas e roteamento de leituraIntermediário
Escalar leitura sem escalar escrita

Na maioria das aplicações, leituras superam escritas por uma ordem de grandeza ou mais — por isso escalar leitura com réplicas costuma ser o primeiro passo antes de qualquer sharding.

TeoriaComo réplicas de leitura recebem dados via replicação assíncrona (na maioria dos casos), existe um atraso — replication lag — entre uma escrita no líder e sua visibilidade nas réplicas. A aplicação precisa decidir, por rota de leitura, se essa defasagem é aceitável. Um padrão comum é rotear leituras "read-your-writes" (o usuário lendo o próprio dado logo após escrevê-lo, como um perfil que acabou de editar) para o líder ou para uma réplica marcada como atualizada, e mandar todo o resto — dashboards, listagens, relatórios — para réplicas, tolerando alguns segundos de atraso.
CacheIntermediário
Cache-aside, write-through e invalidação

Um cache guarda resultados já computados em memória, na frente do banco, para responder às consultas mais frequentes sem tocar o disco. O padrão de invalidação escolhido é o que determina a corretude do sistema.

TeoriaNo padrão cache-aside (o mais comum), a aplicação lê primeiro do cache; se ausente (cache miss), lê do banco e grava o resultado no cache antes de retornar. No padrão write-through, toda escrita vai simultaneamente para o cache e para o banco, mantendo os dois sempre sincronizados ao custo de latência extra na escrita. A parte mais difícil de qualquer estratégia de cache não é preencher — é invalidar corretamente quando o dado de origem muda; cache desatualizado sem invalidação é uma das fontes mais comuns e mais sutis de bugs de inconsistência em produção.
Sharding e particionamentoAvançado
Dividir dados dentro de um nó (particionamento) ou entre vários (sharding)

Particionamento é o conceito geral: dividir os dados de uma tabela grande em partes menores e mais gerenciáveis, chamadas partições. Sharding é o nome dado especificamente ao particionamento distribuído — quando cada partição vive num nó físico diferente. Todo sharding é particionamento, mas nem todo particionamento é sharding.

TeoriaO particionamento local mantém todos os dados no mesmo servidor, apenas organizados fisicamente em partições separadas — por exemplo, uma tabela de eventos particionada por mês. Isso traz dois ganhos mesmo sem distribuir nada: o otimizador consegue ignorar partições inteiras que não correspondem ao filtro da query (partition pruning), e apagar dados antigos vira a operação instantânea de remover uma partição inteira, em vez de um DELETE lento linha a linha. O sharding (particionamento distribuído) vai além: cada partição é hospedada num nó físico diferente, permitindo que o volume de dados e a carga de escrita cresçam além do que uma única máquina suportaria — ao custo de exigir coordenação entre nós para qualquer consulta que precise tocar mais de um shard, e de tornar joins entre shards caros ou impraticáveis.
TeoriaQuando o particionamento é distribuído (sharding), a escolha da chave de partição (shard key) é a decisão mais importante e mais difícil de reverter depois: ela determina em qual shard cada registro vive. Particionamento por intervalo (range) mantém chaves próximas no mesmo shard — bom para varreduras por intervalo, mas propenso a "hot spots" quando o padrão de escrita segue uma ordem (por exemplo, timestamps crescentes concentram toda escrita recente num único shard). Particionamento por hash distribui as chaves uniformemente, evitando hot spots, mas sacrifica a eficiência de varreduras por intervalo, já que registros próximos ficam espalhados por shards diferentes. Rebalancear shards à medida que os dados crescem — sem interromper o sistema — é um dos problemas mais difíceis de engenharia num banco distribuído.
⚠️Escolher uma chave de partição de baixa cardinalidade (poucos valores distintos, como "país" numa base concentrada em um país) é a causa mais comum de hot spots em produção — vale tanto para particionamento local quanto para sharding.
07

Segurança

Parte do desenho da arquitetura, não uma etapa final

Prevenção de injeção de códigoBásico
Queries parametrizadas, não concatenação

Injeção de código acontece quando entrada do usuário é inserida diretamente numa query, permitindo que o atacante altere a estrutura da consulta em vez de apenas fornecer um valor.

TeoriaA defesa correta é sempre usar queries parametrizadas (prepared statements): o valor da entrada é enviado ao banco separadamente do texto da query, então o banco nunca o interpreta como parte da sintaxe — independente de quão "bem sanitizada" a string pareça, concatenar entrada de usuário em texto de query é uma prática insegura por construção, não apenas por descuido. Esse mesmo princípio se aplica além de bancos relacionais: qualquer linguagem de consulta que aceite entrada dinâmica (incluindo filtros de bancos de documentos e comandos de bancos chave-valor) tem sua própria superfície de risco de injeção quando a entrada não é tratada como dado.
sql
-- Inseguro: concatenação de entrada do usuário na query
query = "SELECT * FROM usuario WHERE email = '" + entrada + "'"

-- Seguro: query parametrizada — o valor nunca é interpretado como SQL
query = "SELECT * FROM usuario WHERE email = ?"
executar(query, [entrada])
Autenticação e autorizaçãoIntermediário
Quem é você, e o que você pode fazer

Autenticação prova quem está se conectando; autorização decide o que essa identidade pode fazer. São camadas distintas e ambas precisam de desenho deliberado desde o primeiro schema de permissões.

TeoriaO modelo mais usado para autorização é RBAC (Role-Based Access Control): permissões são atribuídas a papéis (roles), e usuários recebem papéis — em vez de conceder permissões individualmente a cada usuário, o que não escala e é difícil de auditar. O princípio orientador é o privilégio mínimo: cada aplicação ou pessoa recebe exatamente as permissões necessárias para sua função, nunca mais. Na prática isso significa credenciais de aplicação separadas por serviço (nunca uma única credencial "admin" compartilhada), permissões de escrita separadas de permissões de leitura, e revisão periódica de quem tem acesso a quê.
sql
CREATE ROLE app_leitura;
GRANT SELECT ON pedido, cliente TO app_leitura;

CREATE ROLE app_escrita;
GRANT SELECT, INSERT, UPDATE ON pedido TO app_escrita;
-- app_escrita não recebe DELETE nem acesso a outras tabelas
Criptografia em repouso e em trânsitoIntermediário
Proteger dados parados e dados em movimento

Criptografia em repouso protege dados armazenados em disco caso o meio físico seja comprometido; criptografia em trânsito protege dados enquanto trafegam entre a aplicação e o banco.

TeoriaCriptografia em repouso normalmente acontece em nível de disco/volume (transparente para a aplicação) ou em nível de coluna, para dados especialmente sensíveis (documentos, dados de pagamento), onde só a aplicação com a chave certa consegue decifrar o valor. Criptografia em trânsito usa TLS na conexão entre cliente e banco, evitando que a rede intermediária consiga ler ou adulterar o tráfego — deveria ser obrigatória mesmo em redes internas, já que "rede interna confiável" é uma suposição frágil. A gestão de chaves é a parte mais crítica: chaves de criptografia nunca deveriam viver no mesmo lugar que os dados que protegem, nem no código-fonte da aplicação.
Auditoria e isolamento de redeAvançado
Saber quem acessou o quê, e limitar quem pode chegar perto

Duas camadas complementares de segurança: registrar quem fez o quê (para investigar depois) e restringir quem consegue sequer se conectar (para reduzir a superfície de ataque antes de qualquer investigação ser necessária).

TeoriaAuditoria registra de forma imutável quem acessou ou alterou quais dados e quando — essencial tanto para investigar incidentes quanto para atender exigências de conformidade regulatória. Logs de auditoria devem ficar fora do alcance de quem está sendo auditado, para que um acesso indevido não consiga também apagar seu próprio rastro. Isolamento de rede significa que o banco nunca deveria estar diretamente acessível pela internet pública: ele vive numa rede privada, acessível apenas pela aplicação (e por um bastion host controlado para acesso administrativo), com regras de firewall explícitas por origem — reduzindo drasticamente a superfície de ataque antes mesmo de qualquer credencial entrar em jogo.
🔒Um banco de produção exposto diretamente à internet, mesmo com senha forte, é uma das configurações de maior risco em qualquer arquitetura — a exposição de rede deveria ser a primeira linha de defesa, não a última.
08

Operação e Observabilidade

Manter tudo saudável depois de colocar em produção

Métricas essenciais de observabilidadeIntermediário
O que monitorar antes que vire incidente

Um pequeno conjunto de métricas, observado continuamente, detecta a maioria dos problemas antes que virem indisponibilidade — o objetivo é alertar cedo, não só diagnosticar depois do fato.

Latência de query (p50/p95/p99) — médias escondem os piores casos, que são os que afetam usuários reais.
Replication lag — segundos de atraso entre líder e réplicas; crescente indica réplica sobrecarregada.
Conexões ativas vs. limite máximo — esgotar conexões derruba a aplicação inteira, não só uma query.
Cache hit ratio do buffer de páginas — queda súbita geralmente indica um working set que não cabe mais em memória.
Taxa de deadlocks e locks em espera — sinal precoce de contenção antes que vire timeout visível ao usuário.
Connection poolingIntermediário
Por que abrir uma conexão nova a cada query é caro

Abrir e fechar uma conexão com o banco tem um custo real de rede e de processo — um pool de conexões mantém um conjunto de conexões já estabelecidas e as reutiliza entre requisições.

TeoriaCada conexão nova envolve handshake de rede, autenticação e, em muitos bancos, alocação de um processo ou thread dedicado no servidor — sob carga alta, abrir uma conexão por requisição pode facilmente se tornar o próprio gargalo, além de esgotar o limite máximo de conexões simultâneas do servidor. Um pool de conexões mantém um número fixo (ou elástico dentro de limites) de conexões abertas, entregando uma delas à aplicação por requisição e devolvendo-a ao pool ao final, em vez de destruí-la. Dimensionar o pool corretamente é um equilíbrio: pequeno demais gera fila de espera por conexão; grande demais pode esgotar o limite de conexões do servidor de banco, especialmente quando múltiplas instâncias da aplicação têm, cada uma, seu próprio pool.
Capacity planningAvançado
Prever quando a infraestrutura atual vai faltar

Capacity planning é projetar, a partir de métricas de crescimento histórico, quando a infraestrutura atual deixará de suportar a carga — com folga suficiente para agir antes que isso vire incidente.

TeoriaA prática mais confiável combina duas visões: crescimento orgânico (extrapolar a curva histórica de uso de CPU, armazenamento e conexões) e eventos previsíveis de pico (uma campanha de marketing, um lançamento, um pico sazonal conhecido do negócio) que a extrapolação histórica sozinha não captura. Um bom plano de capacidade define um limiar de ação — por exemplo, "iniciar o processo de scale-up quando o uso de armazenamento atingir 70%" — bem antes do limite físico, porque operações como aumentar armazenamento, adicionar réplicas ou fazer resharding levam tempo e não deveriam ser feitas sob pressão de uma quase-indisponibilidade.
Runbooks e resposta a incidentesAvançado
Decidir o processo antes do incidente, não durante

Um runbook é um procedimento documentado e testado para responder a um tipo específico de falha — escrito com calma antes do incidente, para ser seguido sob pressão durante ele.

TeoriaUm bom runbook cobre, no mínimo: como detectar o problema (qual alerta disparou, o que ele significa), os primeiros passos de diagnóstico (quais métricas e logs olhar primeiro), a ação de mitigação imediata (mesmo que não resolva a causa raiz — por exemplo, promover uma réplica enquanto se investiga o líder original) e os critérios para escalar para outra pessoa ou equipe. Runbooks só têm valor se forem testados periodicamente — via simulações programadas (game days) ou revisados a cada incidente real — porque um procedimento nunca exercitado tende a estar desatualizado ou incompleto exatamente quando mais se precisa dele.