Restaurar backup do PostgreSQL

Saber restaurar um backup é tão importante quanto criá-lo. O restore é o momento mais crítico, geralmente em uma crise, com pressão de tempo. Treinar o processo antes da emergência é essencial. Este guia cobre restore completo, parcial, para banco limpo e migração entre servidores.

Restaurar backup SQL (formato plain)

Para backups gerados com pg_dump --format=plain (padrão):

bash
# Cenário 1: Restaurar em banco existente (sobrescrever dados)
# ATENÇÃO: isso apaga os dados atuais do banco!

# Opção A: Dropar e recriar o banco antes do restore
sudo -u postgres psql << 'EOF'
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'minha_db' AND pid <> pg_backend_pid();

DROP DATABASE IF EXISTS minha_db;
CREATE DATABASE minha_db OWNER minha_app;
EOF

# Restaurar o backup (arquivo .sql ou .sql.gz)
gunzip -c /var/backups/minha_db_20260607.sql.gz \
  | sudo -u postgres psql minha_db

# Cenário 2: Restaurar em banco de teste (sem afetar produção)
sudo -u postgres createdb -O minha_app minha_db_restore_test
gunzip -c /var/backups/minha_db_20260607.sql.gz \
  | sudo -u postgres psql minha_db_restore_test

# Verificar restore:
sudo -u postgres psql minha_db_restore_test -c "
SELECT tablename, n_live_tup
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;
"

Restaurar backup custom com pg_restore

Backups no formato custom (pg_dump -Fc) têm mais opções de restore:

bash
# Backup custom (gerado com pg_dump -Fc):
pg_dump -Fc minha_db > backup.dump

# Restaurar todo o banco:
pg_restore -d minha_db -U minha_app -h localhost backup.dump

# Restaurar apenas tabelas específicas:
pg_restore -d minha_db -t usuarios -t pedidos backup.dump

# Restaurar apenas o schema (sem dados):
pg_restore -d minha_db --schema-only backup.dump

# Restaurar apenas os dados (schema já existe):
pg_restore -d minha_db --data-only backup.dump

# Restaurar em paralelo (mais rápido para bancos grandes):
pg_restore -d minha_db -j 4 backup.dump  # 4 workers paralelos

# Ver conteúdo do backup sem restaurar:
pg_restore -l backup.dump | head -50

# Verbose para debug:
pg_restore -d minha_db -v backup.dump 2>&1 | tail -30

Migrar banco de dados entre servidores

Copiar banco de produção para novo servidor sem arquivo intermediário:

bash
# Migração em pipeline (sem arquivo intermediário) — mais rápido
# Fonte: VPS_ANTIGA | Destino: VPS_NOVA

# No servidor de destino (VPS nova):
# 1. Criar banco e usuário destino
sudo -u postgres psql << 'EOF'
CREATE USER minha_app WITH PASSWORD 'senha_app';
CREATE DATABASE minha_db OWNER minha_app;
EOF

# 2. Executar migração em pipeline (SSH + pg_dump + psql):
ssh usuario@VPS_ANTIGA \
  "PGPASSWORD=senha pg_dump -U minha_app minha_db" \
  | PGPASSWORD=senha psql -U minha_app -h localhost minha_db

# Alternativa com compressão (para conexões lentas):
ssh usuario@VPS_ANTIGA \
  "PGPASSWORD=senha pg_dump -U minha_app minha_db | gzip" \
  | gunzip | PGPASSWORD=senha psql -U minha_app minha_db

# Verificar integridade após migração:
# No destino:
psql -U minha_app minha_db -c "SELECT count(*) FROM usuarios;"
# Comparar com a fonte:
ssh usuario@VPS_ANTIGA "psql -U minha_app minha_db -c 'SELECT count(*) FROM usuarios;'"

Restaurar uma tabela específica sem afetar o restante

Recupere apenas a tabela que foi corrompida ou deletada acidentalmente:

bash
# Cenário: DELETE acidental na tabela pedidos
# Backup disponível: /var/backups/minha_db_20260607.sql.gz

# 1. Extrair apenas a tabela pedidos do backup para arquivo separado
pg_restore -t pedidos -f /tmp/pedidos_only.sql /var/backups/minha_db.dump
# (apenas para backups formato custom -Fc)

# Para backup formato plain (SQL):
# Extrair manualmente com grep/sed entre COPY pedidos e \.
# Ou: restaurar em banco temporário e copiar de lá

# 2. Restaurar em banco temporário
sudo -u postgres createdb minha_db_temp
gunzip -c /var/backups/minha_db_20260607.sql.gz | sudo -u postgres psql minha_db_temp

# 3. Copiar a tabela do backup para produção usando INSERT
sudo -u postgres psql << 'EOF'
-- Truncar tabela atual (se corrompida)
TRUNCATE TABLE minha_db.public.pedidos;

-- Copiar do backup
INSERT INTO minha_db.public.pedidos
SELECT * FROM minha_db_temp.public.pedidos;

-- Verificar
SELECT count(*) FROM minha_db.public.pedidos;
EOF

# 4. Limpar banco temporário
sudo -u postgres dropdb minha_db_temp

Point-in-Time Recovery (PITR)

Restaure o banco para um momento específico no tempo com WAL archiving:

bash
# PITR requer configuração prévia de WAL archiving:
# postgresql.conf:
# archive_mode = on
# archive_command = 'aws s3 cp %p s3://meu-bucket/wal/%f'
# restore_command = 'aws s3 cp s3://meu-bucket/wal/%f %p'

# Para fazer PITR após DELETE acidental às 14:32:

# 1. Parar PostgreSQL
sudo systemctl stop postgresql

# 2. Restaurar pg_basebackup (snapshot mais recente antes do incidente)
sudo rm -rf /var/lib/postgresql/17/main/
sudo -u postgres pg_basebackup -D /var/lib/postgresql/17/main -Fp

# 3. Configurar recovery
sudo -u postgres cat > /var/lib/postgresql/17/main/postgresql.auto.conf << EOF
restore_command = 'aws s3 cp s3://meu-bucket/wal/%f %p'
recovery_target_time = '2026-06-07 14:30:00 America/Sao_Paulo'
recovery_target_action = 'promote'
EOF
sudo -u postgres touch /var/lib/postgresql/17/main/recovery.signal

# 4. Iniciar PostgreSQL — ele aplica WAL até o momento especificado
sudo systemctl start postgresql

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

pg_restore com -j 4 (4 workers paralelos): ~20-60 minutos para 100 GB, dependendo do hardware e do tipo de dados. O gargalo principal é o IO de escrita no disco destino. Para servidores com SSD NVMe: mais próximo de 20 min. Para HDD: pode passar de 60 min. Bancos com muitos índices levam mais tempo (índices são criados após a inserção dos dados).

Depende do tipo de erro. Erros esperados e seguros: "already exists" para objetos que já existem, permissões de superusuário (extensões como uuid-ossp). Erros problemáticos: "relation does not exist" ao inserir dados (schema incompleto), erros de constraint (dados inconsistentes). Sempre verifique a contagem de linhas das tabelas principais após o restore.

pg_dump/pg_restore são forward-compatible: um backup gerado no PostgreSQL 15 pode ser restaurado no PostgreSQL 17 sem problemas. A compatibilidade reversa não é garantida (backup do 17 no 15 pode falhar por features novas). Para upgrade major de versão: pg_dump no servidor antigo + pg_restore no novo é o método mais seguro e portátil.

Para .sql.gz: gunzip -t arquivo.sql.gz (testa a integridade da compressão sem descompactar). Para formato custom: pg_restore -l arquivo.dump > /dev/null (verifica o header do arquivo). Para backup completo: pg_restore --dry-run -d BANCO arquivo.dump (simula o restore sem aplicar). Armazene checksum SHA256 junto com o backup e verifique antes do restore.

Opções sem expandir o disco: (1) restaurar em servidor temporário e copiar dados via INSERT SELECT com dblink; (2) usar pg_restore com --section=data apenas para tabelas críticas; (3) restaurar para banco com tablespace em outro disco; (4) comprimir tabelas não essenciais com pg_dump e remover para liberar espaço. Lição: tenha sempre 20% de espaço livre em produção.