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
UPDATEnã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_uringno 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_upgradepreservando 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 locksUm 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
- 1. MySQL
- 1. Oracle Database
- 1. PostgreSQL vs MySQL vs Oracle
- 3. MVCC e Isolamento na Prática
- 5. Índices
- RDS
- Aurora
Parte de MOC PostgreSQL · postgresql.org/docs · Novidades do 18