O que realmente acontece quando você conecta no PostgreSQL
sobre os fundamentos arquiteturais do banco de dados postgresql considere que a arquitetura dele não é intuitiva para quem vem de MySQL ou SQL Server. O PostgreSQL cria um processo separado para cada conexão. Isso significa que uma aplicação com 200 conexões ativas vai gerar 200 processos no sistema operacional. Cada um roda isolado, com sua própria área de memória. Isso é diferente do modelo baseado em threads que muitos outros bancos usam. A vantagem é estabilidade. Se uma consulta trava ou vaza memória, não arrasta o resto do sistema junto.
sobre os fundamentos arquiteturais do banco de dados postgresql considere o processo do postmaster
O postmaster é o processo pai que nasce quando você inicia o serviço. Ele nunca executa queries diretamente. Sua função é aceitars novas conexões e spawna filhotes. Quando um cliente conecta, o postmaster cria um backend para aquela sessão e entrega o pipe de comunicação. Esse design data dos primórdios do Postgres em Berkeley, nos anos 80, e continua essencial até hoje. Eu já vi equipes de produção tentarem otimizar performance ajustando variáveis de thread pool achando que era um banco multi-thread. Perda de tempo. O PostgreSQL não funciona assim. O controle de concorrência é feito por processos mesmo. O que você deve ajustar são os parâmetros de memória por processo, como work_mem e maintenance_work_mem, e não tentar forçar um modelo que não existe.
Memória: onde a confusão acontece
A gestão de memória do PostgreSQL é um dos pontos mais mal compreendidos. Existem shared buffers, que são uma região de memória compartilhada entre todos os processos, e memória privada por processo. Shared buffers guarda páginas de dados lidas do disco. Quando uma query precisa de dados que estão no cache, ela vai direto ali sem passar pelo sistema operacional. O problema prático é que work_mem é alocado por operação, não por conexão. Uma simple query com múltiplas sorts e hash joins pode consumir work_mem vezes o número de operações. Eu já vi servidores com 64GB de RAM entrarem em swap porque alguém deixou work_mem no padrão de 4MB e rodou relatórios com agrupamentos pesados em paralelo. A regra prática é: work_mem alto com poucas conexões. work_mem baixo com muitas conexões. Calcule isso antes de subir em produção.
Outro detalhe que ninguém menciona é o kernel shmem. O PostgreSQL usa System V shared memory ou POSIX semaphores. Em ambientes containerizados, especialmente Kubernetes, isso costuma ser a primeira coisa que quebra. Os limites padrão do kubelet para shm não são suficientes. Você precisa ajustar o campo shareMemory no spec do pod senão o postmaster simplesmente não inicia. Isso não aparece em nenhum tutorial básico.
Concorrência e MVCC
O PostgreSQL usa MVCC de forma diferente da maioria dos bancos. Em vez de manter versões antigas dos dados em um undo log separado, ele marca cada tupla com visibilidade usando snapshots. Cada transação vê uma imagem consistente dos dados no momento em que começou. Linhas atualizadas ou deletadas permanecem no disco até que um VACUUM as reaproveite. Isso tem uma consequência direta e muitas vezes dolorosa: table bloat. Se você tem muitos updates e deletes sem VACUUM regular, a tabela cresce semanticamente mas os blocos não são liberados. Eu tive um caso real com uma tabela de logs que atingiu 800GB em disco porque as atualizações eram constantes e o VACUUM automático não conseguia acompanhar. A solução foi rodar um VACUUM FULL manual, que rebuilda a tabela inteira e libera todo o espaço. O banco ficou com 120GB. O VACUUM FULL trava a tabela, então fiz isso em uma janela de manutenção com replicação standby recebendo os dados.
O autovacuum é projetado para evitar exatamente isso, mas ele tem thresholds baseados em porcentagem de linhas modificadas. Tabelas com padrão de escrita atípico podem precisar de configurações customizadas de autovacuum_vacuum_scale_factor e autovacuum_analyze_scale_factor. Não deixe no padrão se a tabela recebe volume alto de escrita.
👉 Clique no botão abaixo para saber mais sobre o assunto!
WAL e persistência
O Write Ahead Log é obrigatório no PostgreSQL. Nenhuma alteração é considerada cometida até que o WAL seja escrito em disco. Isso garante durabilidade ACID, mas também define a estratégia de recuperação. Quando o banco volta depois de uma queda, ele replaya o WAL desde o último checkpoint. O parâmetro fsync controla se o servidor confia no cache do disco ou força flush físico. Em produção real, fsync deve ficar ligado. Desligar economiza I/O mas coloca dados em risco real. O checkpoint_completion_target é outro parâmetro subutilizado. Por padrão, o PostgreSQL tenta completar o checkpoint o mais rápido possível, o que gera picos de escrita. Definir checkpoint_completion_target para 0.9 espalha aquela escrita por 90% do intervalo entre checkpoints. Isso reduz latência em cargas de trabalho sensíveis a I/O sem perder segurança.
Planner e executor
O custo das queries no PostgreSQL é estimado pelo planner baseado em estatísticas de distribuição. Se as estatísticas estão desatualizadas, o planner escolhe planos ruins. O comando ANALYZE atualiza essas estatísticas. O autovacuum roda ANALYZE automaticamente, mas novamente, tabelas com distribuição atípica podem precisar de stats_target elevado em colunas específicas usando ALTER TABLE ALTER COLUMN SET STATISTICS. Eu perdi duas noites debuggando uma query que de repente passou de 200ms para 45 segundos. A causa era um plano de execução que tinha mudado de index scan para sequential scan. As estatísticas da tabela haviam ficafo obsoletas porque nenhuma escrita significativa acontecia no período, o autovacuum não disparava e o planner estava operando com dados errados. Um ANALYZE manual resolveu. Desde então, em tabelas com padrões de acesso irregular, eu configuro monitoramento ativo de stale statistics usando pg_stat_user_tables e rodando ANALYZE programaticamente.
O parâmetro random_page_cost também merece atenção. O padrão é 4.0, assumindo disco magnético. Se você roda em SSD, colocar esse valor para 1.1 ou 1.2 faz o planner preferir index scans de forma mais agressiva. A diferença no plano de execução pode ser drástica para queries que fazem busca por chaves secundárias em tabelas grandes.
Tipos de armazenamento e tabelas toast
O PostgreSQL armazena valores grandes fora da linha principal usando TOAST. Textos, JSON extensos, campos bytea que excedem cerca de 2KB são comprimidos e empacotados em tabelas TOAST separadas. Isso mantém as linhas compactas e os index scans eficientes. Mas tem um custo: consultas que precisam recuperar esses campos grandes pagam I/O extra. Se você tem muitos campos text grandes e raramente os lê integralmente, considere usar JSONB em vez de TEXT puro. O JSONB permite queries parciais sem despacificiar o campo inteiro. Também é importante notar que o PostgreSQL suporta diferentes estratégias de particionamento. Desde a versão 10, o particionamento declarativo é estável e elimina a necessidade de triggers manuais que muita gente ainda implementa. Particionar por range ou list reduz o tamanho das árvores de índice e permite que o planner faça partition pruning, descartando partições inteiras antes de executar a query.
Extensões e ecossistema
A arquitetura extensível é um dos diferenciais reais do PostgreSQL. Você pode adicionar novos tipos de dados, funções, indexadores e até linguagens procedurais sem modificar o core. Extensions como PostGIS, pg_stat_statements e Citus são carregadas por comando CREATE EXTENSION e registram seus componentes no catálogo do banco. pg_stat_statements é praticamente obrigatória em qualquer produção séria. Ela rastrea todas as queries executadas, agregando tempo, linhas retornadas e chamadas. Sem ela, você está voando cego sobre performance. A desvantagem é que ela consome memória compartilhada proporcional ao número de queries distintas, então em sistemas com milhões de queries únicas diferentes, o crescimento pode ser significativo.
Limitações reais que ninguém enfatiza
O PostgreSQL não escala horizontalmente de forma nativa. Replicação streaming existe e é sólida, mas write scaling exige ferramentas externas como Citus ouPgPool-II com divisão de carga. Para aplicações distribuídas globais, isso adiciona complexidade operacional que muitos times subestimam. Se seu caso de uso exige múltiplos masters em regiões diferentes, o PostgreSQL não é a escolha mais direta. CockroachDB ou PostgreSQL com Citus em mode coordinated são alternativas, mas ambas trazem trade-offs próprios. O locks do PostgreSQL também seguem uma política rigorosa. SELECT sem modificação não bloqueia writes e vice-versa na maioria dos casos, mas operações como CREATE INDEX CONCURRENTLY e ALTER TABLE LOCK MODE EXCLUSIVE podem prender sessões inteiras se não forem planejadas. Eu já vi um deploy de schema change bloquear uma API inteira por 40 minutos porque o ALTER adicionou uma coluna com NOT NULL em uma tabela de 2 bilhões de linhas sem antes preparar o ambiente adequadamente.
A escolha do motor de indexação também importa mais do que se imagina. B-tree é o padrão e funciona bem para a maioria dos casos. GiST e SP-GiST atendem buscas geométricas e full-text. BRIN é eficiente para séries temporais e dados naturalmente ordenados, ocupando fração do espaço de um B-tree. GIN é o caminho para JSONB e arrays. Usar o tipo errado de índice é um erro comum que gera queries lentas sem motivo aparente.