O que é o PostgreSQL e por que startups dependem dele

O PostgreSQL é um banco de dados relacional open source com mais de 35 anos de desenvolvimento ativo. É robusto, extensível e suporta desde JSON até tipos geoespaciais. Por isso, virou a escolha padrão de startups que querem um banco sério sem pagar por Oracle ou SQL Server.

O problema é que PostgreSQL é poderoso e, justamente por isso, tem muitas alavancas. Mexer na alavanca errada em produção pode derrubar tudo. Startups costumam crescer rápido e o banco que funcionava bem com 10 mil registros começa a travar com 10 milhões.

Este guia cobre os pontos críticos que separam um banco saudável em produção de um banco que vira alerta às 3 da manhã. Não é teoria: são situações reais que aparecem quando a startup começa a escalar.

Como o PostgreSQL funciona por dentro

Entender o básico da arquitetura ajuda a tomar decisões melhores. O Postgres usa um modelo de MVCC (Multi-Version Concurrency Control): quando você atualiza uma linha, ele não apaga a versão antiga imediatamente. Cria uma nova versão e marca a antiga como "morta". Isso permite leituras concorrentes sem locks, mas gera acumulo de dados mortos chamados de dead tuples.

O processo AUTOVACUUM é responsável por limpar esses dados mortos periodicamente. Se ele não rodar com frequência suficiente (ou for desativado por engano), a tabela cresce desnecessariamente e as queries ficam mais lentas com o tempo.

Outro ponto importante: o Postgres usa um buffer pool na memória para cachear páginas de dados. Quanto mais RAM você der ao parâmetro shared_buffers, menos I/O de disco acontece. A regra geral é alocar cerca de 25% da RAM disponível para esse parâmetro em servidores dedicados.

Principais armadilhas em produção

Startups repetem os mesmos erros com PostgreSQL. Conhecer esses padrões economiza horas de depuração:

  • Falta de índices nas colunas de filtro: queries com WHERE em colunas sem índice fazem full table scan. Com milhões de registros, isso trava tudo.
  • Connection pooling ausente: cada conexão nova ao Postgres custa memória e CPU. Sem um pooler como o PgBouncer, aplicações que abrem muitas conexões simultâneas esgotam os recursos.
  • Backups não testados: fazer backup sem nunca testar o restore é o mesmo que não ter backup. O restore real precisa ser validado periodicamente.
  • Migrations sem cuidado em produção: um ALTER TABLE ADD COLUMN NOT NULL sem default em tabela grande pode bloquear todas as operações por minutos.
  • Autovacuum mal configurado: tabelas com muitos updates (como filas de jobs) acumulam dead tuples se o autovacuum não for ajustado para rodar mais agressivamente.
⚠️
Atenção

Nunca rode VACUUM FULL em tabelas grandes durante horário de pico. Ele adquire lock exclusivo e bloqueia todas as operações na tabela enquanto roda.

Como começar: configuração mínima segura

Se você está subindo um Postgres novo para produção, faça isso antes de colocar carga real:

Passo 1 - Ajuste o PostgreSQL.conf básico:

shared_buffers = 256MB          # 25% da RAM disponível
work_mem = 4MB                  # por operação de sort/hash
maintenance_work_mem = 64MB     # para VACUUM e CREATE ÍNDEX
max_connections = 100           # ajuste conforme sua app
autovacuum = on                 # nunca desative isso

Passo 2 - Instale o PgBouncer para connection pooling:

sudo apt install pgbouncer
# configure /etc/pgbouncer/pgbouncer.ini
# pool_mode = transaction (mais eficiente para a maioria dos casos)

Passo 3 - Configure backups automáticos com pg_dump ou WAL-G:

# Backup simples diário com pg_dump
0 2 * * * pg_dump -U postgres meu_banco | gzip > /backups/$(date +%Y%m%d).sql.gz

Passo 4 - Habilite a extensão pg_stat_statements para monitorar queries lentas:

-- no PostgreSQL.conf:
shared_preload_libraries = 'pg_stat_statements'

-- no banco:
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- para ver as queries mais lentas:
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

Exemplo prático: encontrando e corrigindo uma query lenta

Cenário comum: a tela de listagem de pedidos da sua startup começa a demorar 8 segundos para carregar. O banco tem 2 milhões de registros na tabela orders.

Primeiro, use o EXPLAIN ANALYZE para entender o que o banco está fazendo:

EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 12345
AND status = 'pending'
ORDER BY created_at DESC
LIMIT 20;

Se o resultado mostrar Seq Scan em vez de Índex Scan, o banco está lendo a tabela inteira. A solução é criar um índice composto nas colunas do filtro:

-- Crie o índice de forma concorrente para não bloquear a tabela
CREATE ÍNDEX CONCURRENTLY idx_orders_user_status
ON orders (user_id, status, created_at DESC);

Após criar o índice, rode o EXPLAIN ANALYZE de novo. O plano deve mudar para Índex Scan e a query vai de 8 segundos para menos de 100ms na maioria dos casos.

💡
Dica

Sempre use CREATE ÍNDEX CONCURRENTLY em produção. Ele demora um pouco mais para criar, mas não bloqueia operações na tabela enquanto roda.

Comparação com alternativas

PostgreSQL não é o único banco relacional. Entender quando usar cada um ajuda a tomar a decisão certa:

MySQL/MariaDB: mais simples de configurar, amplamente suportado em hospedagens compartilhadas. Perde para o Postgres em tipos avançados (JSON nativo, arrays, ranges) e em conformidade com o padrão SQL. Boa escolha para apps simples que precisam de hospedagem barata.

SQLite: excelente para desenvolvimento local, apps mobile e projetos pequenos. Não suporta múltiplas conexões simultâneas de escrita. Não é opção para produção com múltiplos usuários concorrentes.

MongoDB: banco de documentos, sem esquema fixo. Útil quando os dados são realmente não estruturados. O Postgres com JSONB resolve a maioria dos casos de uso do Mongo com a vantagem de ter transações ACID e joins reais.

Para a maioria das startups, PostgreSQL é a escolha certa desde o início: você não vai precisar migrar quando crescer.

Pontos positivos e limitações

Pontos positivos:

  • Open source e gratuito, sem licenciamento por núcleo ou usuário
  • ACID completo: transações confiáveis mesmo em caso de falha
  • Tipos avançados: JSONB, arrays, UUID, tipos geométricos, full-text search nativo
  • Extensível: PostGIS para geodados, TimescaleDB para séries temporais, pgvector para IA
  • Comunidade enorme, documentação excelente

Limitações reais:

  • Escala vertical mais facilmente do que horizontal. Sharding é complexo.
  • Configuração padrão é conservadora: precisa de tuning para produção de verdade
  • Replicação e failover automático exigem ferramentas extras (Patroni, repmgr)
  • Não é a melhor escolha para workloads 100% analíticos pesados: aí o BigQuery ou Redshift ganham
⚠️
Atenção

O PostgreSQL padrão não tem failover automático. Se o servidor cair, alguém precisa promover a réplica manualmente. Considere Patroni ou um serviço gerenciado (RDS, Supabase, Néon) se alta disponibilidade for crítica.

Casos de uso reais

SaaS B2B com múltiplos clientes: cada cliente tem seus dados isolados por tenant_id. O Postgres lida bem com Row Level Security para garantir isolamento sem criar um banco por cliente.

E-commerce com catálogo variado: produtos com atributos diferentes (eletrónicos vs. roupas) ficam bem em JSONB. Você tem esquema fixo nas colunas principais e flexibilidade nos atributos.

Sistema financeiro: transações ACID garantem que um débito sempre tem um crédito correspondente, mesmo em caso de falha no meio da operação.

API com busca textual: o tsvector e tsquery do Postgres oferecem full-text search razoável sem precisar de Elasticsearch para casos simples.

Dicas e boas práticas

💡
Dica

Ative a extensão pg_stat_statements logo no primeiro dia. Ela registra todas as queries executadas e é a melhor ferramenta para encontrar gargalos depois.

🚀
Pro tip

Use EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) para ver não só o plano de execução, mas também quantas páginas foram lidas do cache vs. do disco. Alto número de shared_read indica que o shared_buffers está pequeno.

🔴
Cuidado

Nunca use SELECT * em tabelas grandes em produção. Buscar todas as colunas aumenta o tráfego de rede e impede que o banco use índex-only scans. Sempre especifique as colunas que você precisa.

💡
Dica

Para migrations em produção em tabelas grandes, prefira adicionar colunas com valor NULL e depois fazer o backfill em lotes pequenos. Adicionar coluna NOT NULL sem default bloqueia a tabela inteira durante a migration.

Vale a pena?

Para startups, a resposta é quase sempre sim. O PostgreSQL é gratuito, tem suporte excelente em todos os provedores de cloud (AWS RDS, Google Cloud SQL, Azure Database, Supabase, Néon) e escala bem até o ponto em que você vai precisar de uma equipe de DBA dedicada de qualquer jeito.

O investimento real não é no banco em si, mas em aprender a operá-lo corretamente. Dedicar alguns dias para entender índices, autovacuum e connection pooling vai salvar muitas noites de incidente.

Se você ainda está no início, comece com um serviço gerenciado como o Supabase ou o Néon: eles cuidam de backups, failover e atualizações enquanto você foca no produto. Quando crescer o suficiente para justificar, migra para uma instância gerenciada no seu cloud provider.