Contents
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.tsvtpch_regression_pg16.tsvtpch_regression_pg17.tsvtpch_regression_pg18.tsvtpch_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
empno MonetDB usandopg_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
empdo MonetDB a partir do PostgreSQL
SELECT count(*) FROM emp;
- Para SQL remoto ad hoc,
monet_queryretorna 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 SCHEMApara 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.