{
 "grupo": "banco",
 "descricao": "Banco de dados",
 "itens": [
  {
   "id": "banco-achar-a-consulta-lenta",
   "nome": "Achar a consulta lenta",
   "receita": "ative pg_stat_statements (ou slow log) e ordene por tempo total; trate as 3 primeiras",
   "prova": "lista das consultas mais caras com tempo médio e chamadas"
  },
  {
   "id": "banco-ler-um-explain-analyze",
   "nome": "Ler um EXPLAIN ANALYZE",
   "receita": "procure Seq Scan em tabela grande, estimativa de linhas muito diferente da real e Sort em disco",
   "prova": "o plano mostra Index Scan e tempo menor depois do ajuste"
  },
  {
   "id": "banco-criar-indice-sem-travar",
   "nome": "Criar índice sem travar",
   "receita": "CREATE INDEX CONCURRENTLY em Postgres (ou ALGORITHM=INPLACE no MySQL), fora de transação",
   "prova": "a tabela continua aceitando escrita durante a criação"
  },
  {
   "id": "banco-indice-composto-na-ordem-certa",
   "nome": "Índice composto na ordem certa",
   "receita": "igualdade primeiro, depois ordenação, depois intervalo; cubra a consulta mais frequente",
   "prova": "o plano usa o índice sem Sort extra"
  },
  {
   "id": "banco-indice-parcial",
   "nome": "Índice parcial",
   "receita": "CREATE INDEX ... WHERE status='ativo' para indexar só o subconjunto consultado",
   "prova": "o índice é bem menor e o plano o escolhe"
  },
  {
   "id": "banco-indice-que-nao-e-usado",
   "nome": "Índice que não é usado",
   "receita": "consulte pg_stat_user_indexes por idx_scan zero após semanas e remova com cuidado",
   "prova": "lista de índices sem uso e espaço recuperado"
  },
  {
   "id": "banco-acabar-com-o-n-1",
   "nome": "Acabar com o N+1",
   "receita": "troque consulta por item em laço por join, IN (...) ou carregamento em lote",
   "prova": "contagem de consultas por requisição cai de N+1 para 2"
  },
  {
   "id": "banco-migracao-sem-parar-o-servico",
   "nome": "Migração sem parar o serviço",
   "receita": "expanda (adicione nulo), migre os dados em lotes, mude o código, depois contraia (remova o velho)",
   "prova": "nenhum erro durante o deploy com tráfego ligado"
  },
  {
   "id": "banco-adicionar-coluna-not-null-com-seguranca",
   "nome": "Adicionar coluna NOT NULL com segurança",
   "receita": "adicione nula, preencha em lotes, valide a restrição NOT VALID e depois VALIDATE",
   "prova": "a tabela grande não fica bloqueada por minutos"
  },
  {
   "id": "banco-renomear-coluna-em-producao",
   "nome": "Renomear coluna em produção",
   "receita": "crie a nova, escreva nas duas, copie o histórico, leia da nova e só depois apague a antiga",
   "prova": "versão antiga e nova do código funcionam juntas"
  },
  {
   "id": "banco-backfill-em-lotes",
   "nome": "Backfill em lotes",
   "receita": "atualize por faixa de id com pausa e commit por lote, medindo o atraso de replicação",
   "prova": "o job termina sem aumentar o lag de réplica"
  },
  {
   "id": "banco-backup-que-de-fato-restaura",
   "nome": "Backup que de fato restaura",
   "receita": "pg_dump ou snapshot agendado com retenção, cópia fora da região e teste de restauração mensal",
   "prova": "restaurar num banco limpo e rodar contagens batendo"
  },
  {
   "id": "banco-recuperacao-para-um-ponto-no-tempo",
   "nome": "Recuperação para um ponto no tempo",
   "receita": "WAL arquivado e base backup; restaure até o horário anterior ao erro",
   "prova": "dado apagado por engano volta no ponto escolhido"
  },
  {
   "id": "banco-replica-de-leitura",
   "nome": "Réplica de leitura",
   "receita": "aponte relatórios e leituras tolerantes a atraso para a réplica; leia da principal após escrever",
   "prova": "consulta pesada não afeta a principal e o lag fica monitorado"
  },
  {
   "id": "banco-deadlock",
   "nome": "Deadlock",
   "receita": "acesse tabelas e linhas sempre na mesma ordem, mantenha transações curtas e repita a transação vítima",
   "prova": "log sem deadlock sob carga concorrente"
  },
  {
   "id": "banco-nivel-de-isolamento",
   "nome": "Nível de isolamento",
   "receita": "use READ COMMITTED por padrão, REPEATABLE READ/SERIALIZABLE onde há leitura-decisão-escrita, com retry",
   "prova": "teste concorrente não gera dupla reserva"
  },
  {
   "id": "banco-bloqueio-otimista",
   "nome": "Bloqueio otimista",
   "receita": "coluna version; UPDATE ... WHERE id=? AND version=?; 0 linhas afetadas significa conflito",
   "prova": "duas edições simultâneas: uma passa, a outra recebe conflito"
  },
  {
   "id": "banco-fila-no-banco",
   "nome": "Fila no banco",
   "receita": "SELECT ... FOR UPDATE SKIP LOCKED pega o próximo item sem bloquear os outros workers",
   "prova": "dois workers nunca pegam a mesma linha"
  },
  {
   "id": "banco-contador-sem-corrida",
   "nome": "Contador sem corrida",
   "receita": "UPDATE t SET n = n + 1 atômico, ou tabela de eventos e soma assíncrona",
   "prova": "100 incrementos paralelos somam exatamente 100"
  },
  {
   "id": "banco-chave-primaria-certa",
   "nome": "Chave primária certa",
   "receita": "bigint identity ou UUIDv7 ordenável; evite UUID v4 em tabela enorme com índice clusterizado",
   "prova": "inserções não fragmentam o índice"
  },
  {
   "id": "banco-restricoes-no-banco",
   "nome": "Restrições no banco",
   "receita": "NOT NULL, UNIQUE, CHECK e chave estrangeira protegem o que o código esquece",
   "prova": "inserir dado inválido falha no banco"
  },
  {
   "id": "banco-normalizacao-com-criterio",
   "nome": "Normalização com critério",
   "receita": "3ª forma para dados transacionais; desnormalize só com medição e fonte única da verdade",
   "prova": "nenhuma anomalia de atualização e a consulta cumpre o tempo"
  },
  {
   "id": "banco-tabela-particionada",
   "nome": "Tabela particionada",
   "receita": "particione por data em tabelas de eventos; consultas filtram pela chave; remova partição velha em vez de DELETE",
   "prova": "consulta por mês lê só uma partição (EXPLAIN)"
  },
  {
   "id": "banco-tipos-de-dado-corretos",
   "nome": "Tipos de dado corretos",
   "receita": "dinheiro em numeric/inteiro, tempo em timestamptz, texto com tamanho certo, enum ou tabela de domínio",
   "prova": "nenhum arredondamento errado em soma monetária"
  },
  {
   "id": "banco-dado-json-no-relacional",
   "nome": "Dado JSON no relacional",
   "receita": "jsonb para atributos variáveis com índice GIN; campo consultado com frequência vira coluna",
   "prova": "filtro em campo do jsonb usa índice"
  },
  {
   "id": "banco-limpar-dados-duplicados",
   "nome": "Limpar dados duplicados",
   "receita": "ache com GROUP BY ... HAVING COUNT(*)>1, mantenha um por regra explícita, depois crie UNIQUE",
   "prova": "consulta de duplicados retorna zero"
  },
  {
   "id": "banco-auditar-permissoes-do-banco",
   "nome": "Auditar permissões do banco",
   "receita": "um usuário por aplicação, só o necessário (sem superuser), senha em cofre, acesso por rede restrito",
   "prova": "o usuário da app não consegue DROP nem ler tabela alheia"
  },
  {
   "id": "banco-testar-o-banco-no-ci",
   "nome": "Testar o banco no CI",
   "receita": "suba o mesmo motor em container, aplique as migrações do zero e rode a suíte",
   "prova": "o pipeline cria o schema do zero e passa"
  },
  {
   "id": "banco-monitorar-o-banco",
   "nome": "Monitorar o banco",
   "receita": "acompanhe conexões, cache hit, lag de réplica, bloat, locks e espaço em disco com alerta",
   "prova": "painel com alerta quando o disco passa de 80%"
  },
  {
   "id": "banco-vacuum-e-bloat",
   "nome": "Vacuum e bloat",
   "receita": "ajuste autovacuum para tabelas quentes; meça bloat; reindexe ou pg_repack quando preciso",
   "prova": "tamanho da tabela cai e as consultas voltam ao tempo"
  }
 ]
}
