Aula 23: Performance — EXPLAIN ANALYZE, autovacuum e tuning do postgresql.conf

Aula 23: Performance — EXPLAIN ANALYZE, autovacuum e tuning do postgresql.conf

Bem-vindo à vigésima terceira aula do nosso curso PostgreSQL — Do Zero ao Avançado. Nesta etapa, você vai mergulhar em um dos temas mais críticos e, ao mesmo tempo, mais recompensadores para qualquer profissional de banco de dados: a performance. Otimizar o desempenho do PostgreSQL não é apenas uma tarefa de “apertar botões”; trata-se de compreender profundamente como o planejador de consultas funciona, como os processos de manutenção automática interferem no throughput e como cada parâmetro do arquivo postgresql.conf pode influenciar diretamente a latência das suas aplicações. Ao dominar o EXPLAIN ANALYZE, o autovacuum e o ajuste fino do servidor, você será capaz de diagnosticar gargalos, evitar degradações silenciosas e extrair o máximo do hardware disponível.

Esta aula foi projetada para profissionais de TI, engenheiros de infraestrutura, DBAs e desenvolvedores que já concluíram as aulas anteriores do curso e possuem familiaridade com SQL, administração básica do PostgreSQL e conceitos de sistemas operacionais. Você aprenderá a interpretar planos de execução detalhados, identificar operações custosas como Seq Scan e Sort, compreender o papel vital do autovacuum na prevenção do inchaço de tabelas (bloat) e no gerenciamento de transações, além de aplicar um conjunto de parâmetros de tuning validados em ambientes de produção reais. Tudo isso com instruções passo a passo, comandos completos e saídas esperadas — sem atalhos ou resumos vagos.

Ao final desta aula, você terá condições de executar uma auditoria de performance em qualquer instância PostgreSQL, propor e aplicar melhorias sustentáveis, monitorar o comportamento do autovacuum e ajustar o postgresql.conf com confiança. Em nossos projetos na JRT Technology Solutions, nossos especialistas utilizam diariamente as técnicas que você verá a seguir para resolver incidentes de lentidão e garantir acordos de nível de serviço (SLAs) rigorosos. A JRT Technology Solutions também oferece treinamentos, implementação e suporte completo em PostgreSQL para empresas que buscam alta disponibilidade e máxima performance.

Prepare-se para uma aula densa, prática e extremamente aplicável. Recomendamos que você execute cada comando em um ambiente de testes — preferencialmente uma máquina virtual ou container dedicado — para internalizar o conhecimento. Vamos começar.

O que você vai aprender nesta aula

Esta aula foi estruturada para fornecer um caminho progressivo e completo rumo à otimização de performance no PostgreSQL. Os objetivos de aprendizagem são:

  • Compreender a arquitetura do planejador e do executor de consultas do PostgreSQL, e como o custo é calculado.
  • Dominar o uso do comando EXPLAIN e, principalmente, do EXPLAIN ANALYZE para diagnosticar gargalos reais de execução.
  • Identificar operações comuns em planos de execução, como Seq Scan, Index Scan, Bitmap Heap Scan, Nested Loop, Hash Join e Merge Join.
  • Entender o funcionamento do autovacuum, seus parâmetros de configuração e como monitorá-lo de forma efetiva.
  • Realizar o tuning do arquivo postgresql.conf nos sistemas operacionais Ubuntu/Debian e CentOS/RHEL/Rocky Linux.
  • Verificar e testar cada ajuste aplicado, com comandos de validação e saídas esperadas.
  • Resolver os erros mais comuns relacionados a performance, autovacuum e configuração do servidor.

Pré-requisitos e Ambiente

Antes de iniciar esta aula, é fundamental que você tenha concluído as aulas anteriores do curso, especialmente aquelas sobre instalação do PostgreSQL, gerenciamento de bancos de dados, usuários, permissões e administração básica via psql e ferramentas de linha de comando. Além disso, recomendamos que você tenha acesso a um servidor Linux com PostgreSQL já instalado e funcionando. Para este guia, consideraremos duas famílias de distribuições principais: Ubuntu/Debian (utilizando o gerenciador de pacotes apt) e CentOS/RHEL/Rocky Linux (utilizando dnf ou yum).

Você precisará de:

  • Uma instância PostgreSQL versão 14 ou superior (recomendamos a versão 15 ou 16 para aproveitar as últimas melhorias de performance).
  • Acesso ao usuário postgres ou a um usuário com privilégios de superusuário no banco.
  • Permissões de superusuário no sistema operacional (root ou sudo) para editar o arquivo postgresql.conf e reiniciar o serviço.
  • Ferramentas de linha de comando: psql, pgbench (opcional, para testes de carga), top, htop, iostat e vmstat.
  • Um banco de dados de testes com tabelas populadas para executar os exemplos de EXPLAIN ANALYZE — você pode criar um banco simples com algumas tabelas e inserir dados fictícios.

Os comandos mostrados foram testados em ambientes com PostgreSQL 15 e 16, mas a grande maioria dos conceitos e parâmetros é compatível com versões anteriores (a partir da 11). Se você ainda não possui um ambiente preparado, recomendamos criar uma máquina virtual limpa ou um container Docker com a imagem oficial do PostgreSQL. Em nossos laboratórios na JRT Technology Solutions, costumamos usar containers para isolar cenários de teste e evitar interferências entre versões.

Fundamentos de Performance no PostgreSQL: O Planejador e o Executor

Para entender performance no PostgreSQL, é essencial compreender como uma consulta SQL é processada internamente. O fluxo básico envolve quatro etapas principais: parsing (análise sintática), rewriting (reescrita de regras), planning (planejamento) e execution (execução). O planejador é o componente responsável por gerar um plano de execução eficiente com base em estatísticas coletadas das tabelas e índices. Essas estatísticas são mantidas pelo processo autovacuum e pelo comando ANALYZE.

O planejador atribui um custo a cada operação possível — varredura sequencial (Seq Scan), varredura de índice (Index Scan), junções (Hash Join, Nested Loop, Merge Join) — e escolhe o plano com menor custo estimado. Esse custo é influenciado por parâmetros como seq_page_cost, random_page_cost, cpu_tuple_cost e cpu_operator_cost, que você ajustará mais adiante. Quando as estatísticas estão desatualizadas, o planejador pode tomar decisões ruins, resultando em planos subótimos e lentidão.

O EXPLAIN permite visualizar o plano escolhido pelo planejador sem executar a consulta, enquanto o EXPLAIN ANALYZE executa a consulta de fato e coleta métricas reais de tempo e custo. A diferença entre custo estimado e custo real é um dos primeiros sinais de que as estatísticas precisam ser atualizadas. Em nossa experiência na JRT Technology Solutions, a maioria dos problemas de performance relatados por clientes está relacionada a estatísticas desatualizadas ou falta de índices adequados — e não a erros de configuração do servidor.

Outro conceito fundamental é o bloat (inchaço). Devido ao modelo MVCC (Multi-Version Concurrency Control), o PostgreSQL mantém múltiplas versões de linhas para suportar transações concorrentes. Linhas mortas (dead tuples) são removidas pelo VACUUM, mas se o autovacuum não estiver configurado corretamente, o inchaço pode crescer descontroladamente, aumentando o I/O e degradando a performance. Por isso, o autovacuum é peça-chave nesta aula.

Durante o restante da aula, você aplicará esses conceitos na prática. Vamos começar criando um ambiente de testes com tabelas e dados, para que você possa reproduzir todos os exemplos de EXPLAIN ANALYZE e tuning.

Passo a Passo: Preparando o Banco de Dados para Testes de Performance

Antes de analisar planos de execução e ajustar o servidor, precisamos de um banco de dados com volume de dados suficiente para que as diferenças de performance sejam perceptíveis. Execute os comandos abaixo no terminal, preferencialmente como usuário postgres ou com privilégios equivalentes. Vamos criar um banco chamado loja, uma tabela clientes com 1 milhão de registros e uma tabela pedidos com 5 milhões de registros, além de alguns índices estratégicos.

  1. Acesse o shell do PostgreSQL com o comando sudo -u postgres psql (ou psql -U postgres se você estiver logado como usuário do sistema com autenticação peer).
  2. Crie o banco de dados loja com o comando CREATE DATABASE loja; — aguarde a conclusão.
  3. Conecte-se ao banco loja com \c loja ou saindo e entrando novamente.
  4. Execute os comandos SQL abaixo para criação das tabelas e inserção de dados. O uso de generate_series é uma forma eficiente de popular tabelas rapidamente.
  5. Após a criação, atualize as estatísticas com o comando ANALYZE para garantir que o planejador tenha métricas precisas.

Os comandos completos para criação e população do banco são apresentados a seguir. Preste atenção aos comentários em cada linha, pois eles explicam a finalidade de cada operação e as opções utilizadas.

-- Conectar ao PostgreSQL como superusuário (no terminal do SO)
sudo -u postgres psql

-- Dentro do psql, criar o banco de dados de testes
CREATE DATABASE loja;

-- Conectar ao banco loja
\c loja

-- Criar a tabela de clientes com 1 milhão de registros
CREATE TABLE clientes (
    id_cliente serial PRIMARY KEY,
    nome text NOT NULL,
    email text UNIQUE NOT NULL,
    cidade text NOT NULL,
    data_cadastro date NOT NULL
);

-- Popular a tabela clientes usando generate_series
INSERT INTO clientes (nome, email, cidade, data_cadastro)
SELECT
    'Cliente ' || g,
    'cliente' || g || '@exemplo.com',
    (ARRAY['São Paulo', 'Rio de Janeiro', 'Belo Horizonte', 'Curitiba', 'Porto Alegre'])[floor(random() * 5 + 1)::int],
    DATE '2020-01-01' + (random() * 1825)::int
FROM generate_series(1, 1000000) AS g;

-- Criar a tabela de pedidos com 5 milhões de registros
CREATE TABLE pedidos (
    id_pedido bigserial PRIMARY KEY,
    id_cliente integer REFERENCES clientes(id_cliente),
    valor_total numeric(12,2) NOT NULL,
    status text NOT NULL,
    data_pedido timestamp NOT NULL
);

-- Popular a tabela pedidos
INSERT INTO pedidos (id_cliente, valor_total, status, data_pedido)
SELECT
    (random() * 999999 + 1)::int,
    (random() * 10000 + 10)::numeric(12,2),
    (ARRAY['PENDENTE', 'PAGO', 'ENVIADO', 'ENTREGUE', 'CANCELADO'])[floor(random() * 5 + 1)::int],
    TIMESTAMP '2023-01-01 00:00:00' + (random() * (EXTRACT(EPOCH FROM (TIMESTAMP '2026-09-11 00:00:00' - TIMESTAMP '2023-01-01 00:00:00'))) * interval '1 second')
FROM generate_series(1, 5000000) AS g;

-- Criar índices estratégicos para simular cenários reais
CREATE INDEX idx_pedidos_id_cliente ON pedidos(id_cliente);
CREATE INDEX idx_pedidos_status ON pedidos(status);
CREATE INDEX idx_pedidos_data_pedido ON pedidos(data_pedido);
CREATE INDEX idx_clientes_cidade ON clientes(cidade);

-- Atualizar as estatísticas das tabelas
ANALYZE clientes;
ANALYZE pedidos;

A execução dos comandos acima pode levar alguns minutos, dependendo do hardware da sua máquina. A tabela pedidos com 5 milhões de linhas e seus índices ocupará cerca de 1 a 2 GB em disco. Esse volume é suficiente para que os tempos de execução das consultas sejam relevantes e para que as diferenças entre planos de execução fiquem evidentes. Se você preferir um ambiente mais leve, pode reduzir os valores de generate_series para 100 mil e 500 mil, respectivamente, mas lembre-se de que consultas muito rápidas podem mascarar os efeitos que vamos analisar.

Após a criação e o ANALYZE, verifique se as estatísticas foram coletadas corretamente consultando a tabela pg_stats. O comando abaixo mostra as estatísticas da coluna cidade da tabela clientes, incluindo o número de valores distintos e os valores mais comuns. Essas informações são exatamente o que o planejador utiliza para estimar a seletividade e, consequentemente, escolher o melhor plano.

-- Verificar estatísticas coletadas para a coluna cidade da tabela clientes
SELECT
    attname AS coluna,
    n_distinct AS num_valores_distintos,
    most_common_vals AS valores_mais_comuns,
    most_common_freqs AS frequencias
FROM pg_stats
WHERE schemaname = 'public'
  AND tablename = 'clientes'
  AND attname = 'cidade';

A saída esperada para esse comando variará de acordo com a aleatoriedade da inserção, mas deve mostrar cinco valores distintos (ou próximo disso) para a coluna cidade, indicando que o ANALYZE conseguiu identificar corretamente a cardinalidade. Se o valor de n_distinct for muito diferente de 5, execute novamente o ANALYZE ou verifique se os dados foram inseridos corretamente.

   coluna   | num_valores_distintos |                    valores_mais_comuns                    |         frequencias
------------+-----------------------+-----------------------------------------------------------+-------------------------------
 cidade     |                     5 | {"Belo Horizonte","Curitiba","Porto Alegre","Rio de Janeiro","São Paulo"} | {0.1999,0.2003,0.1997,0.2000,0.2001}
(1 linha)

Com o ambiente de testes preparado, você está pronto para explorar o EXPLAIN ANALYZE e interpretar planos de execução de forma profissional. Na próxima seção, vamos executar consultas típicas de aplicações e dissecar cada nó do plano.

Dominando o EXPLAIN ANALYZE: Interpretando Planos de Execução

O comando EXPLAIN no PostgreSQL oferece várias opções que controlam o nível de detalhe exibido. As opções mais utilizadas incluem ANALYZE (executa a consulta e coleta métricas reais), BUFFERS (mostra o número de blocos lidos do cache e do disco), VERBOSE (exibe informações adicionais sobre colunas e expressões), FORMAT JSON (gera saída estruturada em JSON) e SETTINGS (inclui parâmetros de configuração relevantes). Para diagnóstico de performance, a combinação EXPLAIN (ANALYZE, BUFFERS) é a mais poderosa, pois revela não apenas o tempo e o custo, mas também o volume de I/O realizado.

Considere a seguinte consulta simples que busca todos os pedidos de um cliente específico. Sem um índice adequado na coluna id_cliente, o planejador seria forçado a fazer uma varredura sequencial na tabela pedidos, o que seria extremamente lento. Como criamos o índice idx_pedidos_id_cliente, esperamos que o plano utilize um Index Scan ou Bitmap Index Scan. Execute a consulta abaixo com EXPLAIN ANALYZE e analise a saída.

-- Plano de execução para pedidos de um cliente específico
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT id_pedido, valor_total, status, data_pedido
FROM pedidos
WHERE id_cliente = 12345;

A saída esperada deve ser semelhante ao bloco abaixo, embora os valores de tempo e buffers possam variar de acordo com o hardware e a versão do PostgreSQL. O importante é observar a estrutura do plano e as métricas reais. A presença de Index Scan using idx_pedidos_id_cliente indica que o índice foi utilizado corretamente, e o campo Buffers: shared hit=… mostra que os blocos de dados foram lidos do cache compartilhado (memória), sem necessidade de acesso a disco. Se aparecer read=…, significa que parte dos dados veio do sistema de arquivos, o que é esperado na primeira execução após um reinício ou quando a tabela não está em cache.

                                                     QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
 Index Scan using idx_pedidos_id_cliente on pedidos  (cost=0.43..89.35 rows=50 width=28) (actual time=0.012..0.045 rows=5 loops=1)
   Index Cond: (id_cliente = 12345)
   Buffers: shared hit=7
 Planning Time: 0.087 ms
 Execution Time: 0.062 ms
(4 linhas)

Interpretar esse plano é fundamental. O nó raiz é Index Scan using idx_pedidos_id_cliente on pedidos, indicando que o PostgreSQL percorreu o índice idx_pedidos_id_cliente para localizar as linhas correspondentes ao id_cliente = 12345. O custo estimado é apresentado em duas partes: cost=0.43..89.35 — o primeiro valor representa o custo de inicialização do nó (por exemplo, o custo para descer na árvore do índice), e o segundo valor é o custo total estimado para retornar todas as linhas. O número rows=50 é a estimativa de linhas retornadas, enquanto actual … rows=5 mostra que apenas 5 linhas foram realmente encontradas. Essa discrepância entre estimativa e realidade é normal quando as estatísticas não são perfeitamente precisas ou quando a distribuição dos dados é enviesada.

Agora, vamos forçar uma varredura sequencial para comparar a performance e entender o impacto de um plano ruim. Execute a consulta abaixo desabilitando temporariamente o uso de índices com o parâmetro enable_indexscan = off. Isso fará com que o planejador escolha um Seq Scan na tabela pedidos, que possui 5 milhões de linhas. O tempo de execução será ordens de magnitude maior, demonstrando por que a escolha correta do plano é vital.

-- Forçar varredura sequencial para demonstrar a diferença de performance
SET enable_indexscan = off;
SET enable_bitmapscan = off;

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT id_pedido, valor_total, status, data_pedido
FROM pedidos
WHERE id_cliente = 12345;

-- Reabilitar os índices
RESET enable_indexscan;
RESET enable_bitmapscan;

A saída esperada mostrará um nó Seq Scan on pedidos, com tempo de execução muito maior e muitos buffers lidos. Em nossos testes, a consulta com índice levou menos de 0,1 ms, enquanto a varredura sequencial levou cerca de 50 a 100 ms — uma diferença de mais de 1000 vezes. Esse exemplo simples ilustra o poder de um plano bem otimizado e a importância de manter índices adequados.

                                                     QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
 Seq Scan on pedidos  (cost=0.00..105000.00 rows=50 width=28) (actual time=0.015..72.430 rows=5 loops=1)
   Filter: (id_cliente = 12345)
   Rows Removed by Filter: 4999995
   Buffers: shared hit=20833 read=198877
 Planning Time: 0.054 ms
 Execution Time: 72.448 ms
(5 linhas)

Além de consultas pontuais, é comum enfrentar junções entre tabelas grandes. Considere uma consulta que retorna os nomes dos clientes e o total de pedidos pagos em uma determinada cidade. Sem índices adequados, o planejador pode optar por Hash Join ou Merge Join, cada um com características diferentes. Execute a consulta abaixo com EXPLAIN ANALYZE para observar qual algoritmo de junção é escolhido e por quê.

-- Consulta com junção entre clientes e pedidos, com filtro por cidade
EXPLAIN (ANALYZE, BUFFERS)
SELECT c.nome, COUNT(p.id_pedido) AS total_pedidos, SUM(p.valor_total) AS total_gasto
FROM clientes c
JOIN pedidos p ON p.id_cliente = c.id_cliente
WHERE c.cidade = 'São Paulo'
  AND p.status = 'PAGO'
GROUP BY c.nome;

A saída exibirá um plano com nós como Hash Join (ou Nested Loop, dependendo da seletividade estimada), possivelmente precedido por um Index Scan na tabela clientes usando o índice idx_clientes_cidade e um Bitmap Index Scan na tabela pedidos usando o índice idx_pedidos_status. A análise detalhada desse plano revelará onde estão os custos mais altos e se a estratégia de junção é adequada. Em nossos treinamentos na JRT Technology Solutions, enfatizamos que saber ler esses planos é a habilidade mais valiosa para qualquer DBA que busca excelência em performance.

Nos próximos tópicos, você aprenderá a agir sobre os gargalos identificados pelo EXPLAIN ANALYZE, seja criando índices, reescrevendo consultas ou ajustando parâmetros do servidor. Antes, porém, é crucial entender o papel do autovacuum na manutenção da saúde do banco e na precisão das estatísticas.

Autovacuum: O Coração da Manutenção Automática e da Performance

O autovacuum é um conjunto de processos de manutenção automática executados pelo PostgreSQL para realizar duas tarefas essenciais: limpar linhas mortas (dead tuples) resultantes do MVCC e atualizar as estatísticas do planejador (por meio do ANALYZE automático). Sem um autovacuum configurado corretamente, as tabelas podem sofrer bloat severo, levando a consultas cada vez mais lentas e uso excessivo de disco. Além disso, estatísticas desatualizadas fazem o planejador escolher planos ineficientes, criando um ciclo vicioso de degradação.

O comportamento do autovacuum é controlado por uma série de parâmetros no arquivo postgresql.conf. Os mais importantes incluem autovacuum (habilita ou desabilita o processo), autovacuum_vacuum_threshold e autovacuum_vacuum_scale_factor (definem quando uma tabela é varrida), autovacuum_analyze_threshold e autovacuum_analyze_scale_factor (definem quando as estatísticas são atualizadas), autovacuum_max_workers (número máximo de workers simultâneos), autovacuum_naptime (intervalo entre varreduras do sistema) e autovacuum_vacuum_cost_delay e autovacuum_vacuum_cost_limit (controle de throttling para não sobrecarregar o servidor).

É comum encontrar instâncias com o autovacuum desabilitado ou com parâmetros padrão inadequados para o volume de dados. Em nossa consultoria na JRT Technology Solutions, já diagnosticamos bancos com bloat superior a 300% simplesmente porque o autovacuum estava desativado. Para verificar o estado atual do autovacuum em sua instância, execute o comando abaixo, que consulta a visão pg_stat_user_tables e mostra o número de operações de vacuum e analyze já realizadas, além da contagem de tuplas vivas e mortas.

-- Monitorar o autovacuum e a saúde das tabelas
SELECT
    schemaname || '.' || relname AS tabela,
    n_live_tup AS tuplas_vivas,
    n_dead_tup AS tuplas_mortas,
    last_vacuum AS ultimo_vacuum,
    last_autovacuum AS ultimo_autovacuum,
    last_analyze AS ultimo_analyze,
    last_autoanalyze AS ultimo_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;

A saída esperada mostrará uma lista de tabelas com o número de tuplas vivas e mortas, bem como os momentos da última execução manual e automática de VACUUM e ANALYZE. Se a coluna n_dead_tup for muito alta e o last_autovacuum estiver nulo ou antigo, é um sinal claro de que o autovacuum não está conseguindo acompanhar a carga de atualizações e exclusões. Nesse caso, será necessário ajustar os parâmetros de configuração, como veremos adiante.

          tabela          | tuplas_vivas | tuplas_mortas |        ultimo_vacuum        |      ultimo_autovacuum      |        ultimo_analyze       |      ultimo_autoanalyze
--------------------------+--------------+---------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------
 public.pedidos           |      5000000 |         12345 |                             | 2026-09-11 10:15:32.123456  | 2026-09-11 09:30:00.654321  | 2026-09-11 10:20:11.987654
 public.clientes          |       999999 |           800 |                             | 2026-09-11 10:15:32.123457  | 2026-09-11 09:30:00.654322  | 2026-09-11 10:20:11.987655
(2 linhas)

Além da visão pg_stat_user_tables, você pode consultar a atividade atual do autovacuum por meio da visão pg_stat_activity, filtrando pelos processos de manutenção. O comando abaixo mostra todos os workers de autovacuum em execução no momento, incluindo o nome da tabela, o estado e o tempo de execução. Essa consulta é útil para identificar se o autovacuum está ativo e se há gargalos de I/O causados por múltiplas varreduras simultâneas.

-- Verificar processos de autovacuum em execução
SELECT
    pid,
    datname AS banco,
    usename AS usuario,
    state AS estado,
    query AS consulta,
    now() - xact_start AS duracao
FROM pg_stat_activity
WHERE backend_type = 'autovacuum worker';

A interpretação correta do autovacuum exige monitoramento contínuo. Em ambientes de alta transação, é recomendável habilitar o registro de logs do autovacuum no postgresql.conf para acompanhar suas decisões. Parâmetros como log_autovacuum_min_duration permitem registrar apenas operações que excedem um determinado tempo, ajudando a identificar varreduras longas que possam estar competindo por recursos. Na próxima seção, você aplicará todos os ajustes necessários no postgresql.conf para otimizar tanto o autovacuum quanto a performance geral do servidor.

Tuning do postgresql.conf: Parâmetros Críticos para Alta Performance

O arquivo postgresql.conf é o principal ponto de configuração do PostgreSQL. Ajustar corretamente seus parâmetros é uma das formas mais impactantes de melhorar a performance do banco de dados. Nesta seção, você verá um exemplo completo de arquivo de configuração otimizado para um servidor com 16 GB de RAM, 4 vCPUs e armazenamento SSD. Os valores podem e devem ser adaptados ao seu ambiente, mas os princípios por trás de cada ajuste são universais.

Antes de editar o arquivo, faça um backup da configuração atual. No Ubuntu/Debian, o arquivo normalmente está em /etc/postgresql/15/main/postgresql.conf (ajuste a versão), enquanto no CentOS/RHEL/Rocky está em /var/lib/pgsql/15/data/postgresql.conf. Você pode localizar o arquivo com o comando SHOW config_file; dentro do psql. Execute os comandos abaixo para fazer o backup, substituindo o caminho conforme sua distribuição.

-- No shell do sistema operacional (Ubuntu/Debian)
sudo cp /etc/postgresql/15/main/postgresql.conf /etc/postgresql/15/main/postgresql.conf.backup

-- No shell do sistema operacional (CentOS/RHEL/Rocky)
sudo cp /var/lib/pgsql/15/data/postgresql.conf /var/lib/pgsql/15/data/postgresql.conf.backup

-- Verificar a localização exata do arquivo de configuração
sudo -u postgres psql -c "SHOW config_file;"

A saída do comando SHOW config_file; indicará o caminho exato do arquivo. Em uma instalação típica do Ubuntu, o caminho será /etc/postgresql/15/main/postgresql.conf; no CentOS/RHEL/Rocky, será /var/lib/pgsql/15/data/postgresql.conf. Anote esse caminho, pois ele será usado nos próximos passos.

               config_file
-----------------------------------------
 /etc/postgresql/15/main/postgresql.conf
(1 linha)

Agora, apresentamos o conteúdo completo de um arquivo postgresql.conf otimizado para alta performance. Todos os parâmetros estão comentados com explicações detalhadas. Este arquivo é funcional e pode ser usado como base, mas lembre-se de adequar os valores à sua realidade de hardware e carga de trabalho. O arquivo abaixo foi validado em ambientes de produção pela equipe da JRT Technology Solutions e segue as melhores práticas do PostgreSQL 15/16.

#------------------------------------------------------------------------------
# CONFIGURAÇÃO OTIMIZADA PARA ALTA PERFORMANCE - PostgreSQL 15/16
# Servidor de referência: 16 GB RAM, 4 vCPUs, armazenamento SSD
# Adapte os valores conforme seu ambiente
#------------------------------------------------------------------------------

# --- CONEXÕES E AUTENTICAÇÃO ---
listen_addresses = 'localhost, IP_DO_SERVIDOR'   # Interfaces de escuta
port = 5432                                        # Porta padrão
max_connections = 200                              # Conexões simultâneas máximas
superuser_reserved_connections = 5                # Conexões reservadas para superusuário

# --- RECURSOS DE MEMÓRIA ---
# Regra prática: shared_buffers entre 15% e 25% da RAM total
shared_buffers = 4GB                               # Cache compartilhado de páginas (25% de 16 GB)
# effective_cache_size deve representar a memória total disponível para cache de SO + shared_buffers
effective_cache_size = 12GB                        # Estimativa de cache total (SO + PostgreSQL) para o planejador
work_mem = 64MB                                    # Memória para operações de ordenação e hash por nó de execução
maintenance_work_mem = 1GB                         # Memória para manutenção (VACUUM, CREATE INDEX, etc.)
huge_pages = try                                   # Tentar usar huge pages do SO (requer configuração no SO)
temp_buffers = 32MB                                # Memória para tabelas temporárias por sessão

# --- CUSTOS E PLANEJADOR ---
# Em SSDs, random_page_cost deve ser reduzido para refletir baixa latência de acesso aleatório
random_page_cost = 1.1                             # Custo de acesso aleatório a disco (SSD)
seq_page_cost = 1.0                                # Custo de acesso sequencial
cpu_tuple_cost = 0.03                              # Custo de processamento por tupla
cpu_index_tuple_cost = 0.005                       # Custo de processamento por tupla de índice
cpu_operator_cost = 0.0025                         # Custo de processamento por operador
effective_io_concurrency = 200                     # Número de operações de I/O simultâneas (SSD NVMe)
default_statistics_target = 200                    # Nível de detalhe das estatísticas coletadas
# Evita que o planejador subestime a seletividade de colunas correlacionadas
# (estatísticas estendidas podem ser criadas com CREATE STATISTICS)

# --- WRITE AHEAD LOG (WAL) ---
wal_level = replica                                # Necessário para replicação; 'minimal' reduz volume de WAL
fsync = on                                         # Garantir durabilidade das transações
synchronous_commit = off                           # Para máxima performance (pode perder últimas transações em crash)
full_page_writes = on                              # Proteção contra corrupção em caso de falha
wal_buffers = 16MB                                 # Buffer para escrita de WAL (recomendado 1/32 de shared_buffers)
checkpoint_timeout = 15min                         # Intervalo máximo entre checkpoints
max_wal_size = 8GB                                 # Tamanho máximo do WAL entre checkpoints
min_wal_size = 2GB                                 # Tamanho mínimo do WAL para reciclagem
checkpoint_completion_target = 0.9                 # Suavizar a escrita do checkpoint (90% do intervalo)
wal_compression = on                               # Comprimir páginas completas no WAL (reduz I/O)

# --- AUTOVACUUM ---
autovacuum = on                                    # Habilita a manutenção automática
autovacuum_max_workers = 4                         # Número de workers simultâneos para vacuum/analyze
autovacuum_naptime = 10s                           # Intervalo entre varreduras das tabelas
autovacuum_vacuum_threshold = 50                   # Número mínimo de tuplas mortas para disparar vacuum
autovacuum_vacuum_scale_factor = 0.01              # Fração de tuplas mortas (1%) para disparar vacuum
autovacuum_analyze_threshold = 50                  # Número mínimo de tuplas modificadas para disparar analyze
autovacuum_analyze_scale_factor = 0.005            # Fração de tuplas modificadas (0.5%) para disparar analyze
autovacuum_vacuum_cost_delay = 2ms                 # Delay entre operações para não sobrecarregar o servidor
autovacuum_vacuum_cost_limit = 2000                # Custo máximo de I/O antes de dormir
log_autovacuum_min_duration = 1000ms               # Registrar autovacuum com duração > 1s

# --- LOGGING E MONITORAMENTO ---
log_destination = 'stderr'                         # Enviar logs para stderr
logging_collector = on                             # Coletar logs em arquivos
log_directory = 'log'                              # Diretório de logs (relativo ao data_directory)
log_filename = 'postgresql-%a.log'                 # Rotação por dia da semana
log_truncate_on_rotation = on                      # Truncar arquivo na rotação
log_rotation_age = 1d                              # Rotacionar diariamente
log_min_duration_statement = 1000                  # Registrar consultas com duração > 1s
log_checkpoints = on                               # Registrar ocorrência de checkpoints
log_connections = on                               # Registrar tentativas de conexão
log_disconnections = on                            # Registrar desconexões
log_lock_waits = on                                # Registrar esperas por locks
log_temp_files = 64MB                              # Registrar arquivos temporários maiores que 64 MB
log_timezone = 'America/Sao_Paulo'                 # Fuso horário dos logs

# --- OUTROS PARÂMETROS RELEVANTES ---
timezone = 'America/Sao_Paulo'                     # Fuso horário da sessão
lc_messages = 'pt_BR.UTF-8'                        # Idioma das mensagens
lc_monetary = 'pt_BR.UTF-8'                        # Formato monetário
lc_numeric = 'pt_BR.UTF-8'                         # Formato numérico
lc_time = 'pt_BR.UTF-8'                            # Formato de data/hora
default_text_search_config = 'pg_catalog.portuguese' # Busca textual em português

Este arquivo de configuração é um ponto de partida sólido para a maioria dos servidores de aplicação. Cada bloco foi comentado com o objetivo de educar e orientar. No entanto, é importante compreender que o tuning de performance não é uma ciência exata: você deve medir o impacto de cada alteração usando ferramentas como pgbench, EXPLAIN ANALYZE e monitoramento do sistema. Em implantações na JRT Technology Solutions, sempre realizamos testes de carga antes e depois de cada mudança para garantir que os ganhos sejam reais e não meramente teóricos.

Após editar o arquivo, é necessário reiniciar o serviço do PostgreSQL para que as novas configurações entrem em vigor. Os comandos variam conforme a distribuição:

-- No Ubuntu/Debian (usando systemd)
sudo systemctl restart postgresql

-- No CentOS/RHEL/Rocky (usando systemd)
sudo systemctl restart postgresql-15

Você pode verificar se o serviço foi reiniciado com sucesso e se as configurações foram aplicadas consultando alguns parâmetros diret

Quer aprender na prática com especialistas?

A JRT Technology Solutions oferece treinamentos e implementação de PostgreSQL para equipes corporativas.



Falar no WhatsApp

Avatar photo

Thiago Paes Rodrigues

Com mais de 22 anos de experiência em Tecnologia da Informação, este profissional construiu uma trajetória sólida como empresário, atuando de forma estratégica na implementação de soluções tecnológicas que otimizam processos e impulsionam resultados em diferentes setores.