Cover do episódio 91: Por que fui aprender banco de dados do zero em plena carreira
#09122 de julho, 20194 min leituraBastidores do CódigoS3 · 2018–2019

Por que fui aprender banco de dados do zero em plena carreira

Sabia SQL desde a faculdade. Mas índice, vacuum, explain analyze, transaction isolation — aprendi porque precisei, não porque estudei. Aqui está o que a produção me ensinou.

PostgreSQLSQLPerformanceBackend

SQL eu sabia desde a faculdade. SELECT, INSERT, JOIN, GROUP BY. Funciona em qualquer entrevista.

Banco de dados de produção é diferente.

O momento que entendi isso foi quando um endpoint começou retornando em 8 segundos. Mesmo SQL. Mesma lógica. Mas com 50 mil registros em vez de 500, tudo mudou.


EXPLAIN ANALYZE mostra o que o banco está fazendo para executar sua query — o plano de execução.

EXPLAIN ANALYZE
SELECT * FROM leads 
WHERE email = '[email protected]' 
  AND status = 'ativo';

Saída (simplificada):

Seq Scan on leads  (cost=0.00..1840.00 rows=1) 
                   (actual time=1203.445..1203.447 rows=1 loops=1)
  Filter: (email = '[email protected]' AND status = 'ativo')
  Rows Removed by Filter: 49999

Seq Scan = o banco leu todos os 50.000 registros para encontrar um. Por isso 8 segundos.

Adicionei índice:

CREATE INDEX idx_leads_email ON leads(email);

Mesma query depois:

Index Scan using idx_leads_email on leads  (cost=0.29..8.31 rows=1)
                                           (actual time=0.034..0.035 rows=1)
  Index Cond: (email = '[email protected]')
  Filter: (status = 'ativo')

De 1.203ms para 0.034ms. Índice no campo certo, na coluna certa.


O que eu não sabia sobre índice

Índice tem custo de escrita. Cada INSERT/UPDATE/DELETE atualiza todos os índices da tabela. Índice demais em tabela com muita escrita = lento para escrever.

Índice não é usado se você faz função na coluna. WHERE LOWER(email) = '[email protected]' não usa índice em email. Precisa de índice funcional: CREATE INDEX idx_leads_email_lower ON leads(LOWER(email)).

Índice composto tem ordem. CREATE INDEX ON leads(status, email) é diferente de CREATE INDEX ON leads(email, status). O banco usa o índice pelo prefixo — o primeiro campo deve ser o que você filtra com mais frequência ou com maior seletividade.

Índice parcial existe. CREATE INDEX ON leads(email) WHERE status = 'ativo' cria índice só para leads ativos. Se 90% dos leads são inativos e você sempre filtra por ativo, esse índice é muito menor e mais eficiente.


O incidente de locking que aprendi da pior forma

Precisava adicionar coluna NOT NULL em tabela de 200k registros em produção.

ALTER TABLE leads ADD COLUMN fonte VARCHAR(50) NOT NULL DEFAULT 'formulario';

Executei. Travou. Todos os outros processos que tentavam escrever ou ler a tabela ficaram em fila esperando. O ALTER TABLE com NOT NULL + DEFAULT no PostgreSQL pré-11 reescreve a tabela inteira com lock exclusivo.

Por sorte, desisti depois de 30 segundos de tensão, rodei em horário de menor tráfego, e não quebrou produção de vez.

A forma correta para zero-downtime:

-- Passo 1: adiciona coluna nullable (sem lock problemático)
ALTER TABLE leads ADD COLUMN fonte VARCHAR(50);

-- Passo 2: preenche em batches (não em uma transaction gigante)
UPDATE leads SET fonte = 'formulario' WHERE id BETWEEN 1 AND 10000;
UPDATE leads SET fonte = 'formulario' WHERE id BETWEEN 10001 AND 20000;
-- ... e assim por diante

-- Passo 3: adiciona constraint (depois que todos têm valor)
ALTER TABLE leads ALTER COLUMN fonte SET NOT NULL;

PostgreSQL 11+ mudou esse comportamento para DDL simples. Mas o padrão de "migrations em múltiplos passos com batches" se aplica para qualquer banco.


O que banco de dados me ensinou sobre sistemas

Banco de dados é onde tudo converge. A velocidade da sua query reflete a qualidade do seu modelo de dados. A estabilidade das suas migrations reflete o quanto você pensa em reversibilidade. O tamanho das suas transactions reflete o quanto você entende de locking.

Não é possível ser bom em backend sem entender banco de dados de verdade. Não "SQL que funciona" — banco de dados: índices, transactions, locking, vacuum, explain analyze.

Você não precisa saber tudo agora. Mas quando um endpoint lento aparecer, o EXPLAIN ANALYZE é o primeiro lugar para olhar.

Essa semana: pega a query mais lenta que você tem em produção (ou a mais importante). Roda EXPLAIN ANALYZE nela. O que o banco está fazendo? Tem Seq Scan em tabela grande? Esse é o índice que precisa existir.