Introdução

Olá, pessoal! Neste primeiro post, iremos explorar juntos o funcionamento interno do PostgreSQL, visando construir um modelo mental, capaz de te permitir raciocinar sobre performance e compreender como os dados são armazenados e recuperados. É comum no mercado, por exemplo, encontrar ORMs mapeando instruções de uma linguagem de programação de alto nível em SQL. A consulta é executada sem que seja necessário compreender o que acontece por trás dela, e essa abstração cumpre seu papel enquanto tudo funciona dentro do esperado. No entanto, à medida que seu sistema passa a ganhar escala, problemas começam a surgir: consultas que passam a demorar sem qualquer alteração no codebase, tabelas cujo tamanho cresce de forma desproporcional ao volume de dados reais ou índices criados simplesmente não utilizados.

Nesses casos, argumento que a compreensão de como o RDBMS opera internamente, associada à prática de monitoramento, é o que diferencia uma correção efetiva de uma tentativa meramente paliativa, isto é, que beira o aleatório. Um bom modelo mental permite navegar pela complexidade de uma ferramenta com décadas de pesquisa por trás, como o PostgreSQL, e iterar rapidamente sobre hipóteses que conduzem à resolução de um problema em posse dos dados coletados por ferramentas que compõem sua stack de observabilidade.

Como qualquer modelo, trata-se de uma simplificação e, portanto, possui limitações. Podemos tomar como exemplo o modelo atômico de Rutherford, apresentado nas primeiras aulas de química: um núcleo pequeno e denso, orbitado por elétrons como se fossem planetas em torno do sol. Sabe-se que um átomo está longe de se comportar dessa forma; elétrons não ocupam órbitas definidas, mas existem em nuvens de probabilidade. Ainda assim, esse modelo é ensinado, porque é simples o suficiente para sustentar o raciocínio sobre ligações químicas e reações, sem exigir do aluno anos de estudo em mecânica quântica. O modelo que construiremos aqui para o PostgreSQL segue a mesma lógica: não pretende esgotar décadas de pesquisa em sistemas de banco de dados, mas ser suficientemente preciso para sustentar boas decisões de engenharia no dia a dia.

O caminho de uma query da conexão à resposta

O Postgres adota um modelo de processo por conexão: cada nova conexão ao banco gera um novo processo (backend), criado a partir de um fork do processo principal, chamado postmaster.

O postmaster é o primeiro processo iniciado quando um servidor Postgres sobe, e é responsável por escutar novas conexões e delegar cada uma delas a um backend process dedicado. É por essa razão que se costuma dizer que abrir e fechar conexões com frequência é uma operação custosa, o que deu origem a ferramentas de pooling como o PgBouncer.

Estabelecer essa conexão é, na prática, a primeira de seis fases que uma consulta percorre até retornar um resultado, sendo:

  1. Conexão: o cliente estabelece conexão com o postmaster, que delega a um backend dedicado o tratamento da consuta.
  2. Parse: o parser recebe o texto bruto da consulta em SQL e o converte em uma árvore de análise sintática, a parse tree.
  3. Análise Semântica: o analisador semântico valida essa árvore, verificando se tabelas e colunas mencionadas realmente existem. Ele as transforma em uma representação mais completa da consulta.
  4. Rewrite: O rewriter aplica regras e expande views, quando houver alguma envolvida.
  5. Plan: O planner constrói um plano de execução, escolhendo entre diferentes estratégias possíveis a que estima ser mais eficiente.
  6. Execution: O executor percorre esse plano e produz o resultado, buscando e retornando os dados solicitados.

Descendo o nível de abstração de tabelas

É no executor que a consulta finalmente deixa de ser uma estrutura abstrata e passa a tocar os dados de fato. Buscar e retornar uma linha significa localizar uma página específica em memória ou em disco, dentro de um arquivo que representa a tabela. Para entender o que acontece nesse instante, e por que operações aparentemente simples, como um UPDATE, têm implicações que vão muito além de “trocar um valor por outro”, é preciso descer um nível e olhar para como uma tabela é de fato organizada por baixo dos panos.

Uma tabela no Postgres não é um bloco único de dados, mas uma hierarquia de camadas, cada uma contida na anterior: tablespace, arquivo, página e tupla.

No topo está o tablespace, que define em qual diretório do sistema de arquivos os dados de um determinado conjunto de tabelas serão fisicamente armazenados. Por padrão, todo cluster Postgres já nasce com um tablespace, o pg_default, mas é possível criar outros, por exemplo, para direcionar tabelas específicas a um disco mais rápido. Dentro de um tablespace, cada tabela corresponde a um ou mais arquivos no disco. Vale notar que o Postgres identifica esses arquivos internamente por um número, chamado relfilenode, e não pelo nome que você usa no SQL; é por isso que renomear uma tabela é uma operação praticamente instantânea, já que apenas o metadado muda, o arquivo em disco permanece o mesmo.

Esses arquivos, por sua vez, não são lidos byte a byte. Eles são divididos em blocos de tamanho fixo, chamados páginas, com 8 KB cada por padrão. Esse valor é definido em tempo de compilação. Ela é a menor unidade de leitura e escrita do Postgres. Isso significa que, mesmo que uma consulta precise de apenas uma linha, o banco sempre carrega a página inteira que a contém, seja do disco, seja do buffer cache. É por isso que o tamanho e a organização física dos dados dentro de uma página têm impacto direto em performance: quanto mais linhas relevantes couberem em uma mesma página, menos páginas precisam ser lidas do disco para atender a uma consulta, e menos memória do buffer cache é consumida no processo.

Por último, dentro de cada página, residem as tuplas. Termo para cada linha física de uma tabela.

Compreendendo o MVCC

Essa estrutura interna do Postgres é especialmente útil para compreender o MVCC. Cada tupla, além dos dados da linha em si, carrega um pequeno cabeçalho com campos de controle, entre eles xmin e xmax. O primeiro guarda o identificador da transação que criou aquela versão da linha; o segundo, o identificador da transação que a tornou obsoleta, quando isso já aconteceu.

Esses dois campos existem porque o Postgres não sobrescreve uma tupla quando ela é atualizada. Ao executar um UPDATE, o Postgres não altera a linha existente: ele insere uma nova tupla, com os valores atualizados e um novo xmin, e marca a tupla antiga com o xmax da transação atual, sinalizando que ela deixou de ser a versão válida daquela linha. Um DELETE segue a mesma lógica: a tupla não é removida na hora, apenas recebe um xmax, passando a ser considerada morta a partir daquele ponto.

Esse comportamento de nunca sobrescrever, e sim criar novas versões, é o que chamamos de MVCC, ou controle de concorrência multiversão. É ele que permite ao Postgres manter várias transações lendo e escrevendo ao mesmo tempo sem depender de locks constantes entre elas: cada transação enxerga a versão da linha que era válida no momento em que começou, mesmo que outra transação esteja, naquele exato instante, criando uma versão mais nova da mesma linha.

Planos de Execução e Recapitulando a Reflexão

Até aqui, construímos as peças do quebra-cabeça separadamente: como uma consulta percorre os componentes do Postgres até formar uma resposta, como uma tabela é organizada em páginas e tuplas e o que o MVCC faz por trás de um UPDATE.

Para encerrar hoje, vale a pena citar que existe uma ferramenta que, de forma direta, expõe tudo o que construímos até aqui: o EXPLAIN. Ao rodar EXPLAIN ANALYZE sobre uma consulta, você vê o Postgres expor as decisões do planner, algo como:

1
2
3
4
5
6
7
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42;

Seq Scan on orders  (cost=0.00..18334.00 rows=12 width=72) (actual time=0.02..142.31 rows=8 loops=1)
  Filter: (customer_id = 42)
  Rows Removed by Filter: 999992
Planning Time: 0.15 ms
Execution Time: 142.38 ms

Nessa linha já dá pra enxergar boa parte do que discutimos: o Seq Scan diz que o Postgres varreu página por página em busca das tuplas com id = 42, descartando quase um milhão de tuplas no caminho, e o cost mostra a estimativa do planner sendo comparada ao actual time, ou seja, o que de fato aconteceu na execução. É o ponto em que teoria e prática se encontram: páginas, tuplas e o custo de varrê-las deixam de ser conceito e passam a aparecer, de fato, na saída do comando.

Como havia mencionado, esse modelo mental que pensamos juntos é útil e deve servir de base para formular hipóteses quando você enfrentar problemas em produção. Porém, note que ele deve ser sustentado por métricas. Por isso, exploraremos o tema em um próximo post.

Thank you, folks =)

Referências e Materiais para Aprofundar

POSTGRESQL GLOBAL DEVELOPMENT GROUP. PostgreSQL Documentation. [S. l.], 2026. Disponível em: https://www.postgresql.org/docs/. Acesso em: 5 set. 2026.

ESPINO, Jesús. Deep Dive Into a SQL Query: a journey through PostgreSQL’s query processing. [S. l.]: Leanpub, 2026. E-book.