All Products
Search
Document Center

Hologres:Percepat penggabungan multi-tabel dengan Runtime Filters

Last Updated:Jun 22, 2026

Pada kueri penggabungan multi-tabel, data yang tidak memenuhi kondisi join meningkatkan overhead I/O dan menurunkan performa kueri. Dalam skenario tersebut, runtime filters secara otomatis menghasilkan filter ringan yang memangkas data yang tidak relevan selama fase pemindaian. Hal ini mengurangi overhead I/O dan meningkatkan performa kueri. Fitur ini telah mendukung Hash Join sejak Hologres V2.0 dan diperluas ke skenario Cross Join pada V4.2.

Latar Belakang

Kasus Penggunaan

Hologres telah mendukung runtime filters sejak V2.0. Fitur ini umumnya digunakan dalam skenario Hash Join yang melibatkan dua tabel atau lebih, terutama ketika tabel besar digabungkan dengan tabel kecil. Tidak diperlukan konfigurasi manual. Pengoptimal (optimizer) dan mesin eksekusi secara otomatis mengoptimalkan perilaku filtering join saat kueri dijalankan, sehingga mengurangi overhead I/O dan meningkatkan performa join.

Mulai dari V4.2, kemampuan runtime filter diperluas ke skenario Cross Join, khususnya untuk mengoptimalkan pola SQL umum di mana subkueri skalar menghitung hasil agregat dan menggunakannya sebagai kondisi filter pada tabel besar. Untuk informasi lebih lanjut, lihat Dukungan Cross Join untuk runtime filters (Baru di V4.2).

Cara Kerja

Runtime filters untuk hash join

Saat dua tabel digabungkan, data dari salah satu tabel dimuat ke dalam tabel hash, dan data dari tabel lainnya dicocokkan terhadapnya. Proses join memiliki dua sisi:

  • Build side: Sisi yang membangun tabel hash, sesuai dengan node Hash dalam rencana eksekusi.

  • Probe side: Sisi yang membaca data dan mencocokkannya terhadap tabel hash build side.

Umumnya, tabel kecil berperan sebagai build side dan tabel besar sebagai probe side.

Runtime filters bekerja dengan membangun filter ringan dari distribusi data build side dan mendorongnya ke probe side untuk memangkas data. Hal ini mengurangi jumlah data probe side yang diproses dalam Hash Join dan meminimalkan lalu lintas jaringan, sehingga meningkatkan performa join. Akibatnya, fitur ini paling efektif untuk join antara tabel besar dan kecil dengan perbedaan ukuran signifikan, memberikan peningkatan performa yang lebih besar dibandingkan join standar.

Runtime filters untuk cross join (Baru di V4.2)

Sebelum V4.2, runtime filters hanya mencakup Hash Join. Namun, pengguna sering menggunakan subkueri skalar untuk menghitung nilai agregat, seperti min atau max, lalu menggunakan hasilnya sebagai kondisi filter pada tabel besar. Dalam rencana eksekusi, pola SQL ini menghasilkan Cross Join:

  • Build side: Hasil subkueri skalar, yang tepat satu baris.

  • Probe side: Pemindaian tabel besar.

V4.2 memperkenalkan jenis filter baru, ScalarFilter. Pengoptimal secara otomatis mengenali pola ini dan mendorong nilai skalar yang telah dievaluasi dari build side ke bawah menuju ScanNode probe side. Hal ini memungkinkan filtering tingkat baris dan tingkat RowGroup selama fase pemindaian, sehingga menghindari full table scan diikuti perbandingan baris per baris.

Batasan dan Kondisi Pemicu

Batasan

  • Runtime filters hanya didukung di Hologres V2.0 dan versi setelahnya.

  • Dalam skenario Hash Join, V2.0 hanya mendukung runtime filters ketika kondisi join berisi satu bidang saja. Mulai dari V2.1, runtime filters mendukung beberapa bidang.

  • TopN runtime filter hanya didukung di V4.0 dan versi setelahnya serta digunakan untuk meningkatkan performa perhitungan TopN pada tabel tunggal.

  • Cross Join runtime filter (ScalarFilter) hanya didukung di V4.2 dan versi setelahnya.

Kondisi Pemicu

Skenario Hash Join

Mesin secara otomatis memicu runtime filter ketika semua kondisi berikut terpenuhi:

  • Probe side memiliki 100.000 baris atau lebih.

  • Rasio data yang dipindai dari build side terhadap probe side adalah 0,1 atau kurang. Semakin rendah rasionya, semakin besar kemungkinan filter dipicu.

  • Rasio data keluaran join terhadap data probe side adalah 0,1 atau kurang. Semakin rendah rasionya, semakin besar kemungkinan filter dipicu.

Skenario Cross Join (V4.2+)

Pengoptimal secara otomatis menghasilkan ScalarFilter ketika kondisi berikut terpenuhi:

  • Build side Cross Join memiliki jumlah baris statistik sebesar 1 dan distribusinya Replicated.

  • Predikat filter pada probe side berisi kondisi perbandingan dengan ekspresi dari build side, seperti >, <, >=, <=, atau BETWEEN.

Jenis-jenis runtime filters

Runtime filters dapat diklasifikasikan berdasarkan dua dimensi berikut.

Berdasarkan cakupan shuffle (untuk hash join)

Jenis

Versi yang didukung

Skenario

Local

V2.0+

Diaplikasikan ketika data probe side tidak perlu di-shuffle. Runtime filter Local dapat digunakan jika kunci join build side dan probe side memiliki distribusi yang sama, jika data build side disiarkan (broadcast) ke probe side, atau jika data build side di-shuffle agar sesuai dengan distribusi probe side. Jenis ini hanya mengurangi jumlah data yang dipindai dan data yang diproses oleh Hash Join.

Global

V2.2+

Diaplikasikan ketika data probe side perlu di-shuffle. Runtime filter diterapkan sebelum data di-shuffle, sehingga mengurangi network traffic.

Catatan

Anda tidak perlu menentukan jenisnya. Mesin memilihnya secara adaptif.

Berdasarkan jenis filter

Jenis

Versi yang didukung

Deskripsi

Bloom filter

V2.0+

Filter ini bersifat probabilistik dan mungkin menghasilkan false positive, artinya beberapa data mungkin tidak difilter. Namun, filter ini sangat luas penerapannya dan tetap menjaga efisiensi filtering tinggi bahkan ketika build side berisi banyak data.

In filter

V2.0+

Diaplikasikan ketika build side memiliki NDV (jumlah nilai unik) kecil. Filter ini membangun HashSet dari data build side dan mengirimkannya ke probe side untuk filtering. Filter ini secara tepat memfilter semua data yang diperlukan dan dapat digunakan bersamaan dengan indeks bitmap.

MinMax filter

V2.0+

Mengirimkan nilai minimum dan maksimum dari data build side ke probe side untuk filtering. Filter ini dapat memanfaatkan metadata untuk melewatkan seluruh file atau batch data, sehingga mengurangi biaya I/O.

ScalarFilter

V4.2+

Dirancang khusus untuk skenario Cross Join. Ketika build side memiliki tepat satu baris, mesin mendorong nilai skalar ke bawah menuju ScanNode probe side untuk filtering selama fase pemindaian.

Catatan

Anda tidak perlu menentukan jenis filter. Hologres memilih jenis secara adaptif berdasarkan kondisi join saat waktu proses.

Dukungan Cross Join untuk runtime filters (Baru di V4.2)

Motivasi

Pengguna sering menghitung nilai agregat dengan subkueri skalar dan menggunakan hasilnya sebagai kondisi filter pada tabel besar. Sebelum V4.2, pola SQL ini memiliki masalah berikut dalam rencana eksekusi:

  • ScanNode probe side tidak dapat memanfaatkan nilai build side untuk filtering dini dan harus melakukan full table scan.

  • Setelah pemindaian, Cross Join melakukan perbandingan baris per baris, yang menyebabkan pemborosan I/O signifikan.

  • Runtime filter yang ada hanya mencakup Hash Join dan tidak mendukung Cross Join.

V4.2 memperkenalkan ScalarFilter untuk menyelesaikan masalah ini. Fitur ini diaktifkan secara default dan tidak memerlukan tindakan pengguna.

SQL dan perbandingan rencana eksekusi khas

SQL khas:

-- t1 adalah tabel besar; hasil agregat min/max dari t2 tepat 1 baris.
SELECT * FROM t1
WHERE a >= (SELECT min(a) FROM t2 WHERE b BETWEEN 0 AND 1);

Sebelum optimasi (tanpa runtime filter):

 Cross Join
   ->  Seq Scan on t1             -- Full table scan, tanpa filtering dini
   ->  Aggregate                  -- Build side min/max, hasil 1 baris
         ->  Seq Scan on t2

Probe side harus memindai semua data, lalu Cross Join membandingkan baris satu per satu, menyebabkan pemborosan I/O signifikan.

Setelah optimasi (V4.2 dengan ScalarFilter):

 Cross Join
   Runtime Filter Build Expr: (min(t2.a)), (max(t2.a))
   ->  Seq Scan on t1             -- Menerima ScalarFilter, memfilter baris dan RowGroup yang tidak sesuai selama pemindaian
         Runtime Filter Target Expr: (t1.a >= ${1}) AND (t1.a <= ${2})
   ->  Aggregate
         ->  Seq Scan on t2

Setelah build side dievaluasi, nilai aktual didorong ke bawah menuju ScanNode probe side, sehingga filtering dapat diselesaikan selama fase pemindaian.

Skenario khas lainnya

-- Skenario 1: Tabel tunggal + subkueri skalar
SELECT * FROM t1
WHERE a >= (SELECT min(a) FROM t2 WHERE b BETWEEN 0 AND 1);
-- Skenario 2: Multi-tabel + subkueri skalar
SELECT * FROM t1, t3
WHERE (SELECT min(a) FROM t2) <= t1.a
  AND t3.a <= (SELECT max(a) FROM t2);
-- Skenario 3: CTE + Cross Join
WITH r AS (SELECT MIN(a) AS lo, MAX(a) AS hi FROM t2)
SELECT t1.* FROM t1, r
WHERE t1.a >= r.lo AND t1.a <= r.hi;

Cara Kerja

  1. Identifikasi oleh pengoptimal: Ketika build side Cross Join memiliki jumlah baris statistik sebesar 1 dan distribusi Replicated, pengoptimal mengekstraksi ekspresi perbandingan dari predikat (memperluas BETWEEN menjadi >= dan <=) dan menghasilkan kandidat ScalarFilter.

  2. Pushdown saat eksekusi: Selama fase Open, Cross Join mengonsumsi data build side, mengevaluasi build_expr untuk menghasilkan nilai skalar, membangun ScalarFilter, dan mempublikasikannya ke ScanNode probe side.

  3. Filtering dini di ScanNode: Setelah NiagaraScan probe side menerima ScalarFilter, placeholder dalam target_expr diganti dengan nilai aktual dan menghasilkan dua tingkat filtering:

    • Filtering tingkat baris (evaluasi konjungsi).

    • Filtering tingkat RowGroup (mesin penyimpanan melewatkan blok data yang tidak sesuai).

Batasan

  • Build side harus memiliki tepat satu baris. Pengoptimal hanya menghasilkan ScalarFilter ketika build side memiliki jumlah baris statistik sebesar 1 dan distribusi Replicated. Jika build side tidak menghasilkan tepat satu baris saat waktu proses, Hologres melaporkan error RT_CHECK.

  • Jika nilai build side adalah NULL, Hologres mempublikasikan filter FILTER_ALL (yang memfilter semua baris), membersihkan data build side untuk memperpendek Cross Join, dan mengembalikan set hasil kosong.

  • Kolom dictionary tidak didukung. Jika kolom probe side yang dirujuk oleh target_expr merupakan kolom berkode dictionary (tipe DICTIONARY), ScalarFilter tidak diterapkan.

  • Tipe data yang didukung mencakup INT8, UINT8, INT16, UINT16, INT32, UINT32, INT64, UINT64, DATE32, TIMESTAMP, FLOAT, DOUBLE, dan STRING. Jika tipe data tidak didukung, ScalarFilter tidak didorong ke bawah dan tidak ada error yang dilaporkan.

  • Operator perbandingan yang didukung mencakup >, <, >=, dan <=. BETWEEN diperluas menjadi dua perbandingan rentang.

Parameter GUC

Parameter

Type

Default

Tingkat

Deskripsi

hg_experimental_generate_runtime_scalar_filter

bool

true

PGC_USERSET

Mengontrol apakah akan menghasilkan ScalarFilter untuk Cross Join. Fitur ini diaktifkan secara default dan tidak memerlukan tindakan pengguna.

Untuk menonaktifkan fitur ini, jalankan perintah berikut:

SET hg_experimental_generate_runtime_scalar_filter = off;

Bidang baru dalam output EXPLAIN

Ketika ScalarFilter diaktifkan, node Cross Join menampilkan informasi berikut:

  • Runtime Filter Build Expr: Ekspresi build side, seperti (min(t2.a)) dan (max(t2.a)).

  • Runtime Filter Target Expr: Ekspresi filter target pada probe side, seperti (t1.a >= ${1}) AND (t1.a <= ${2}). Dalam ekspresi ini, ${N} adalah placeholder yang diganti saat waktu proses dengan nilai build side yang sesuai dengan filter_id.

Manfaat Performa

Pada dataset TPC-DS 10 TB, pola SQL berikut umum digunakan:

DELETE FROM inventory
WHERE inv_date_sk >= (SELECT min(d_date_sk) FROM date_dim WHERE d_date BETWEEN 'INV_S_1' AND 'INV_E_1')
  AND inv_date_sk <= (SELECT max(d_date_sk) FROM date_dim WHERE d_date BETWEEN 'INV_S_1' AND 'INV_E_1');

Dengan optimasi ScalarFilter V4.2, waktu kueri turun dari 2,672 detik menjadi 0,552 detik, peningkatan performa sekitar 4,8x.

Memverifikasi runtime filters

Contoh berikut menunjukkan cara memverifikasi efek runtime filters dalam berbagai skenario.

Contoh 1: Kondisi join satu kolom (jenis local)

BEGIN;
CREATE TABLE test1 (x int, y int);
CALL set_table_property('test1', 'distribution_key', 'x');
CREATE TABLE test2 (x int, y int);
CALL set_table_property('test2', 'distribution_key', 'x');
END;
INSERT INTO test1 SELECT t, t FROM generate_series(1, 100000) t;
INSERT INTO test2 SELECT t, t FROM generate_series(1, 1000) t;
ANALYZE test1;
ANALYZE test2;
EXPLAIN ANALYZE SELECT * FROM test1 JOIN test2 ON test1.x = test2.x;

Rencana eksekusi:

QUERY PLAN
Gather  (cost=0.00..10.20 rows=1000 width=16)
[40:1 id=100002 dop=1 time=9/9/9ms rows=1000(1000/1000/1000) mem=16/16/16KB open=2/2/2ms get_next=7/7/7ms]
  -> Hash Join  (cost=0.00..10.16 rows=1000 width=16)
      Hash Cond: (test1.x = test2.x)
      Runtime Filter Cond: (test1.x = test2.x)
      [id=8 dop=40 time=9/4/2ms rows=1000(39/25/17) mem=6/5/5KB open=4/1/0ms get_next=6/2/0ms]
      -> Local Gather  (cost=0.00..5.11 rows=1000000 width=8)
          [id=3 dop=40 time=6/1/0ms rows=1000(39/25/17) mem=600/600/600B open=1/0/0ms get_next=6/1/0ms local_dop=1/1/1]
          -> Seq Scan on test1  (cost=0.00..5.10 rows=1000000 width=8)
              Runtime Filter Target Expr: test1.x
              [id=2 split_count=40 time=11/7/7ms rows=1000(39/25/17) mem=41/41/41KB open=11/7/7ms get_next=0/0/0ms scan_rows=1000000 25270/25000/24697))]
      -> Hash  (cost=5.00..5.00 rows=1000 width=8)
          [id=7 dop=40 time=3/1/0ms rows=1000(39/25/17) mem=396/396/396KB open=3/1/0ms get_next=1/0/0ms rehash=1/1/1 hash_mem=384/384/384KB]
          -> Local Gather  (cost=0.00..5.00 rows=1000 width=8)
              [id=5 dop=40 time=1/0/0ms rows=1000(39/25/17) mem=0/0/0B open=1/0/0ms get_next=1/0/0ms local_dop=0/0/0]
              -> Seq Scan on test2  (cost=0.00..5.00 rows=1000 width=8)
                  [id=4 split_count=40 time=1/0/0ms rows=1000(39/25/17) mem=528/528/528B open=1/0/0ms get_next=1/0/0ms scan_rows=1000(39/25/17)]
  • Tabel test2 memiliki 1.000 baris dan tabel test1 memiliki 100.000 baris. Rasio ukuran data build terhadap probe adalah 0,01 (kurang dari 0,1), yang memenuhi kondisi pemicu default untuk runtime filters.

  • Pemindaian probe side pada test1 menampilkan Runtime Filter Target Expr, yang menunjukkan bahwa runtime filter telah didorong ke bawah.

  • Pada probe side, scan_rows adalah 100.000 (jumlah baris yang dibaca dari penyimpanan), sedangkan rows adalah 1.000 (jumlah baris setelah filtering). Perbedaan ini menunjukkan efek filtering.

Contoh 2: Kondisi join multi-kolom (V2.1+, jenis local)

DROP TABLE IF EXISTS test1, test2;
BEGIN;
CREATE TABLE test1 (x int, y int);
CREATE TABLE test2 (x int, y int);
END;
INSERT INTO test1 SELECT t, t FROM generate_series(1, 1000000) t;
INSERT INTO test2 SELECT t, t FROM generate_series(1, 1000) t;
ANALYZE test1;
ANALYZE test2;
EXPLAIN ANALYZE SELECT * FROM test1 JOIN test2 ON test1.x = test2.x AND test1.y = test2.y;

Rencana eksekusi:

QUERY PLAN
Gather  (cost=0.00..10.46 rows=1000 width=16)
[40:1 id=100003 dop=1 time=6/6/6ms rows=1000(1000/1000/1000) mem=600/600/600B open=0/0/0ms get_next=6/6/6ms]
  -> Hash Join  (cost=0.00..10.43 rows=1000 width=16)
       Hash Cond: ((test1.x = test2.x) AND (test1.y = test2.y))
       Runtime Filter Cond: ((test1.x = test2.x) AND (test1.y = test2.y))
       [id=8 dop=40 time=5/3/3ms rows=1000(1000/25/0) mem=40/2/1KB open=1/0/0ms get_next=4/3/3ms]
       -> Local Gather  (cost=0.00..5.11 rows=1000000 width=8)
            [id=5 dop=40 time=4/3/3ms rows=1000(1000/25/0) mem=600/600/600B open=0/0/0ms get_next=4/3/3ms local_dop=1/1/1]
            -> Seq Scan on test1  (cost=0.00..5.10 rows=1000000 width=8)
                 Runtime Filter Target Expr: (test1.x AND test1.y)
                 [id=4 split_count=40 time=7/5/5ms rows=1000(1000/25/0) mem=49/11/9KB open=7/5/5ms get_next=1/0/0ms scan_rows=1000000(32768/25000/24576)]
       -> Hash  (cost=5.02..5.02 rows=40000 width=8)
            [id=7 dop=40 time=1/0/0ms rows=40000(1000/1000/1000) mem=417/417/417KB open=1/0/0ms get_next=1/0/0ms rehash=1/1/1 hash_mem=384/384/384KB]
            -> Broadcast  (cost=0.00..5.02 rows=40000 width=8)
                 [40:40 id=100002 dop=40 time=1/0/0ms rows=40000(1000/1000/1000) mem=0/0/0B open=1/0/0ms get_next=0/0/0ms * ]
                 -> Local Gather  (cost=0.00..5.00 rows=1000 width=8)
                      [id=3 dop=40 time=1/0/0ms rows=1000(1000/25/0) mem=600/600/600B open=1/0/0ms get_next=0/0/0ms local_dop=1/1/1]
                      -> Seq Scan on test2  (cost=0.00..5.00 rows=1000 width=8)
                           [id=2 split_count=40 time=1/0/0ms rows=1000(1000/25/0) mem=528/528/528B open=0/0/0ms get_next=1/0/0ms scan_rows=1000(1000/1000/1000)]
  • Kondisi join berisi beberapa kolom, dan runtime filter juga dihasilkan untuk beberapa kolom.

  • Data build side disiarkan (broadcast), sehingga digunakan runtime filter Local.

Contoh 3: Jenis Global (V2.2+, shuffle join)

SET hg_experimental_enable_result_cache = OFF;
DROP TABLE IF EXISTS test1, test2;
BEGIN;
CREATE TABLE test1 (x int, y int);
CREATE TABLE test2 (x int, y int);
END;
INSERT INTO test1 SELECT t, t FROM generate_series(1, 100000) t;
INSERT INTO test2 SELECT t, t FROM generate_series(1, 1000) t;
ANALYZE test1;
ANALYZE test2;
EXPLAIN ANALYZE SELECT * FROM test1 JOIN test2 ON test1.x = test2.x;

Rencana eksekusi:

QUERY PLAN
   -> Hash Join  (cost=0.00..10.08 rows=1000 width=16)
      Hash Cond: (test1.x = test2.x)
      Runtime Filter Cond: (test1.x = test2.x)
      [id=9 dop=40 time=10/8/8ms rows=1000(34/25/13) mem=6/6/5KB open=2/1/1ms get_next=8/7/7ms]
      -> Redistribution  (cost=0.00..5.07 rows=100000 width=8)
         Hash Key: test1.x
         [40:40 id=100002 dop=40 time=8/7/7ms rows=1289(46/32/20) mem=512/432/0B open=0/0/0ms get_next=8/7/7ms * ]
      -> Local Gather  (cost=0.00..5.01 rows=100000 width=8)
         [id=3 dop=40 time=9/2/0ms rows=1289(1042/32/0) mem=600/600/600B open=0/0/0ms get_next=9/2/0ms local_dop=1/1/1]
         -> Seq Scan on test1  (cost=0.00..5.01 rows=100000 width=8)
            Runtime Filter Target Expr: test1.x
            [id=2 split_count=40 time=11/3/0ms rows=1289(1042/32/0) mem=50512/11849/528B open=11/3/0ms get_next=1/0/0ms scan_rows=100000(8192/7692/1696)]
      -> Hash  (cost=5.00..5.00 rows=1000 width=8)
         [id=8 dop=40 time=2/1/1ms rows=1000(34/25/13) mem=396/396/396KB open=2/1/1ms get_next=0/0/0ms rehash=1/1/1 hash_mem=384/384/384KB]
         -> Redistribution  (cost=0.00..5.00 rows=1000 width=8)
            Hash Key: test2.x
            [40:40 id=100003 dop=40 time=2/1/1ms rows=1000(34/25/13) mem=0/0/0B open=0/0/0ms get_next=2/1/1ms * ]
         -> Local Gather  (cost=0.00..5.00 rows=1000 width=8)
            [id=5 dop=40 time=1/0/0ms rows=1000(1000/25/0) mem=600/600/600B open=1/0/0ms get_next=1/0/0ms local_dop=1/1/1]
            -> Seq Scan on test2  (cost=0.00..5.00 rows=1000 width=8)
               [id=4 split_count=40 time=0/0/0ms rows=1000(1000/25/0) mem=528/528/528B open=0/0/0ms get_next=0/0/0ms scan_rows=1000(1000/1000/1000)]

Data probe side di-shuffle ke operator Hash Join. Mesin secara otomatis menggunakan runtime filter Global untuk mempercepat kueri.

Contoh 4: In filter dengan indeks bitmap (V2.2+)

SET hg_experimental_enable_result_cache = OFF;
DROP TABLE IF EXISTS test1, test2;
BEGIN;
CREATE TABLE test1 (x text, y text);
CALL set_table_property('test1', 'distribution_key', 'x');
CALL set_table_property('test1', 'bitmap_columns', 'x');
CALL set_table_property('test1', 'dictionary_encoding_columns', '');
CREATE TABLE test2 (x text, y text);
CALL set_table_property('test2', 'distribution_key', 'x');
END;
INSERT INTO test1 SELECT t::text, t::text FROM generate_series(1, 10000000) t;
INSERT INTO test2 SELECT t::text, t::text FROM generate_series(1, 50) t;
ANALYZE test1;
ANALYZE test2;
EXPLAIN ANALYZE SELECT * FROM test1 JOIN test2 ON test1.x = test2.x;

Rencana eksekusi:

QUERY PLAN
Gather  (cost=0.00..11.70 rows=50 width=14)
[40:1 id=100002 dop=1 time=16/16/16ms rows=50(50/50/50) mem=2/2/2KB open=0/0ms get_next=16/16/16ms]
  -> Hash Join  (cost=0.00..11.70 rows=50 width=14)
       Hash Cond: (test1.x = test2.x)
       Runtime Filter Cond: (test1.x = test2.x)
       [id=7 dop=40 time=15/9/3ms rows=50(3/1/0) mem=5132/3774/264B open=1/0/0ms get_next=14/8/3ms]
       -> Local Gather  (cost=0.00..6.26 rows=10000000 width=12)
            [id=3 dop=40 time=14/8/3ms rows=50(3/1/0) mem=600/600/600B open=1/0/0ms get_next=14/8/3ms local_dop=1/1/1]
            -> Seq Scan on test1  (cost=0.00..6.06 rows=10000000 width=12)
                 Runtime Filter Target Expr: test1.x
                 [id=2 split_count=40 time=16/10/5ms rows=50(3/1/0) mem=67544/48945/528B open=16/9/5ms get_next=1/0/0ms scan_rows=7247692(250875/249920/248982) bitmap_used=50]
       -> Hash  (cost=5.00..5.00 rows=50 width=2)
            [id=6 dop=40 time=1/0/0ms rows=61(3/1/1) mem=534/530/521KB open=1/0/0ms get_next=1/0/0ms rehash=1/1/1 hash_mem=512/512/512KB]
            -> Local Gather  (cost=0.00..5.00 rows=50 width=2)
                 [id=5 dop=40 time=1/0/0ms rows=50(3/1/0) mem=600/600/600B open=0/0/0ms get_next=1/0/0ms local_dop=1/1/1]
                 -> Seq Scan on test2  (cost=0.00..5.00 rows=50 width=2)
                      [id=4 split_count=40 time=1/0/0ms rows=50(3/1/0) mem=528/528/528B open=1/0/0ms get_next=0/0/0ms scan_rows=50(3/1/1)]

Operator pemindaian probe side menggunakan indeks bitmap. In filter memberikan filtering yang tepat, menyisakan hanya 50 baris. Nilai scan_rows pada operator pemindaian lebih dari 7 juta, lebih rendah daripada 10 juta baris aslinya. Hal ini karena In filter dapat didorong ke mesin penyimpanan, sehingga mengurangi overhead I/O. Menggabungkan In filter dengan indeks bitmap memberikan keuntungan signifikan ketika kunci join bertipe STRING.

Contoh 5: MinMax filter untuk pengurangan I/O (V2.2+)

SET hg_experimental_enable_result_cache = OFF;
DROP TABLE IF EXISTS test1, test2;
BEGIN;
CREATE TABLE test1 (x int, y int);
CALL set_table_property('test1', 'distribution_key', 'x');
CREATE TABLE test2 (x int, y int);
CALL set_table_property('test2', 'distribution_key', 'x');
END;
INSERT INTO test1 SELECT t::int, t::int FROM generate_series(1, 10000000) t;
INSERT INTO test2 SELECT t::int, t::int FROM generate_series(1, 100000) t;
ANALYZE test1;
ANALYZE test2;
EXPLAIN ANALYZE SELECT * FROM test1 JOIN test2 ON test1.x = test2.x;

Rencana eksekusi:

QUERY PLAN
 Gather  (cost=0.00..15.68 rows=100000 width=16)
   [40:1 id=100002 dop=1 time=5/5/5ms rows=100000(100000/100000/100000) mem=600/600/600B open=0/0/0ms get_next=5/5/5ms]
   -> Hash Join  (cost=0.00..11.98 rows=100000 width=16)
        Hash Cond: (test1.x = test2.x)
        Runtime Filter Cond: (test1.x = test2.x)
        [id=7 dop=40 time=5/4/4ms rows=100000(2639/2500/2406) mem=97/92/89KB open=1/0/0ms get_next=4/3/3ms]
        -> Local Gather  (cost=0.00..6.14 rows=10000000 width=8)
             [id=3 dop=40 time=5/3/3ms rows=100000(2639/2500/2406) mem=600/600/600B open=1/0/0ms get_next=4/3/3ms local_dop=1/1/1]
             -> Seq Scan on test1  (cost=0.00..6.00 rows=10000000 width=8)
                  Runtime Filter Target Expr: test1.x
                  [id=2 split_count=40 time=6/6/5ms rows=100000(2639/2500/2406) mem=61/60/59KB open=6/5/5ms get_next=0/0/0ms scan_rows=327680(8192/8192/8192)]
        -> Hash  (cost=5.01..5.01 rows=100000 width=8)
             [id=6 dop=40 time=1/0/0ms rows=100000(2639/2500/2406) mem=463/460/458KB open=1/0/0ms get_next=1/0/0ms rehash=1/1/1 hash_mem=384/384/384KB]
             -> Local Gather  (cost=0.00..5.01 rows=100000 width=8)
                  [id=5 dop=40 time=1/0/0ms rows=100000(2639/2500/2406) mem=600/600/600B open=0/0/0ms get_next=1/0/0ms local_dop=1/1/1]
                  -> Seq Scan on test2  (cost=0.00..5.01 rows=100000 width=8)
                       [id=4 split_count=40 time=1/0/0ms rows=100000(2639/2500/2406) mem=528/528/528B open=1/0/0ms get_next=0/0/0ms scan_rows=100000(2639/2500/2406)]

Operator pemindaian probe side hanya membaca sedikit lebih dari 320.000 baris dari mesin penyimpanan, jauh lebih sedikit daripada 10 juta aslinya. Hal ini terjadi karena runtime filter didorong ke mesin penyimpanan, yang menggunakan metadata batch data untuk memfilter seluruh batch sekaligus. Hal ini secara signifikan mengurangi overhead I/O. Jenis filter ini paling efektif ketika kunci join numerik dan rentang nilai build side lebih kecil daripada rentang probe side.

Contoh 6: TopN runtime filter (V4.0+)

Ketika pernyataan SQL mencakup operator topN, Hologres tidak menghitung semua hasil. Sebaliknya, Hologres menghasilkan filter dinamis untuk memangkas data lebih awal.

SELECT o_orderkey FROM orders ORDER BY o_orderdate LIMIT 5;

Rencana eksekusi:

QUERY PLAN
Limit  (cost=0.00..116554.70 rows=0 width=8)
  ->  Sort  (cost=0.00..116554.70 rows=100 width=12)
        Sort Key: o_orderdate
      [id=6 dop=1 time=317/317/317ms rows=5(5/5/5) mem=1/1/1KB open=317/317/317ms get_next=0/0/0ms]
        ->  Gather  (cost=0.00..116554.25 rows=100 width=12)
            [20:1 id=100002 dop=1 time=317/317/317ms rows=100(100/100/100) mem=6/6/6KB open=0/0/0ms get_next=317/317/317ms * ]
              ->  Limit  (cost=0.00..116554.25 rows=0 width=12)
                    ->  Sort  (cost=0.00..116554.25 rows=150000000 width=12)
                          Sort Key: o_orderdate
                          Runtime Filter Sort Column: o_orderdate
                        [id=3 dop=20 time=318/282/258ms rows=100(5/5/5) mem=96/96/96KB open=318/282/258ms get_next=1/0/0ms]
                          ->  Local Gather  (cost=0.00..9.59 rows=150000000 width=12)
                              [id=2 dop=20 time=316/280/256ms rows=1372205(68691/68610/68498) mem=0/0/0B open=0/0/0ms get_next=316/280/256ms local_dop=1/1/1 * ]
                                ->  Seq Scan on orders  (cost=0.00..8.24 rows=150000000 width=12)
                                      Runtime Filter Target Expr: o_orderdate
                                    [id=1 split_count=20 time=286/249/222ms rows=1372205(68691/68610/68498) mem=179/179/179KB open=0/0/0ms get_next=286/249/222ms physical_reads=27074(1426/1353/1294) scan_rows=144867963(7324934/7243398/7172304)]
Query id:[1001003033996040311]
QE version: 2.0
Query Queue: init_warehouse.default_queue
======================cost======================
Total cost:[343] ms
Optimizer cost:[13] ms
Build execution plan cost:[0] ms
Init execution plan cost:[6] ms
Start query cost:[6] ms
- Queue cost: [0] ms
- Wait schema cost:[0] ms
- Lock query cost:[0] ms
- Create dataset reader cost:[0] ms
- Create split reader cost:[0] ms
Get result cost:[318] ms
- Get the first block cost:[318] ms
====================resource====================
Memory: total 7 MB. Worker stats: max 3 MB, avg 3 MB, min 3 MB, max memory worker id: 189*****.
CPU time: total 5167 ms. Worker stats: max 2610 ms, avg 2583 ms, min 2557 ms, max CPU time worker id: 189*****.
DAG CPU time stats: max 5165 ms, avg 2582 ms, min 0 ms, cnt 2, max CPU time dag id: 1.
Fragment CPU time stats: max 5137 ms, avg 1721 ms, min 0 ms, cnt 3, max CPU time fragment id: 2.
Ec wait time: total 90 ms. Worker stats: max 46 ms, max(max) 2 ms, avg 45 ms, min 44 ms, max ec wait time worker id: 189*****, max(max) ec wait time worker id: 189*****.
Physical read bytes: total 799 MB. Worker stats: max 400 MB, avg 399 MB, min 399 MB, max physical read bytes worker id: 189*****.
Read bytes: total 898 MB. Worker stats: max 450 MB, avg 449 MB, min 448 MB, max read bytes worker id: 189*****.
DAG instance count: total 3. Worker stats: max 2, avg 1, min 1, max DAG instance count worker id: 189*****.
Fragment instance count: total 41. Worker stats: max 21, avg 20, min 20, max fragment instance count worker id: 189*****.

Tanpa TopN runtime filter, ScanNode akan membaca setiap blok data dari tabel orders dan meneruskannya ke node TopN, yang kemudian menggunakan heap sort untuk mempertahankan 5 baris teratas yang ditemui sejauh ini.

Sebagai contoh, setiap blok data berisi sekitar 8.192 baris. Setelah blok pertama diproses, TopN mengetahui o_orderdate peringkat ke-5 dalam blok tersebut. Misalnya tanggal tersebut adalah 1995-01-01. Saat node Scan membaca blok kedua, nilai 1995-01-01 digunakan sebagai kondisi filter dan hanya baris dengan o_orderdate <= 1995-01-01 yang dikirim ke TopN. Ambang batas diperbarui secara dinamis. Jika o_orderdate peringkat ke-5 dalam blok kedua lebih kecil, TopN mengganti ambang batas lama dengan nilai baru.

Anda dapat menggunakan output EXPLAIN untuk melihat TopN runtime filter yang dihasilkan oleh pengoptimal:

->  Limit  (cost=0.00..116554.25 rows=0 width=12)
  ->  Sort  (cost=0.00..116554.25 rows=150000000 width=12)
        Sort Key: o_orderdate
        Runtime Filter Sort Column: o_orderdate
      [id=3 dop=20 time=318/282/258ms rows=100(5/5/5) mem=96/96/96KB open=318/282/258ms get_next=1/0/0ms]

Kehadiran Runtime Filter Sort Column pada node TopN menunjukkan bahwa node ini menghasilkan TopN runtime filter.

Contoh 7: Cross join dengan ScalarFilter (V4.2+)

SET hg_experimental_enable_result_cache = OFF;
DROP TABLE IF EXISTS t1, t2;
BEGIN;
CREATE TABLE t1 (a int, b int);
CREATE TABLE t2 (a int, b int);
END;
INSERT INTO t1 SELECT t, t FROM generate_series(1, 1000000) t;
INSERT INTO t2 SELECT t, t FROM generate_series(1, 1000) t;
ANALYZE t1;
ANALYZE t2;
EXPLAIN ANALYZE
SELECT * FROM t1
WHERE a >= (SELECT min(a) FROM t2 WHERE b BETWEEN 0 AND 1)
  AND a <= (SELECT max(a) FROM t2 WHERE b BETWEEN 0 AND 1);

Sorotan rencana eksekusi:

  • Node Cross Join menampilkan Runtime Filter Build Expr: (min(t2.a)), (max(t2.a)).

  • ScanNode pada t1 menampilkan Runtime Filter Target Expr: (t1.a >= ${1}) AND (t1.a <= ${2}).

  • Untuk t1, scan_rows mencerminkan jumlah baris yang dibaca dari penyimpanan, sedangkan rows turun signifikan setelah ScalarFilter diterapkan. Perbedaan ini memverifikasi efek filter.

Menonaktifkan fitur untuk perbandingan:

SET hg_experimental_generate_runtime_scalar_filter = off;
EXPLAIN ANALYZE
SELECT * FROM t1
WHERE a >= (SELECT min(a) FROM t2 WHERE b BETWEEN 0 AND 1)
  AND a <= (SELECT max(a) FROM t2 WHERE b BETWEEN 0 AND 1);

Setelah fitur dinonaktifkan, rencana eksekusi tidak lagi menampilkan bidang Runtime Filter Build Expr dan Target Expr, dan pemindaian pada t1 kembali menjadi full table scan.

Riwayat Versi

Versi

Fitur Baru

V2.0

Dukungan runtime filters dalam Hash Join (jenis Local, termasuk filter Bloom, In, dan MinMax).

V2.1

Dukungan runtime filters dengan kondisi join multi-kolom.

V2.2

Dukungan runtime filters Global (untuk shuffle join); filter In dapat dikombinasikan dengan indeks bitmap.

V4.0

Dukungan TopN runtime filter.

V4.2

Dukungan runtime filters Cross Join (ScalarFilter) untuk mengoptimalkan skenario yang menggunakan subkueri skalar untuk memfilter tabel besar.