PostgreSQL · Arquitetura Interna

Arquitetura PostgreSQL

Como o PostgreSQL funciona por dentro, do básico ao avançado: processos, armazenamento físico, MVCC, WAL, replicação, segurança e extensibilidade. Este é o mapa conceitual que sustenta os dois estudos aplicados do hub — otimização de queries e migração sem downtime — que aprofundam, respectivamente, a Fase 9 deste guia.

36 tópicos
Interno + operação
PostgreSQL 16–18
Recursos Gerais
01Siga as fases em ordem: elas dependem umas das outras — MVCC (Fase 3) só faz sentido depois de entender páginas e tuplas (Fase 2), e replicação (Fase 6) depende de WAL (Fase 4).
02Este guia cobre como o Postgres funciona; os módulos de otimização de queries e o runbook de migração (link abaixo) cobrem como operá-lo na prática — leia este primeiro.
03A documentação oficial é a referência mais confiável e atualizada que existe para PostgreSQL — praticamente todo tópico aqui linka de volta para um capítulo específico dela.
Aprofunde-se: Otimização & Administração
01A Fase 9 deste guia (Configuração, Otimização e Migração) é a ponte para dois estudos dedicados e muito mais extensos, publicados neste mesmo hub.
01

Fundamentos e Processos

Filosofia do projeto e o que roda por trás de uma conexão

O que é o PostgreSQLBásico
Um SGBD objeto-relacional, extensível e fiel ao padrão SQL

O PostgreSQL nasceu do projeto acadêmico POSTGRES (Berkeley, 1986), pensado desde o início para ser extensível — não só nos dados que armazena, mas no próprio banco: tipos, operadores, índices e linguagens procedurais podem ser adicionados sem recompilar o servidor.

TeoriaTrês características definem a filosofia do Postgres frente a outros SGBDs relacionais: modelo objeto-relacional (suporta herança de tabelas, tipos compostos e arrays nativos, não só linhas e colunas planas); extensibilidade radical (o sistema de extensões — Fase 8 — permite adicionar tipos de dados inteiros como PostGIS ou pgvector como se fossem nativos); e fidelidade rígida ao padrão SQL, priorizando correção sobre atalhos de performance quando os dois entram em conflito. Essa combinação explica por que o Postgres tende a ser a escolha "segura por padrão" para cargas de trabalho que não cabem perfeitamente no modelo relacional puro.
Arquitetura de processosBásico
Postmaster, backends por conexão e processos auxiliares

Diferente de bancos que usam um modelo multi-thread, o Postgres usa um processo do sistema operacional por conexão — uma decisão de projeto antiga que ainda molda como ele escala e por que connection pooling (Fase 9) é praticamente obrigatório em produção.

TeoriaO postmaster é o processo pai: escuta a porta 5432, aceita conexões e, para cada uma, faz um fork() de um novo processo backend dedicado. Em paralelo rodam processos auxiliares fixos: checkpointer (grava páginas sujas periodicamente), background writer (grava páginas sujas de forma incremental fora dos checkpoints), WAL writer (descarrega o log de escrita para disco), autovacuum launcher (dispara workers de limpeza — Fase 5) e stats collector (coleta estatísticas de uso). Cada backend tem sua própria memória privada, mas todos compartilham uma região de memória compartilhada (shared_buffers) via mmap/shmget.
💡O modelo processo-por-conexão custa memória: cada backend ocioso ainda ocupa alguns MB de RAM. É por isso que abrir centenas de conexões diretas do lado da aplicação, sem um pooler, é um anti-padrão clássico de operação Postgres.
PGDATA: diretórios e configuraçãoBásico
Onde tudo vive em disco, e os três arquivos que controlam o servidor

PGDATA é o diretório raiz de um cluster Postgres (o termo "cluster" aqui significa "instância do servidor com seus múltiplos bancos", não um cluster distribuído). Sua estrutura interna raramente precisa ser tocada à mão, mas entendê-la ajuda a diagnosticar problemas de disco e I/O.

TeoriaDentro de PGDATA: base/ guarda um subdiretório por banco de dados, cada um com os arquivos de tabela (um oid numérico por relação, fatiado a cada 1GB); pg_wal/ guarda os segmentos do write-ahead log (Fase 4); pg_xact/ guarda o status de commit de cada transação; global/ guarda catálogos compartilhados entre todos os bancos. Três arquivos de configuração controlam o comportamento do servidor: postgresql.conf (parâmetros gerais — memória, WAL, planner), pg_hba.conf (regras de autenticação — Fase 7) e pg_ident.conf (mapeamento de identidade para autenticação externa).
Catálogo de sistemaIntermediário
pg_catalog e information_schema: o banco descrevendo a si mesmo

Todo metadado do Postgres — tabelas, colunas, índices, roles, tipos — é guardado em tabelas comuns, consultáveis com SQL normal. Não existe um "modo especial" para ver a estrutura do banco.

Teoriapg_catalog é o esquema nativo do Postgres, com tabelas como pg_class (relações: tabelas, índices, views), pg_attribute (colunas) e pg_proc (funções) — rápido, mas com nomes internos pouco amigáveis. information_schema é uma camada padronizada pelo SQL:2016 sobre esses mesmos dados, portável entre SGBDs diferentes, mas mais lenta e menos completa para peculiaridades específicas do Postgres. Na prática: use information_schema para queries portáveis entre bancos, e pg_catalog quando precisar de detalhes internos (como o plano de um índice ou o OID de uma relação).
SQL
SELECT relname, relkind, reltuples
FROM pg_catalog.pg_class
WHERE relnamespace = 'public'::regnamespace
ORDER BY reltuples DESC;
-- relkind: 'r' tabela, 'i' índice, 'v' view, 'm' matview
02

Armazenamento Físico

Como uma linha vira bytes em disco

Páginas de 8KB, tuplas e o heapBásico
A unidade fundamental de I/O de qualquer tabela

Uma tabela no Postgres é, fisicamente, um arquivo (ou vários, fatiados a cada 1GB) dividido em páginas de 8KB — o tamanho padrão de leitura e escrita para praticamente qualquer operação de disco.

TeoriaCada página de 8KB tem um cabeçalho, um array de ponteiros de item (item pointers, apontando para onde cada tupla começa dentro da página) e as próprias tuplas, crescendo do fim para o começo da página. Uma tupla é uma versão física de uma linha — e é aqui que o MVCC (Fase 3) deixa sua marca: cada tupla carrega campos ocultos xmin/xmax (o ID da transação que a criou/removeu), então um UPDATE no Postgres nunca sobrescreve uma tupla no lugar — ele cria uma nova versão e marca a antiga como morta. O conjunto de todas as páginas de uma tabela é chamado de heap: não há ordem física garantida entre as tuplas, ao contrário de um índice clustered em outros SGBDs.
TablespacesBásico
Redirecionando onde objetos específicos vivem em disco

Por padrão, todo objeto de um banco fica sob o mesmo PGDATA. Tablespaces permitem apontar tabelas ou índices específicos para outro caminho de disco — útil para separar dados quentes e frios entre discos de velocidades diferentes.

TeoriaUm tablespace é, na prática, um ponteiro simbólico para um diretório fora de PGDATA. Casos de uso reais: mover um índice muito acessado para um SSD NVMe enquanto a tabela histórica fica num disco mais barato; isolar uma tabela de logs de alto volume de escrita do resto do banco para não competir por I/O nos checkpoints; ou, em ambientes on-premise antigos, contornar limites de tamanho de um único volume. Em ambientes cloud-managed (RDS, Cloud SQL), tablespaces raramente são usados, já que o storage já é elástico por padrão.
SQL
CREATE TABLESPACE fast_ssd LOCATION '/mnt/nvme/pgdata_idx';
ALTER INDEX idx_pedidos_status SET TABLESPACE fast_ssd;
TOASTIntermediário
Como o Postgres guarda valores maiores que uma página

Como toda tupla precisa caber numa página de 8KB, campos grandes — um JSONB extenso, um texto longo — não podem simplesmente crescer. O TOAST (The Oversized-Attribute Storage Technique) resolve isso.

TeoriaQuando uma linha ultrapassaria cerca de 2KB (um quarto da página, o limite prático que o Postgres tenta respeitar), campos "toastáveis" (tipos de tamanho variável, como text, bytea, jsonb) são movidos para uma tabela TOAST auxiliar associada à tabela original, e o campo original passa a guardar só um ponteiro. Antes de mover, o Postgres tenta comprimir o valor no lugar (estratégia PGLZ ou LZ4 a partir da v14); só se ainda não couber é que ele é fatiado em pedaços de ~2KB e movido para fora da linha (out-of-line storage). Isso explica por que uma tabela com poucas colunas JSONB grandes pode ter um arquivo .toast maior que a própria tabela.
💡A estratégia de TOAST por coluna é configurável via ALTER TABLE ... ALTER COLUMN ... SET STORAGE {PLAIN|EXTENDED|EXTERNAL|MAIN} — útil quando você sabe que um campo específico nunca deveria ser comprimido (dados já comprimidos, como imagens) ou nunca deveria sair da linha.
Free Space Map e Visibility MapIntermediário
Os dois mapas auxiliares que aceleram escritas e scans

Toda tabela vem acompanhada de dois arquivos auxiliares compactos que o planner e o executor consultam sem precisar tocar o heap inteiro: a FSM e a VM.

TeoriaA Free Space Map (_fsm) rastreia, por página, quanto espaço livre existe — assim um INSERT não precisa varrer o heap inteiro procurando onde caber uma nova tupla. A Visibility Map (_vm) marca páginas onde todas as tuplas são visíveis para todas as transações em andamento (nenhuma tupla morta, nada em disputa) — isso permite dois ganhos: o VACUUM pode pular inteiramente essas páginas (Fase 5), e um Index Only Scan pode responder sem tocar o heap, só o índice, quando a página está marcada como "all-visible". Manter a VM atualizada é, por isso, um dos motivos práticos mais importantes para rodar autovacuum regularmente, não só a limpeza de tuplas mortas.
03

MVCC e Concorrência

Como o Postgres deixa leitores e escritores trabalharem sem se bloquearem

MVCC no PostgresBásico
Leitores nunca bloqueiam escritores, e vice-versa

Multiversion Concurrency Control é a razão pela qual um SELECT longo nunca trava um UPDATE concorrente no Postgres — cada transação enxerga uma "fotografia" consistente dos dados no momento em que começou.

TeoriaComo visto na Fase 2, um UPDATE não sobrescreve uma tupla — ele cria uma nova versão com um novo xmin (a transação que a criou) e marca a versão antiga com xmax (a transação que a substituiu). Quando uma transação lê uma linha, ela compara os IDs xmin/xmax de cada versão contra seu próprio snapshot — um retrato de quais transações já haviam sido commitadas no instante em que ela começou — e escolhe a versão correta a enxergar. O resultado prático: leituras nunca precisam de lock, porque elas simplesmente ignoram versões que não deveriam ver, em vez de esperar por elas.
Níveis de isolamentoIntermediário
O que o Postgres realmente implementa, além do que o padrão pede

O padrão SQL define quatro níveis de isolamento, mas o Postgres implementa só três deles fisicamente — porque o MVCC já elimina uma das anomalias na raiz.

TeoriaREAD UNCOMMITTED existe apenas como sinônimo de READ COMMITTED no Postgres, já que o MVCC torna "leitura suja" (ver dados não commitados) impossível por construção. READ COMMITTED (padrão) tira um snapshot novo a cada comando dentro da transação. REPEATABLE READ tira um único snapshot para a transação inteira, prevenindo non-repeatable reads e phantom reads — mas, diferente do padrão SQL, ainda não é serializável de verdade sozinho. SERIALIZABLE usa uma técnica chamada SSI (Serializable Snapshot Isolation) que detecta, em tempo real, quando o resultado de transações concorrentes divergiria de alguma ordem serial possível, abortando uma delas com erro de serialização — é o único nível que garante ausência total de anomalias, ao custo de exigir retry na aplicação.
⚠️Usar SERIALIZABLE exige que a aplicação trate o erro 40001 (serialization_failure) com retry automático — sem isso, transações vão falhar silenciosamente sob carga concorrente.
LockingIntermediário
Tipos de lock e como identificar contenção real

MVCC elimina locks de leitura, mas não elimina locks — escritas concorrentes na mesma linha, e certas operações de DDL, ainda precisam coordenar acesso exclusivo a algo.

TeoriaLocks no Postgres existem em duas granularidades principais: locks de linha (row-level: FOR UPDATE, FOR SHARE, aplicados quando duas transações tentam modificar a mesma linha) e locks de tabela (table-level: de ACCESS SHARE, o mais fraco, tirado por qualquer SELECT, até ACCESS EXCLUSIVE, tirado por um DROP TABLE ou certos ALTER TABLE). Um deadlock acontece quando duas transações esperam locks uma da outra em ordem cruzada; o Postgres detecta o ciclo automaticamente (a cada deadlock_timeout, 1s por padrão) e aborta uma das transações. A view pg_locks, cruzada com pg_stat_activity, é a ferramenta padrão para ver quem está esperando por quem em tempo real.
SQL
-- Quem está bloqueando quem, agora
SELECT blocked.pid AS blocked_pid, blocking.pid AS blocking_pid,
       blocked.query AS blocked_query
FROM pg_stat_activity blocked
JOIN pg_locks bl ON bl.pid = blocked.pid AND NOT bl.granted
JOIN pg_locks kl ON kl.locktype = bl.locktype AND kl.granted
JOIN pg_stat_activity blocking ON blocking.pid = kl.pid;
Transaction IDs e wraparoundAvançado
O contador de 32 bits que, sem manutenção, pode parar o banco

Todo o MVCC depende de comparar IDs de transação (XIDs) para decidir "isso é passado ou futuro?" — e esse contador é de apenas 32 bits, o que cria um dos poucos cenários catastróficos reais em operação Postgres.

TeoriaCom XIDs de 32 bits (~4,2 bilhões de valores), o Postgres usa aritmética circular: metade dos valores é considerada "passado", metade "futuro", relativo a um XID de referência. Se um banco rodar 2 bilhões de transações sem que o VACUUM "congele" (freeze) as tuplas antigas — marcando-as como permanentemente visíveis, fora da contagem circular —, o contador dá a volta e tuplas antigas passam a parecer "do futuro", ou seja, invisíveis. Antes de chegar lá, o Postgres entra em modo de proteção: primeiro emite warnings agressivos, depois recusa novas transações até que um VACUUM manual libere XIDs. O autovacuum já prioriza tabelas próximas do limite de wraparound automaticamente — mas isso pode ser insuficiente se autovacuum estiver desabilitado ou mal ajustado numa tabela de altíssimo volume.
🛑Wraparound é um dos poucos incidentes Postgres que param completamente as escritas do banco. Monitore age(datfrozenxid) em pg_database — valores acima de ~1,5 bilhão merecem atenção imediata.
04

WAL, Durabilidade e Recuperação

O log que garante que nada commitado se perde

Write-Ahead LogBásico
Por que toda mudança é registrada antes de tocar os dados

A regra de ouro do WAL: uma mudança só é considerada durável depois que o registro dela chegou em disco no log — nunca antes. Isso é o que sobrevive a uma queda de energia no meio de uma escrita.

TeoriaEm vez de gravar cada mudança de página imediatamente em disco (caro e lento), o Postgres registra um registro de log compacto descrevendo a mudança em pg_wal/ e só faz fsync desse log a cada COMMIT. As páginas de dados em si são gravadas depois, de forma preguiçosa, pelo background writer e pelo checkpointer. Se o servidor cair entre o commit e a gravação da página real, a recuperação (Passo 16) simplesmente reaplica o WAL a partir do último checkpoint, reconstruindo o estado correto. Esse é o mesmo princípio de write-ahead logging usado por praticamente todo SGBD transacional sério — o Postgres só o implementa de um jeito particular.
Checkpoints e fsyncIntermediário
Como o WAL para de crescer para sempre

Se o WAL crescesse para sempre, a recuperação após uma queda demoraria cada vez mais. O checkpoint é o ponto de corte que limita esse custo.

TeoriaUm checkpoint força todas as páginas sujas em memória (modificadas mas ainda não gravadas em disco) a serem persistidas, e registra no WAL um marco: "tudo antes daqui já está garantidamente em disco". A partir desse ponto, segmentos de WAL anteriores ao checkpoint podem ser reciclados ou removidos com segurança. Dois parâmetros controlam a frequência: checkpoint_timeout (tempo máximo entre checkpoints, padrão 5min) e max_wal_size (volume de WAL que dispara um checkpoint antecipado). Checkpoints muito frequentes geram picos de I/O; checkpoints muito espaçados alongam o tempo de recuperação após uma falha — checkpoint_completion_target suaviza isso espalhando a escrita ao longo do intervalo em vez de rajadas.
wal_level, archiving e PITRIntermediário
Guardando WAL suficiente para restaurar para qualquer segundo

Por padrão o Postgres recicla WAL antigo assim que possível. Para backup contínuo e recuperação ponto-no-tempo (PITR), é preciso dizer explicitamente ao banco para guardar mais informação e arquivar os segmentos antes de reciclá-los.

Teoriawal_level controla quanta informação cada registro de WAL carrega: minimal (só o necessário para crash recovery), replica (padrão — inclui o suficiente para replicação física e PITR) e logical (adiciona metadados para replicação lógica — Fase 6). Continuous archiving copia cada segmento de WAL fechado para um armazenamento externo (S3, GCS, ou um servidor de arquivo) via archive_command, antes que o Postgres o recicle. Combinado com um backup base periódico, isso permite Point-in-Time Recovery: restaurar o banco para o estado exato de, por exemplo, "2 minutos antes de alguém rodar um DELETE sem WHERE" — reaplicando o WAL arquivado até o instante desejado.
⚠️PITR sem testar a restauração regularmente é uma falsa sensação de segurança — o runbook de migração (link no topo) cobre como validar um restore de ponta a ponta antes de precisar dele de verdade.
Crash recoveryAvançado
O que acontece nos primeiros segundos depois de uma queda

Quando o Postgres é reiniciado depois de uma queda (em vez de um shutdown limpo), ele não assume que os dados em disco estão corretos — ele os reconcilia automaticamente contra o WAL antes de aceitar conexões.

TeoriaNa inicialização, o Postgres verifica o control file e detecta que o último shutdown não foi limpo. Ele então entra em modo de recuperação: localiza o último checkpoint válido e reaplica (replay) todos os registros de WAL a partir dali, um a um, refazendo cada mudança física de página exatamente como ela aconteceu. Como cada operação de WAL é idempotente (reaplicar a mesma mudança duas vezes não causa dano — o Postgres compara o LSN da página com o do registro), o replay pode inclusive recomeçar do zero se cair de novo no meio do processo. Só depois que o replay alcança o fim do WAL disponível é que o banco aceita novas conexões — por isso um max_wal_size muito alto (checkpoints raros) alonga esse tempo de indisponibilidade após uma falha.
05

VACUUM e Manutenção

O preço operacional de nunca sobrescrever uma tupla

Por que o MVCC exige VACUUMBásico
Tuplas mortas não desaparecem sozinhas

Como visto na Fase 3, um UPDATE ou DELETE não remove nada fisicamente — só marca a versão antiga como morta. Alguém precisa, mais tarde, efetivamente liberar esse espaço.

TeoriaUma tupla "morta" (removida por DELETE, ou substituída por UPDATE) só pode ser fisicamente removida quando nenhuma transação em andamento ainda poderia precisar enxergá-la — ou seja, quando ela está fora do snapshot de toda transação ativa. O VACUUM varre o heap, identifica essas tuplas mortas seguras para remover, libera o espaço de volta para a Free Space Map (Fase 2) e atualiza a Visibility Map. Sem VACUUM regular, uma tabela com muita escrita acumula tuplas mortas indefinidamente — um fenômeno chamado bloat (Passo 19), que infla o tamanho físico da tabela sem aumentar os dados úteis.
autovacuumIntermediário
Os parâmetros que decidem quando e com que agressividade ele roda

Deixar VACUUM manual a cargo do operador não escala. O autovacuum é um daemon que monitora tabelas e dispara VACUUM (e ANALYZE) automaticamente — mas seus padrões de fábrica são conservadores demais para muitas cargas modernas.

TeoriaO autovacuum dispara um VACUUM numa tabela quando o número de tuplas mortas ultrapassa autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × n_tuplas (padrão: 50 + 20% do tamanho da tabela) — o que, numa tabela de 50 milhões de linhas, significa esperar 10 milhões de tuplas mortas antes de agir. Em tabelas grandes e de alto volume de escrita, isso é tarde demais; reduzir autovacuum_vacuum_scale_factor por tabela (via ALTER TABLE ... SET) é um dos ajustes de tuning mais comuns e de maior impacto em produção. autovacuum_vacuum_cost_limit e _cost_delay controlam quanto I/O o autovacuum pode consumir antes de pausar, equilibrando velocidade de limpeza contra impacto nas queries concorrentes.
SQL
-- Autovacuum mais agressivo numa tabela de altíssima escrita
ALTER TABLE eventos SET (
  autovacuum_vacuum_scale_factor = 0.02,
  autovacuum_vacuum_cost_delay = 2
);
BloatIntermediário
Sintomas, causas e como medir

Bloat é o espaço físico ocupado por tuplas mortas e páginas parcialmente vazias que o VACUUM comum ainda não conseguiu devolver ao sistema operacional.

TeoriaVACUUM (sem FULL) libera espaço de volta para a Free Space Map da própria tabela, para ser reaproveitado por INSERTs futuros — mas raramente devolve o espaço ao sistema operacional, porque isso exigiria mover dados para compactar páginas no fim do arquivo. O resultado, sob padrões de UPDATE intenso mal servidos pelo autovacuum, é uma tabela fisicamente maior que seus dados úteis, com índices B-tree também inflados por ponteiros para tuplas mortas. Sintomas típicos: tamanho em disco crescendo sem crescimento proporcional de linhas, e planos de execução ficando mais lentos porque scans percorrem mais páginas vazias. A correção estrutural (quando o bloat já está feito) é VACUUM FULL ou a extensão pg_repack — ambos reescrevem a tabela do zero, mas o primeiro exige lock exclusivo.
⚠️VACUUM FULL trava a tabela inteira (leituras e escritas) enquanto reescreve — em produção, prefira pg_repack, que faz o mesmo trabalho online usando uma tabela sombra.
HOT updatesAvançado
O atalho que evita reescrever índices num UPDATE simples

Normalmente, um UPDATE precisa atualizar a nova versão da tupla em todo índice da tabela, mesmo que só uma coluna sem índice tenha mudado. Heap-Only Tuples é a otimização que evita isso.

TeoriaQuando um UPDATE não modifica nenhuma coluna indexada e a nova versão da tupla cabe na mesma página da versão antiga, o Postgres cria a nova tupla como HOT: os índices continuam apontando para a tupla antiga, que por sua vez tem um ponteiro interno (t_ctid) para a nova versão — uma cadeia dentro da própria página. Isso evita completamente o trabalho de inserir uma nova entrada em cada índice. Um subproduto valioso: páginas com cadeias HOT podem sofrer pruning (uma limpeza leve, oportunista, feita durante o acesso normal à página) sem precisar de um VACUUM completo. Na prática, isso significa que deixar algum fillfactor de folga (ex: 90 em vez de 100) numa tabela de UPDATE intenso aumenta a chance de cada UPDATE caber na mesma página e virar HOT.
06

Replicação e Alta Disponibilidade

Cópias do WAL em outros servidores, e o que fazer quando o primário cai

Replicação física em streamingBásico
Uma réplica que reaplica o WAL do primário em tempo real

Se o crash recovery (Fase 4) já reaplica o WAL localmente, a replicação física é a mesma ideia levada adiante: um segundo servidor reaplica continuamente o WAL de um primário remoto.

TeoriaUma réplica (standby) abre uma conexão de replicação com o primário e recebe um fluxo contínuo de registros WAL assim que são gerados, aplicando-os localmente em modo de recovery permanente. Como é uma cópia byte-a-byte do estado físico, a réplica é idêntica ao primário em nível de página — inclusive índices, sem precisar reconstruí-los. Por padrão o standby também aceita conexões de leitura (hot standby), servindo como réplica de leitura para distribuir carga, com a ressalva de que os dados podem estar alguns milissegundos atrás do primário (Passo 22).
Síncrona vs assíncronaIntermediário
O trade-off entre durabilidade extra e latência de escrita

Por padrão, um COMMIT no primário é considerado concluído assim que o WAL é durável localmente — a réplica pode ainda não ter recebido nada. A replicação síncrona muda esse contrato.

TeoriaEm replicação assíncrona (padrão), o primário não espera confirmação de nenhuma réplica para dar o COMMIT como concluído — rápido, mas com uma janela real de perda de dados se o primário cair antes da réplica alcançar o WAL mais recente. Em replicação síncrona (synchronous_standby_names), o COMMIT só retorna depois que pelo menos uma réplica designada confirma ter recebido (ou aplicado, dependendo de synchronous_commit) o WAL — eliminando a janela de perda, ao custo de que a latência de cada escrita passa a incluir uma viagem de rede até a réplica. Um meio-termo comum é synchronous_commit = remote_write: a réplica confirma que recebeu o WAL (mas não necessariamente aplicou), reduzindo a latência frente a on completo.
Replicação lógicaIntermediário
Publication/subscription: replicar dados, não páginas de disco

A replicação física copia bytes idênticos — mesma versão, mesmo esquema. A replicação lógica decodifica o WAL em mudanças de linha (INSERT/UPDATE/DELETE) e as reaplica via SQL, abrindo um leque de casos de uso diferente.

TeoriaUma publication no lado de origem declara quais tabelas (ou todas) devem ser replicadas; uma subscription no destino se conecta e recebe o fluxo de mudanças decodificado. Como opera no nível lógico (linhas, não páginas), a replicação lógica permite coisas que a física não permite: replicar só um subconjunto de tabelas, replicar entre versões diferentes do Postgres (essencial para upgrades sem downtime — o runbook do hub cobre isso em detalhe), e até replicar para um destino não-Postgres via ferramentas de CDC. O custo é maior overhead de CPU (a decodificação lógica do WAL não é gratuita) e a ausência de DDL replicado automaticamente — mudanças de schema precisam ser aplicadas manualmente em ambos os lados.
Failover e orquestraçãoAvançado
O Postgres não promove réplicas sozinho — algo precisa decidir

O PostgreSQL, sozinho, não tem failover automático — ele sabe replicar, mas não sabe decidir "o primário caiu, promova essa réplica agora". Essa decisão exige uma camada externa de orquestração.

TeoriaPatroni é hoje o padrão de fato para HA self-managed: usa um armazenamento distribuído de consenso (etcd, Consul ou ZooKeeper) para eleger um líder, monitora a saúde do primário via health checks contínuos e promove uma réplica automaticamente em caso de falha, reconfigurando as demais para seguir o novo primário. Alternativas mais leves incluem repmgr (mais simples, menos automação) e pg_auto_failover (da própria comunidade Postgres/Citus). Provedores gerenciados (RDS, Cloud SQL, Azure Database) implementam sua própria orquestração interna, escondendo essa complexidade — uma das razões práticas para escolher managed sobre self-hosted quando HA automatizado é um requisito duro.
💡Failover automático troca disponibilidade por risco de split-brain (dois primários ativos ao mesmo tempo) se mal configurado — fencing do nó antigo é uma etapa obrigatória, não opcional, em qualquer runbook de failover.
07

Segurança e Controle de Acesso

Quem pode conectar, autenticar e ver o quê

Roles e privilégiosBásico
O Postgres não distingue "usuário" de "grupo" internamente

No Postgres, um "usuário" é só uma role com a permissão LOGIN ativada — roles e grupos são o mesmo conceito, o que torna o modelo de permissões mais uniforme (e mais fácil de compor) do que em sistemas com dois conceitos separados.

TeoriaRoles podem ser concedidas a outras roles (GRANT role_a TO role_b), criando herança de privilégios: se role_b herda de role_a, ela automaticamente tem tudo que role_a tem, sem precisar de GRANTs redundantes. O padrão recomendado é separar roles de "grupo" (sem LOGIN, representando um papel: leitura_bi, app_writer) de roles de "pessoa/aplicação" (com LOGIN), concedendo o grupo à pessoa — assim, revisar quem tem acesso a quê vira uma questão de olhar as roles de grupo, não caçar GRANTs individuais espalhados.
SQL
CREATE ROLE app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;
CREATE ROLE svc_relatorios LOGIN PASSWORD '...';
GRANT app_readonly TO svc_relatorios;
pg_hba.confBásico
Quem pode conectar de onde, e com qual método

Antes de qualquer role ou privilégio de tabela entrar em jogo, o Postgres decide se a conexão sequer é permitida — e isso é governado por um único arquivo, lido de cima para baixo.

TeoriaCada linha de pg_hba.conf (host-based authentication) declara: tipo de conexão (local, host, hostssl), banco, role, origem (CIDR) e método de autenticação. O Postgres avalia as linhas em ordem e usa a primeira que casar — regras mais específicas devem vir antes das genéricas. Métodos comuns: scram-sha-256 (senha, hash moderno — padrão recomendado hoje), cert (certificado de cliente TLS), peer/ident (identidade do SO, só para conexões locais) e trust (sem autenticação — nunca em produção, exceto talvez para local em desenvolvimento).
🛑trust ou 0.0.0.0/0 com senha fraca em pg_hba.conf são os dois erros de configuração mais comuns que resultam em bancos Postgres expostos publicamente na internet.
Row-Level SecurityIntermediário
Filtrando linhas por política, direto no banco

GRANT controla acesso a tabelas inteiras. Quando o requisito é "cada cliente só vê suas próprias linhas", RLS aplica esse filtro automaticamente, sem depender da aplicação lembrar de um WHERE tenant_id = ... em toda query.

TeoriaRow-Level Security define políticas (CREATE POLICY) que o Postgres injeta automaticamente como filtro implícito em toda query contra a tabela — para SELECT, INSERT, UPDATE e DELETE separadamente, se necessário. É o mecanismo de isolamento de tenant mais robusto em arquiteturas multi-tenant com schema compartilhado: mesmo uma query mal escrita pela aplicação, sem filtro de tenant, não consegue vazar dados de outro cliente, porque o filtro vive no banco, não na camada de aplicação. RLS só se aplica a roles sem o atributo BYPASSRLS (superusers o ignoram por padrão) — um detalhe frequentemente esquecido ao testar políticas.
SQL
ALTER TABLE pedidos ENABLE ROW LEVEL SECURITY;
CREATE POLICY isolamento_tenant ON pedidos
  USING (tenant_id = current_setting('app.tenant_id')::uuid);
Criptografia e auditoriaAvançado
Em trânsito, em repouso, e quem fez o quê

O Postgres nativamente não criptografa dados em repouso — isso normalmente vem de uma camada abaixo (disco, provedor cloud) ou de uma extensão, dependendo do requisito de compliance.

TeoriaEm trânsito: ssl = on em postgresql.conf, combinado com hostssl em pg_hba.conf, força TLS em toda conexão de rede. Em repouso: a abordagem mais comum é criptografia no nível do sistema de arquivos ou do volume (LUKS, EBS encryption, TDE do provedor cloud) — transparente para o Postgres. Para criptografia seletiva por coluna, a extensão pgcrypto oferece funções como pgp_sym_encrypt, embora isso impeça indexação eficiente sobre o valor cifrado. Auditoria: log_statement e log_connections nativos cobrem o básico; a extensão pgAudit é o padrão de mercado para trilhas de auditoria detalhadas exigidas por frameworks de compliance (SOC 2, PCI-DSS, LGPD/GDPR).
08

Extensibilidade e Tipos de Dados

O que diferencia o Postgres de um SGBD relacional comum

Sistema de extensõesBásico
Código adicional que se integra como se fosse nativo

CREATE EXTENSION é provavelmente o comando mais estratégico do Postgres — é a porta de entrada para tudo que o transforma de "SGBD relacional" em "plataforma de dados" (geoespacial, vetorial, full-text, time-series).

TeoriaUma extensão empacota tipos de dados, funções, operadores e até tipos de índice inteiros num pacote instalável com um único comando, versionado e atualizável (ALTER EXTENSION ... UPDATE). O catálogo pg_extension rastreia o que está instalado; pg_available_extensions mostra o que está disponível no servidor mas ainda não ativado. Extensões escritas em C rodam no mesmo processo do backend (máxima performance, mas exigem confiança — só superusers instalam extensões não marcadas trusted); extensões em SQL/PL puro são mais seguras e portáveis.
SQL
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
SELECT * FROM pg_available_extensions ORDER BY name;
Tipos de dados avançadosIntermediário
JSONB, arrays, ranges e composite types nativos

Antes de recorrer a uma extensão, vale lembrar que o Postgres já vem, nativamente, com tipos que cobrem boa parte do que outros SGBDs relacionais exigem uma tabela extra ou uma ferramenta externa para representar.

TeoriaJSONB guarda JSON num formato binário decomposto (não texto), permitindo indexação eficiente via GIN e operadores de contenção (@>) — na prática, um documento semi-estruturado dentro de uma coluna relacional. Arrays são um tipo de primeira classe (integer[], text[]), com operadores próprios, evitando uma tabela de junção para listas pequenas e de tamanho conhecido. Range types (int4range, tsrange, daterange) representam um intervalo com operadores de sobreposição nativos (&&) — ideais para agendas, reservas e qualquer coisa com "período de validade". Composite types permitem definir um tipo com múltiplos campos, reutilizável como coluna ou retorno de função.
Particionamento declarativoIntermediário
Uma tabela lógica, muitas tabelas físicas por trás

Desde a versão 10, o Postgres suporta particionamento como recurso de primeira classe — a aplicação continua enxergando uma única tabela, mas o Postgres decide sozinho em qual partição cada linha vive.

TeoriaUma tabela particionada (PARTITION BY RANGE/LIST/HASH) não tem dados próprios — ela é só uma definição lógica; cada partição é uma tabela física real. O planner usa partition pruning: se a query filtra pela chave de particionamento, ele consulta só as partições relevantes, ignorando o resto. Isso ajuda tanto performance de leitura quanto manutenção — dropar uma partição inteira (ex: dados de um mês antigo) é uma operação de metadado quase instantânea, contra um DELETE que percorreria milhões de linhas e geraria bloat. A extensão pg_partman automatiza a criação e retenção de partições por tempo, um padrão comum para tabelas de eventos e séries temporais.
SQL
CREATE TABLE eventos (
  id bigserial, criado_em timestamptz NOT NULL, payload jsonb
) PARTITION BY RANGE (criado_em);

CREATE TABLE eventos_2026_09 PARTITION OF eventos
  FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
Extensões essenciais de operaçãoAvançado
O que instalar em praticamente todo banco de produção

Além das extensões de propósito específico (PostGIS para geoespacial, pgvector para busca vetorial), existe um pequeno conjunto que quase toda operação séria acaba instalando.

Teoriapg_stat_statements é, na prática, obrigatória: agrega estatísticas de execução por query normalizada (tempo total, chamadas, I/O), a base de qualquer diagnóstico de performance real — o estudo de otimização de queries do hub depende dela diretamente. pg_partman automatiza o ciclo de vida de partições (Passo 31). pg_repack resolve bloat sem lock exclusivo (Passo 19). pgAudit cobre trilhas de auditoria (Fase 7). PostGIS transforma o Postgres num SGBD geoespacial completo, com tipos de geometria e centenas de funções espaciais — motivo pelo qual muitas equipes de SIG escolhem Postgres em vez de um banco especializado proprietário.
09

Configuração, Otimização e Migração

A ponte entre este guia e os dois estudos aplicados do hub

Configuração de memória e recursosBásico
Os parâmetros que mais movem a agulha, fora do default

Os valores padrão de postgresql.conf são deliberadamente conservadores — pensados para rodar em qualquer máquina, não para aproveitar o hardware real de um servidor de produção.

Teoriashared_buffers é o cache principal de páginas em memória compartilhada entre backends — regra prática: 25% da RAM disponível, não mais (o sistema operacional já faz cache de página por conta própria, e duplicar demais esse cache é desperdício). work_mem é o limite de memória por operação de ordenação/hash dentro de cada conexão — um valor alto demais multiplicado por muitas conexões concorrentes pode esgotar a RAM do servidor rapidamente. effective_cache_size não aloca nada; é só uma dica ao planner sobre quanta memória de cache (SO + Postgres) está disponível, influenciando se ele prefere um Index Scan ou um Seq Scan.
Connection poolingIntermediário
Domando o custo do modelo processo-por-conexão (Fase 1)

Voltando ao Passo 2: cada conexão é um processo do SO. Um pooler resolve isso reutilizando um número pequeno de conexões reais para atender um número muito maior de clientes.

TeoriaPgBouncer é o pooler leve e mais adotado, operando em três modos: session (uma conexão real por cliente, igual ao Postgres puro, sem ganho de densidade), transaction (a conexão real é devolvida ao pool assim que a transação termina — o modo mais usado, mas incompatível com recursos que exigem estado por sessão, como PREPARE ou variáveis de sessão), e statement (o mais agressivo, incompatível até com transações multi-comando). Pgpool-II é uma alternativa mais pesada que também faz balanceamento de carga entre réplicas e cache de query, mas com uma superfície operacional maior. A escolha do modo do PgBouncer é, na prática, uma negociação entre densidade de conexões e quais recursos SQL a aplicação pode abrir mão.
Otimização de queriesAvançado
Aqui o guia termina e o estudo dedicado começa

Este guia cobriu por que o planner precisa de estatísticas, como índices e MVCC funcionam por dentro. Como usar isso para diagnosticar e corrigir uma query lenta específica é um estudo completo à parte.

TeoriaO estudo dedicado Otimização de Queries PostgreSQL, publicado neste mesmo hub, assume os fundamentos deste guia (MVCC, páginas, WAL) como pré-requisito e vai fundo em 17 módulos práticos: leitura de EXPLAIN (ANALYZE, BUFFERS), o modelo de custos do planner, métodos de acesso (scans), algoritmos de join, os seis tipos de índice do Postgres (B-tree, Hash, GIN, GiST, SP-GiST, BRIN) e quando escolher cada um, anti-padrões comuns de query, e um laboratório reproduzível para praticar tudo isso com volume de dados realista.
Migração e upgrades sem downtimeAvançado
Aplicando replicação física e lógica (Fase 6) num runbook real

Toda estratégia de migração sem downtime é, no fundo, uma aplicação prática dos mecanismos de replicação da Fase 6 — este guia dá a teoria; o runbook do hub dá o passo a passo operacional.

TeoriaO runbook dedicado Migração PostgreSQL com Zero Downtime, publicado neste mesmo hub, cobre sete estratégias completas — de replicação física + switchover (mesma versão major) a replicação lógica nativa, pg_createsubscriber + pg_upgrade, pg_upgrade in-place, pglogical, CDC com Debezium/Kafka, e ferramentas gerenciadas de nuvem — além de um capítulo comum de preparação, cutover/validação/rollback, e troubleshooting rápido. A escolha entre estratégias depende essencialmente de três fatores: é upgrade de versão major ou só troca de infraestrutura, qual o tamanho do banco, e quanto tempo de indisponibilidade é aceitável — o runbook ajuda a decidir isso na primeira fase, antes de qualquer comando.