English README | README 中文

pg_monetdb

pg_monetdb e um fork de monetdb_fdw focado em pushdown mais forte para formatos de consultas analiticas derivados de cargas no estilo TPC-H e TPC-DS.

Este fork se apoia nos excelentes projetos oracle_fdw (https://github.com/laurenz/oracle_fdw.git) e postgres_fdw (https://www.postgresql.org/docs/current/postgres-fdw.html).

Ele tambem inclui suporte a HUGEINT do MonetDB, BLOB do MonetDB e round trip parcial de INTERVAL. As familias de interval totalmente validadas sao interval month, interval day e interval second; qualificadores do MonetDB suportados por sec_interval ainda sao importados para o PostgreSQL como interval second, entao o qualificador original ainda nao e preservado.

Agradecimento ao Upstream

Repositorio upstream: https://github.com/HaloTech-Co-Ltd/MonetDB_fdw

Agradecimento especial aos mantenedores e contribuidores de monetdb_fdw, base do trabalho upstream sobre o qual este fork foi construido.

Benchmark

Tempos locais de TPC-H versionados no repositorio, obtidos a partir de uma execucao real sobre tabelas heap do PostgreSQL no schema pg e da execucao correspondente via pg_monetdb no schema monet, ambos em PostgreSQL 19:

Query PostgreSQL heap pg_monetdb Pushdown
Query 1 1272.074 ms 139.990 ms Full
Query 2 298.228 ms 9.162 ms Partial
Query 3 322.203 ms 41.508 ms Partial
Query 4 183.436 ms 21.230 ms Full
Query 5 559.401 ms 23.874 ms Full
Query 6 152.712 ms 9.087 ms Full
Query 7 2448.717 ms 36.381 ms Full
Query 8 246.125 ms 35.699 ms Full
Query 9 1645.422 ms 68.392 ms Full
Query 10 346.119 ms 206.652 ms Full
Query 11 109.532 ms 39.043 ms Partial
Query 12 278.782 ms 11.945 ms Full
Query 13 525.866 ms 61.930 ms Full
Query 14 123.599 ms 4.649 ms Full
Query 15 474.821 ms 36.892 ms Partial
Query 16 182.510 ms 55.098 ms Full
Query 17 596.631 ms 28.372 ms Full
Query 18 1888.438 ms 39.790 ms Full
Query 19 43.837 ms 43.812 ms Full
Query 20 253.526 ms 350.296 ms Partial
Query 21 1474.114 ms 75.057 ms Partial
Query 22 74.176 ms 23.588 ms Partial
Total 13500.269 ms 1362.447 ms -

Neste benchmark versionado de PostgreSQL 19, pg_monetdb conclui o total do TPC-H cerca de 9.91x mais rapido que a baseline local em heap, uma reducao de aproximadamente 89.9%.

Pushdown aparece como Full quando o artefato atual de PostgreSQL 19 eh um plano FS puro e como Partial quando a forma ainda inclui trabalho LOCAL_*, INITPLAN ou MIXED.

Esses totais sao reproduziveis com scripts/load_pg18_heap_into_pg19.sh, scripts/run_tpch_all_sql.sh e scripts/benchmark_tpch_schema.sh.

Observacao importante: tpch_regression_baseline.tsv continua no repositorio como um artefato historico de FDW, mas nao eh um benchmark somente de heap do PostgreSQL e nao deve ser lido como comparacao direta entre heap e FDW.

Ambiente usado para os totais versionados de PostgreSQL 19 acima:

  • SO: Ubuntu 26.04 LTS (Resolute Raccoon), kernel 7.0.0-15-generic
  • CPU: AMD Ryzen 7 5800H with Radeon Graphics, 16 threads
  • Memoria: 38.5 GiB RAM
  • PostgreSQL usado no benchmark acima: postgres (PostgreSQL) 19devel (Ubuntu 19~~devel-3~20260525.0815.g0b8fa5fd37b.pgdg26.04+1)

Matriz de Validacao

O repositorio tambem carrega artefatos versionados de regressao TPC-H para PostgreSQL 15 ate 19:

  • tpch_regression_pg15.tsv
  • tpch_regression_pg16.tsv
  • tpch_regression_pg17.tsv
  • tpch_regression_pg18.tsv
  • tpch_regression_pg19.tsv

Esses artefatos sao a referencia versionada da matriz de validacao cross-version atual, cobrindo PostgreSQL 15, 16, 17, 18 e 19.

Sistemas Operacionais e Versoes de Banco Suportados

  • RHEL 8/9, CentOS 8/9, Ubuntu
  • Halo 1.0.14, 1.0.16
  • PostgreSQL 15, 16, 17, 18, 19
  • MonetDB 11.56

Cookbook

Instalacao

Instalacao rapida oficial do MonetDB: https://www.monetdb.org/easy-setup/

Se o MonetDB foi instalado a partir de pacotes padrao da distribuicao, um valor comum para MONETDB_HOME e:

export MONETDB_HOME=/usr
  • Build via PGXS
export USE_PGXS=1
export MONETDB_HOME=<MonetDB installation path>
export PATH=$MONETDB_HOME/bin:$PATH
export LD_LIBRARY_PATH=$MONETDB_HOME/lib64:$LD_LIBRARY_PATH
git clone https://github.com/saulojb/pg_monetdb.git
cd pg_monetdb
make && make install
  • Build dentro de uma arvore de codigo-fonte do PostgreSQL
export MONETDB_HOME=<MonetDB installation path>
export PATH=$MONETDB_HOME/bin:$PATH
export LD_LIBRARY_PATH=$MONETDB_HOME/lib64:$LD_LIBRARY_PATH
git clone https://github.com/saulojb/pg_monetdb.git <PostgreSQL contrib source path>/pg_monetdb
cd <PostgreSQL contrib source path>/pg_monetdb
make && make install

Tutorial Rapido

  • Criar a extensao pg_monetdb
CREATE EXTENSION pg_monetdb;
  • Criar o foreign server
CREATE SERVER foreign_server FOREIGN DATA WRAPPER pg_monetdb
OPTIONS (host '127.0.0.1', port '50000', dbname 'test');
  • Criar o user mapping
CREATE USER MAPPING FOR CURRENT_USER SERVER foreign_server OPTIONS (user 'zm', password 'zm');
  • Criar uma tabela de exemplo emp no MonetDB usando pg_monetdb_execute
SELECT pg_monetdb_execute('foreign_server', $$CREATE TABLE emp(
        name VARCHAR(20),
        age INTEGER
)$$);
  • Criar a foreign table
CREATE FOREIGN TABLE emp(
        name VARCHAR(20),
        age INTEGER
)
SERVER foreign_server
OPTIONS (schema_name 'zm', table_name 'emp');
  • Agora voce pode consultar a tabela emp do MonetDB a partir do PostgreSQL
SELECT count(*) FROM emp;
  • Para SQL remoto ad hoc, monet_query retorna linhas de texto cruas, enquanto as variantes auxiliares conseguem interpretar resultados escalares simples em arrays ou objetos JSON.
SELECT * FROM monet_query('foreign_server', $$SELECT name, age FROM emp$$);

SELECT * FROM monet_query_to_array('foreign_server', $$SELECT name, age FROM emp$$);

SELECT *
FROM monet_query_to_jsonb(
        'foreign_server',
        $$SELECT name, age FROM emp$$,
        ARRAY['name', 'age']
);
  • OBS: voce pode usar IMPORT FOREIGN SCHEMA para criar a foreign table de forma mais conveniente
DROP FOREIGN TABLE emp;
IMPORT FOREIGN SCHEMA "zm" limit to (emp) from server foreign_server into public;

Operacoes Suportadas

  • INSERT
  • DELETE
  • UPDATE
  • SELECT
  • COPY
  • TRUNCATE
  • EXPLAIN
  • IMPORT FOREIGN SCHEMA

Tipos Suportados

Tipo Suportado Descricao
CHAR Y Ref PostgreSQL Doc
VARCHAR Y Ref PostgreSQL Doc
TEXT Y Ref PostgreSQL Doc. TEXT(x) nao e suportado;
TEXT(x) vira VARCHAR(x) quando importado para o PostgreSQL
CLOB Y O tipo base e TEXT. CLOB(x) nao e suportado;
CLOB(x) vira VARCHAR(x) quando importado para o PostgreSQL
STRING Y O tipo base e TEXT, STRING(x) nao e suportado;
STRING(x) vira VARCHAR(x) quando importado para o PostgreSQL
BLOB Y O tipo base e bytea; domains sobre bytea, como blob, sao suportados
BOOL Y Ref PostgreSQL Doc
TINYINT Y O tipo base e smallint
SMALLINT Y Ref PostgreSQL Doc
INTEGER Y Ref PostgreSQL Doc
BIGINT Y Ref PostgreSQL Doc
HUGEINT Y Mapeado para um dominio PostgreSQL HUGEINT sobre numeric(39,0) com faixa de -2^127 + 1 a 2^127 - 1
DECIMAL Y NUMERIC
REAL Y Ref PostgreSQL Doc
DOUBLE PRECISION Y Ref PostgreSQL Doc
FLOAT Y Ref PostgreSQL Doc
DATE Y Ref PostgreSQL Doc
TIME Y Ref PostgreSQL Doc
TIME WITH TIME ZONE Y Ref PostgreSQL Doc
TIMESTAMP Y Ref PostgreSQL Doc
TIMESTAMP WITH TIME ZONE Y Ref PostgreSQL Doc
INTERVAL YEAR Y Importado como PostgreSQL interval month; o round trip eh tratado pela familia month-based de interval do MonetDB
INTERVAL YEAR TO MONTH Y Importado como PostgreSQL interval month; o round trip eh tratado pela familia month-based de interval do MonetDB
INTERVAL MONTH Y Importado como PostgreSQL interval month; round trip validado por IMPORT FOREIGN SCHEMA
INTERVAL DAY Y Importado como PostgreSQL interval day; o FDW normaliza o armazenamento bruto em segundos do MonetDB na leitura e na escrita
INTERVAL DAY TO HOUR Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL DAY TO MINUTE Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL DAY TO SECOND Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL HOUR Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL HOUR TO MINUTE Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL HOUR TO SECOND Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL MINUTE Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL MINUTE TO SECOND Partial O MonetDB armazena isso em sec_interval; ao importar para o PostgreSQL vira interval second, entao o qualificador original nao e mantido
INTERVAL SECOND Y Importado como PostgreSQL interval second; round trip validado por IMPORT FOREIGN SCHEMA
JSON Y Ref PostgreSQL Doc
UUID Y Ref PostgreSQL Doc
URL Y O tipo base e TEXT
INET Y Ref PostgreSQL Doc

Veja os casos de teste em type_support.sql

Estado atual para intervals do MonetDB: o engine remoto aceita formas qualificadas como INTERVAL MONTH, INTERVAL DAY e INTERVAL SECOND, que aparecem nos metadados do MonetDB como month_interval, day_interval e sec_interval. IMPORT FOREIGN SCHEMA mapeia essas familias para interval month, interval day e interval second no PostgreSQL, e pg_monetdb agora faz a formatacao de escrita e a normalizacao de leitura necessarias para round trips ponta a ponta nessas familias importadas. A limitacao restante eh a fidelidade do qualificador para tipos do MonetDB apoiados em sec_interval: formas como INTERVAL DAY TO SECOND sao importadas atualmente como interval second no PostgreSQL, entao a familia de armazenamento funciona, mas o qualificador original ainda nao e preservado.

Validacao Manual

Para validacao do planner contra um banco PostgreSQL existente com tabelas TPC-H importadas para o schema monet, veja materialized_cte_manual.sql.

Observacao importante: o MonetDB nao aceita a sintaxe ANSI de CTE MATERIALIZED / NOT MATERIALIZED. Por isso, pg_monetdb nao consegue fazer pushdown de uma clausula PostgreSQL WITH ... AS MATERIALIZED (...) como SQL remoto equivalente. O comportamento seguro ja validado eh manter essa fronteira de materializacao local no PostgreSQL.

Invocacao tipica:

sudo -n -u postgres psql -X -p 5433 -d monet_test -f sql/materialized_cte_manual.sql

Para validacao do caso de ponte entre subconsulta agrupada e etapa local de janela acima de um CTE agrupado com pushdown, veja grouped_bridge_window_manual.sql.

Invocacao tipica:

sudo -n -u postgres psql -X -p 5433 -d monet_test -f sql/grouped_bridge_window_manual.sql

Para consultas com INNER JOIN LATERAL em que a subconsulta lateral eh apenas um agregado correlacionado escalar, o comportamento atual do pg_monetdb eh manter o join externo local. Nesse padrao especifico, um rewrite para WHERE escalar correlacionado eh um workaround seguro e ja consegue pushdown completo. Veja lateral_scalar_rewrite_manual.sql.

Invocacao tipica:

sudo -n -u postgres psql -X -p 5433 -d monet_test -f sql/lateral_scalar_rewrite_manual.sql

Opcao experimental:

Se a sessao backend fizer preload de pg_monetdb antes da primeira query FDW, o experimento atual com planner hook pode normalizar automaticamente esse padrao exato de agregado escalar com INNER JOIN LATERAL e produzir o mesmo plano com pushdown total. Uma forma de testar esse comportamento eh:

sudo -n -u postgres env PGOPTIONS='-c session_preload_libraries=pg_monetdb' \
        psql -X -p 5433 -d monet_test -f sql/lateral_scalar_rewrite_manual.sql

Este eh um fluxo experimental. Sem preload na sessao, a primeira query FDW de um backend ainda pode deixar passar o rewrite e manter a forma original de JOIN LATERAL local. Executar LOAD 'pg_monetdb' antes da primeira query FDW tambem basta para ativar o mesmo caminho do planner nessa sessao backend.

Exemplo de rewrite:

-- Forma original com INNER JOIN LATERAL
SELECT
        SUM(l.l_extendedprice) / 7.0 AS avg_yearly
FROM
        part p
        JOIN lineitem l ON l.l_partkey = p.p_partkey
        JOIN LATERAL (
                SELECT 0.2 * AVG(l2.l_quantity) AS threshold
                FROM lineitem l2
                WHERE l2.l_partkey = p.p_partkey
        ) aq ON l.l_quantity < aq.threshold
WHERE
        p.p_brand = 'Brand#23'
        AND p.p_container = 'MED BOX';

-- Rewrite escalar correlacionado recomendado para pushdown
SELECT
        SUM(l.l_extendedprice) / 7.0 AS avg_yearly
FROM
        part p
        JOIN lineitem l ON l.l_partkey = p.p_partkey
WHERE
        p.p_brand = 'Brand#23'
        AND p.p_container = 'MED BOX'
        AND l.l_quantity < (
                SELECT 0.2 * AVG(l2.l_quantity)
                FROM lineitem l2
                WHERE l2.l_partkey = p.p_partkey
        );

Esse rewrite so eh recomendado para o caso de INNER JOIN LATERAL em que o lado lateral retorna uma unica linha com agregado escalar correlacionado na relacao externa e o predicado do join apenas compara colunas externas com esse resultado escalar.

Limites

Primary Key e obrigatoria para operacoes DELETE e UPDATE.