trilha

Tecnologias/02 - Bancos de Dados/01 - Relacionais/01 - PostgreSQL5 min

PostgreSQL

Conceito

A tese do Postgres é correção e extensibilidade, nessa ordem. Ele é um projeto acadêmico (Berkeley, 1986) que virou produto sem nunca abandonar a obsessão original: tudo é extensível — tipos, operadores, métodos de índice, linguagens procedurais, planejadores de acesso a dados externos.

Isso produz um banco que, na prática, absorve casos de uso que exigiriam outro sistema:

Você precisaria de… No Postgres é…
Banco de documentos jsonb + índice GIN
Busca textual tsvector + GIN
Banco geoespacial extensão PostGIS
Banco vetorial (IA/RAG) extensão pgvector
Série temporal particionamento nativo ou TimescaleDB
Fila de trabalho SELECT ... FOR UPDATE SKIP LOCKED
Chave-valor hstore ou jsonb

Não é "tão bom quanto o especialista" em nenhum desses. É bom o suficiente para você não operar cinco sistemas — e essa é a proposta de valor real.

Arquitetura em uma tela

  • Um processo por conexão (não thread). Fork do postmaster, ~5–10 MB de memória base cada. Este é o fato arquitetural mais consequente do Postgres.
  • WAL (Write-Ahead Log): tudo é gravado no log antes da página de dados. É a base de durabilidade, point-in-time recovery, replicação física e replicação lógica.
  • Heap + MVCC por versionamento na tabela: um UPDATE não altera a linha; ele grava uma nova versão (tupla) e marca a antiga como morta. Ver 3. MVCC e Isolamento na Prática.
  • VACUUM / autovacuum: o processo que recolhe as tuplas mortas. Não é manutenção opcional; é parte do funcionamento.
  • Índices variados: B-tree, Hash, GIN (multivalorado: array, jsonb, texto), GiST (geometria, ranges), BRIN (tabelas enormes e ordenadas fisicamente), SP-GiST. Mais índices parciais (WHERE ativo) e por expressão (lower(email)) — dois recursos que MySQL e Oracle não têm no mesmo formato.
  • DDL transacional: BEGIN; ALTER TABLE ...; ALTER TABLE ...; ROLLBACK; funciona. Migração de schema atômica.

PostgreSQL 18 (set/2025)

Uma major por ano, suporte de 5 anos por versão. A 18 foi das mais relevantes em muito tempo:

  • Novo subsistema de I/O assíncrono (io_uring no Linux): até 3× mais rápido em leitura de disco.
  • Skip scan em B-tree: consultas que só filtram a segunda coluna de um índice composto passam a usar o índice. Menos índice redundante para manter.
  • uuidv7() nativo: UUID ordenável por tempo — importa muito no MySQL (1. MySQL) e ajuda na localidade de índice aqui também.
  • Colunas geradas virtuais (calculadas na leitura) como padrão.
  • Autenticação OAuth 2.0.
  • pg_upgrade preservando estatísticas do planejador — o upgrade deixa de ter aquela janela de queries lentas.

Série atual em manutenção: 18.6 (ago/2026).

Trade-offs

Conexão é caro. Um processo por conexão significa que 500 conexões são 500 processos. Acima de ~algumas centenas, o custo de contexto e de memória degrada tudo. A consequência não é opcional: serviço com muitas réplicas precisa de pooler (PgBouncer, pgcat, ou o pool do próprio driver bem configurado — ver 5. Go em Produção). É o oposto do MySQL, que é thread-per-connection.

VACUUM é uma preocupação permanente. Como as versões antigas ficam na tabela, tabelas com muita atualização incham (bloat). Se o autovacuum não acompanhar o ritmo de escrita, o disco cresce, o cache hit piora e o planejador erra. Ninguém "resolve" o vacuum; você o monitora.

Alta disponibilidade não vem na caixa. Replicação (física e lógica) é nativa e sólida. Failover automático não é: você precisa de Patroni, repmgr, pg_auto_failover ou um gerenciado (RDS, Aurora). Compare com o RAC/Data Guard do Oracle e com o InnoDB Cluster do MySQL.

O trade-off central: o Postgres entrega o modelo de dados mais rico dos três e exige mais cuidado operacional em troca. É um banco de dev generoso e de DBA vigilante.

Exemplo prático

Fila de trabalho confiável sem broker, o padrão que faz muita gente adiar o Kafka por bons dois anos:

CREATE TABLE jobs (
  id          bigserial PRIMARY KEY,
  payload     jsonb        NOT NULL,
  status      text         NOT NULL DEFAULT 'pendente',
  tentativas  int          NOT NULL DEFAULT 0,
  disponivel_em timestamptz NOT NULL DEFAULT now()
);

-- índice PARCIAL: só indexa o que a fila consulta
CREATE INDEX jobs_pendentes ON jobs (disponivel_em)
  WHERE status = 'pendente';
BEGIN;
  SELECT id, payload
    FROM jobs
   WHERE status = 'pendente' AND disponivel_em <= now()
   ORDER BY disponivel_em
   FOR UPDATE SKIP LOCKED      -- <- a peça-chave
   LIMIT 10;
  -- ... processa ...
  UPDATE jobs SET status = 'ok' WHERE id = ANY($1);
COMMIT;

SKIP LOCKED faz cada worker pular as linhas que outro já travou: N consumidores concorrentes sem coordenação externa, com a garantia transacional do banco. É Competing Consumers implementado em 8 linhas de SQL — e como o UPDATE está na mesma transação do trabalho, você ganha atomicidade que um broker não dá de graça (4. Garantias de Entrega e Idempotência).

Como isso falha em produção

Sintoma Causa real
Disco crescendo sem crescer o dado bloat: autovacuum não acompanha o ritmo de UPDATE
VACUUM roda e não libera nada uma transação idle in transaction antiga segura o xmin — nada anterior a ela pode ser limpo
"too many connections" falta de pooler + MaxOpenConns ilimitado no app (5. Go em Produção)
Query que era rápida ficou lenta estatísticas velhas, ou bloat deixando o índice inútil
Réplica cancelando queries max_standby_streaming_delay: query longa na réplica conflita com o replay do WAL
UPDATE travando o mundo ALTER TABLE esperando um ACCESS EXCLUSIVE — e enfileirando todos atrás dele
ALTER TABLE e a fila de locks

Um ALTER TABLE que espera lock bloqueia todas as queries que chegam depois, mesmo as que só leem. Migração em tabela grande sem lock_timeout é o jeito mais rápido de derrubar um serviço saudável. Sempre: SET lock_timeout = '3s'; antes do DDL.

Aprofundamento aplicado

A regra que precisa sobreviver a dois clientes

Considere cadastrar um e-mail único. Fazer SELECT, observar ausência e depois inserir permite que dois clientes passem pela verificação ao mesmo tempo. Uma restrição UNIQUE coloca a regra no ponto em que o banco arbitra as escritas concorrentes.

CREATE TABLE cadastro_estudo (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email text NOT NULL UNIQUE,
    saldo numeric(12,2) NOT NULL CHECK (saldo >= 0)
);

Este exemplo trata unicidade textual conforme a comparação configurada. Se o domínio considera maiúsculas e minúsculas equivalentes, a normalização e o índice precisam representar essa escolha. O NOT NULL também importa: um CHECK isolado pode aceitar resultado desconhecido causado por NULL. Referência: restrições.

Exercício: tente cadastrar o mesmo e-mail em duas sessões concorrentes. Resultado esperado: no máximo uma inserção confirma; a aplicação traduz a violação em conflito de negócio, sem depender de uma consulta preventiva.

Relacionado


Parte de MOC PostgreSQL · postgresql.org/docs · Novidades do 18

Buscar

Busca por título, seção e texto das notas