Extensão pg_vexec traz execução vetorizada ao PostgreSQL 19 sem alterar o núcleo
Pg_vexec é um conjunto de extensões criado por Igor Suhorukov que adiciona execução vetorizada ao PostgreSQL 19 via hooks do planejador, sem patches no núcleo. O projeto nasceu da tentativa de portar o Apache Cloudberry e promete ganhos de até quase 3 vezes em consultas analíticas, mas ainda é trabalho de um único desenvolvedor.

Pg_vexec é um conjunto de extensões criado por Igor Suhorukov que adiciona execução vetorizada ao PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → 19 via hooks do planejador, sem patches no núcleo. O projeto nasceu da tentativa de portar o Apache Cloudberry e promete ganhos de até quase 3 vezes em consultas analíticas, mas ainda é trabalho de um único desenvolvedor.
O problema por trás do projeto
O ponto de partida está descrito pelo próprio autor, Igor Suhorukov, em publicação no Planet PostgreSQL: ao portar o Apache Cloudberry (fork open source↳Open source71 conteúdosComo o Open Source Está Liberando o Poder da Automação para TodosDev (Back & Front) · out 2025Código aberto: programadores criam software da NASA sem saberDev (Back & Front) · abr 2021N8N: O que é a ferramenta open source que está revolucionando a automação em TI?Dev (Back & Front) · dez 2025Ver tudo em Dev (Back & Front) → do Greenplum) para rodar como extensões sobre o PostgreSQL 19, ele constatou no ClickBench que motores colunares vencem a implementação por uma ordem de grandeza. A causa não está no modelo MPP do Cloudberry, e sim no executor do PostgreSQL, que processa linha por linha: cada valor passa pelo despacho de função (fmgr) individualmente, e os segmentos do cluster apenas multiplicam o mesmo laço sobre tuplas.
Para quem administra bancos analíticos, esse diagnóstico não é novidade: o gargalo de fmgr por valor é conhecido há anos como o motivo de PostgreSQL perder para DuckDB, ClickHouse e Snowflake em cargas de agregação pesada. O que muda aqui é a resposta: em vez de mais um motor rodando ao lado do banco, Suhorukov decidiu vetorizar a execução dentro do próprio backend do PostgreSQL, como extensão, sem tocar no núcleo.
O caminho abandonado: embutir o DataFusion
A primeira tentativa, batizada de pg_arrow, propunha embarcar o Apache DataFusion (motor vetorizado em Rust) num worker de background, comunicando-se com o backend via ABI em C. A ideia foi detalhada e descartada antes de uma linha de código, pelos motivos que o próprio autor lista.
- Semântica divergente:
collation, escala denumeric, ordenação deNaN, fusos horários e até a funçãonow(), que o DataFusion fixa por sessão, de forma que dois comandos da mesma transação veriam horários diferentes. - Cópias extras em cada fronteira: tipos como
jsonbe geometrias trafegariam como texto ou EWKB entre processos, reconstruídos a cada linha. - Isolamento de falhas ruim: o serviço seria filho do
postmaster, então um abort ou um segfault emzstdderrubaria o cluster inteiro. - Lacunas técnicas do DataFusion 55.1:
hash joinsemspillpara disco, perda silenciosa de campos de plano nodatafusion-protoe ausência de tradução paraSubPlans,CTEse partições.
O ganho também era discutível: segundo o autor, scans, filtros e agregações vetorizados podem ser feitos dentro do próprio backend, com a semântica do PostgreSQL e sem cópias entre processos. Essa conclusão é o que deu origem ao pg_vexec.
Como a vetorização entra só por hooks
O requisito que Suhorukov impôs a si mesmo (e aos agentes de IA↳Agentes de IA42 conteúdosOpera passa a integrar ChatGPT, Claude e outros agentes de IADev (Back & Front) · mar 2026Operações mais inteligentes, decisões mais rápidas: o impacto da IA agêntica na rotina de TIAI · abr 2026Adobe aposta em orquestração de agentes de IA: o que muda para devsDev (Back & Front) · abr 2026Ver tudo em AI → que escreveram o código, ponto que detalho adiante) foi direto: nenhuma mudança no núcleo, e com a extensão descarregada o servidor deve se comportar como PostgreSQL vanilla. Para o planejador nativo, o vexec adiciona caminhos vetorizados através dos hooks já existentes: VecScan em set_rel_pathlist_hook, VecHashJoin em set_join_pathlist_hook, e VecAgg/VecSort em create_upper_paths_hook. Esses caminhos entram na disputa de custo comum, pela função add_path, lado a lado com os planos baseados em linha.
Isso é a diferença central em relação a tentativas anteriores de vetorizar o PostgreSQL. openGauss embutiu um executor vetorizado de cerca de 82 mil linhas direto num fork da versão 9.2, hoje impossível de reconciliar com o upstream. Já pg_duckdb entrega a consulta inteira para um segundo motor, o DuckDB, dentro do próprio backend, reabrindo os mesmos problemas de semântica do pg_arrow. E projetos como vectorize_engine, Hydra e o VectorAgg do TimescaleDB inserem nós vetoriais num plano já fechado, fora da disputa de custo, o que no TimescaleDB já produziu resultados incorretos (issue #9902) e travamentos.
| Abordagem | Como decide vetorizar | Risco conhecido |
|---|---|---|
| openGauss | Fork do núcleo, ~82 mil linhas | Não reconcilia com upstream |
| pg_duckdb | Delega a consulta a outro motor | Semântica diferente do PostgreSQL |
| vectorize_engine / Hydra / TimescaleDB VectorAgg | Insere nós após o plano pronto | Fora da disputa de custo; bugs relatados |
| pg_vexec | Compete por custo nos hooks do planejador | Projeto individual, não revisado pela comunidade |
Para o planejador ORCA (usado no modo MPP do Cloudberry), que monta sua própria árvore de plano e não passa pelos hooks do PostgreSQL, a integração acontece por uma API pequena no módulo gp_orca: a classe CCostModelVec recalcula o custo de cada alternativa com e sem multiplicadores vetoriais antes de o ORCA decidir ordem de junção, estágios de agregação e movimentação de dados entre segmentos.
O que um plano vetorizado mostra na prática
Com vexec.mode = force, uma junção com agregação parcial aparece assim no EXPLAIN:
SET vexec.mode = force;
EXPLAIN (COSTS OFF)
SELECT hw.k, count(*), sum(hp.b) FROM hp JOIN hw ON hp.id = hw.id GROUP BY hw.k;Vec Finalize HashAggregate
Group Key: k
-> Gather
Workers Planned: 3
-> Vec Partial HashAggregate
-> Vec Hash Join
Hash Cond: (hp.id = hw.id)
-> Parallel Vec Seq Scan on hp
-> Vec Seq Scan on hwEm vexec.mode = explain, o custo vetorial é calculado mas não escolhido, e o EXPLAIN (VEXEC) relata por que cada alternativa perdeu a disputa de custo, o que é justamente o tipo de transparência que um DBA precisa antes de confiar num executor novo em produção.
Dois formatos em memória, um só buffer
A unidade de execução é um lote de até 1.024 linhas organizado por coluna, dimensionado para caber nos caches L1/L2 durante filtro, projeção e agregação. O layout interno é configurável: vexec.batch_format = postgres mantém a representação nativa do banco (época 2000-01-01, interval de 16 bytes, Datum com cabeçalho varlena); arrow usa os tipos padrão Arrow (época Unix, month_day_nano, strings em utf8_view). Tipos de largura fixa, como inteiros, float, uuid e numeric com até 38 dígitos, são idênticos nos dois formatos.
A conversão só acontece nas fronteiras nomeadas (exportação, Flight SQL, armazenamento), e o modelo de custo contabiliza esse preço. No ClickBench, os dois formatos diferiram em no máximo 2,6%. O ganho maior veio de outro lugar: o numeric escalado, ideia emprestada do openGauss mas reescrita do zero para as regras de numeric do PostgreSQL, representado como inteiro de 64 ou 128 bits. Na consulta Q1 do TPC-H, o layout varlena comum trouxe de 2% a 9% de ganho; o numeric escalado, de 1,8 a 2,8 vezes.
Leitura direta de tabelas colunares
O contrato de origem explica por que o projeto não precisou de hooks novos no table access method: um módulo de armazenamento publica funções begin, next, rescan, end e estimate por uma variável de rendezvous, e o scan é aberto pelo table_beginscan comum, deixando snapshot, bloqueios de predicado e MVCC a cargo do método de acesso de sempre.
- heap: lido página a página via
heap_prepare_pagescan, usando a verificação em loteHeapTupleSatisfiesMVCCBatchdo PostgreSQL 19, o mesmo mecanismo doTABLESAMPLE. - PAX (formato colunar do Cloudberry): devolve grupos de até 131.072 linhas sem cópia;
count,min,max,sumeavgsem filtro saem direto das estatísticas do arquivo, fazendo umcount(*)sobre 10 milhões de linhas cair de 265 ms para 0,33 ms. - ao_column: devolve blocos de até 16.384 linhas, com colunas de largura fixa viradas em fatias de buffer descomprimido.
O VecInsert faz o caminho inverso, escrevendo lotes direto no sink de armazenamento ou, na ausência de um, via table_multi_insert em grupos de mil linhas, como o COPY já fazia. Gatilhos, chaves estrangeiras, ON CONFLICT e RETURNING continuam caindo no ModifyTable tradicional, e o EXPLAIN informa o motivo.
O rigor dos testes, e o que ele não resolve
Suhorukov relata uma bateria de validação incomum para um projeto individual: os 239 testes de regressão do PostgreSQL passam sem alteração (contra os arquivos de resultado esperado originais) com a extensão carregada nos modos off e explain. No modo force, um executor diferencial compara as respostas contra o modo off, nos formatos postgres, arrow e com layouts de coluna aleatórios, e cada diferença encontrada foi examinada e registrada.
Com o planejador ORCA rodando sobre PostgreSQL vanilla, os mesmos 239 testes passam, 27 deles com diferenças examinadas. As 121 consultas do TPC-H e TPC-DS são checadas contra as respostas do DuckDB. Das 652 junções hash dessas consultas, 651 viraram VecHashJoin; a única exceção é a junção completa (full join), ainda sem versão vetorizada.
Como nas refatorações de sistemas legados que fiz em empregos anteriores, decidi limpar os estábulos de Augias começando pela principal dependência, o planejador.
As in the refactorings of legacy systems at my previous jobs, I decided to clean out the Augean stables starting from the main dependency, the planner.Igor Suhorukov, autor do pg_vexec
Vale registrar um ponto que a própria publicação detalha: boa parte do código foi escrita pelo agente Claude Code, em sessões paralelas organizadas em git worktrees separados, com Suhorukov definindo tarefas, decisões de arquitetura e validando os resultados. O cronograma é vertiginoso: planejamento em 2 de outubro, código a partir de 5 de outubro, treze iterações concluídas até a manhã de 7 de outubro de 2026, incluindo o envio de dados colunares como Arrow entre servidores via Motions.
O que isso significa para quem administra PostgreSQL hoje
pg_vexec não é um recurso do PostgreSQL 19 nem uma mudança aceita pela comunidade: é um conjunto de extensões (vexec, vexec_flight, vexec_pgvector, vexec_postgis) de autoria individual, publicado como relato técnico, que exige compilação via PGXS e entrada em shared_preload_libraries. Para usar os recursos de ORCA e PAX, é necessário o fork do Cloudberry como extensões que o próprio autor construiu em trabalho anterior.
O que torna o projeto relevante para quem constrói sobre PostgreSQL não é a promessa de performance isolada, mas o desenho: vetorização como caminho que disputa custo com o plano baseado em linha, em vez de reescrever um plano pronto, é a diferença que evita os bugs relatados em alternativas como o VectorAgg do TimescaleDB.
Antes de considerar algo assim para produção, o caminho correto continua sendo o de sempre: ler o plano de execução, entender onde o otimizador está errando a estimativa, e só então avaliar se vetorização resolve o gargalo real ou se um índice mais adequado já resolveria sem introduzir uma extensão nova e ainda não revisada pela comunidade.
Fonte 1: Planet PostgreSQL (https://postgr.es/p/9xa)
Vectorized Query Execution in PostgreSQL 19: Arrow, ORCA and Flight SQL in a PostgreSQL Extension (Igor Suhorukov).
Fonte: Planet PostgreSQL
Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
PostgreSQL com direct I/O evita que backups piorem a latência de consultas
A ClickHouse detalhou como troca o cache de página por leitura direta de disco nos backups do Postgres gerenciado, para parar de roubar memória e CPU das consultas em produção.











