From e6c8c6f5d1d9420a5166512af25e7c3407a877d1 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sat, 13 Apr 2024 22:25:35 +0200 Subject: [PATCH v20240413 3/4] Add regression tests for BRIN parallel builds Adds regression test for parallel CREATE INDEX with a BRIN index. The test is added to pageinspect, and which makes it easy to compare the parallel index to an index built without parallelism. --- contrib/pageinspect/expected/brin.out | 106 ++++++++++++++++++++++++++ contrib/pageinspect/sql/brin.sql | 85 +++++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/contrib/pageinspect/expected/brin.out b/contrib/pageinspect/expected/brin.out index 3f6e5174bc6..66b199dba8a 100644 --- a/contrib/pageinspect/expected/brin.out +++ b/contrib/pageinspect/expected/brin.out @@ -108,3 +108,109 @@ SELECT (COUNT(*) = (SELECT relpages FROM pg_class WHERE relname = 'test2')) AS r DROP TABLE test1; DROP TABLE test2; +-- test that parallel index build produces the same BRIN index as serial build +CREATE TABLE brin_parallel_test (a int, b text) WITH (fillfactor=25); +-- a mix of NULLs and non-NULL values +INSERT INTO brin_parallel_test +SELECT (CASE WHEN (mod(i,231) = 0) OR (i BETWEEN 3500 AND 4000) THEN NULL ELSE i END), + (CASE WHEN (mod(i,233) = 0) OR (i BETWEEN 3750 AND 4250) THEN NULL ELSE md5(i::text) END) + FROM generate_series(1,10000) S(i); +-- delete a couple pages, to make a range empty +DELETE FROM brin_parallel_test WHERE a BETWEEN 1000 and 2000; +-- vacuum to make the ranges actually empty +VACUUM brin_parallel_test; +-- We build index with different BRIN opclasses, but we can't use minmax-multi +-- because the summary is not deterministic (i.e. the order of values affects +-- how ranges get merged, and if workers see random subsets between runs, that +-- affects the resulting summaries too). The summaries are equivalent in the +-- sense that should give the same query results, but look different enough to +-- not be very useful/convenient for this test. +SET max_parallel_maintenance_workers = 0; +CREATE INDEX brin_test_serial_idx ON brin_parallel_test USING brin (a int4_minmax_ops, a int4_bloom_ops, b) WITH (pages_per_range=7); +-- XXX we have to set some parameters to force parallel build, both because +-- there's a requirement for table size and plan_create_index_workers assumes +-- each worker will use work_mem=32MB for sorting (which works for btree, but +-- not really for BRIN) +SET min_parallel_table_scan_size = 0; +SET max_parallel_maintenance_workers = 4; +SET maintenance_work_mem = '128MB'; +CREATE INDEX brin_test_parallel_idx ON brin_parallel_test USING brin (a int4_minmax_ops, a int4_bloom_ops, b) WITH (pages_per_range=7); +SELECT relname, relpages FROM pg_class WHERE relname IN ('brin_test_serial_idx', 'brin_test_parallel_idx') ORDER BY relname; + relname | relpages +------------------------+---------- + brin_test_parallel_idx | 4 + brin_test_serial_idx | 4 +(2 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +-- now do the same parallel build, but don't allow starting parallel workers +-- to force fallback to serial build, and repeat the checks +DROP INDEX brin_test_parallel_idx; +SET max_parallel_workers = 0; +CREATE INDEX brin_test_parallel_idx ON brin_parallel_test USING brin (a int4_minmax_ops, a int4_bloom_ops, b) WITH (pages_per_range=7); +SELECT relname, relpages FROM pg_class WHERE relname IN ('brin_test_serial_idx', 'brin_test_parallel_idx') ORDER BY relname; + relname | relpages +------------------------+---------- + brin_test_parallel_idx | 4 + brin_test_serial_idx | 4 +(2 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx'); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- +(0 rows) + +DROP TABLE brin_parallel_test; +RESET min_parallel_table_scan_size; +RESET max_parallel_maintenance_workers; +RESET maintenance_work_mem; diff --git a/contrib/pageinspect/sql/brin.sql b/contrib/pageinspect/sql/brin.sql index 50f260b8e1f..affd805bb27 100644 --- a/contrib/pageinspect/sql/brin.sql +++ b/contrib/pageinspect/sql/brin.sql @@ -54,3 +54,88 @@ SELECT (COUNT(*) = (SELECT relpages FROM pg_class WHERE relname = 'test2')) AS r DROP TABLE test1; DROP TABLE test2; + +-- test that parallel index build produces the same BRIN index as serial build +CREATE TABLE brin_parallel_test (a int, b text) WITH (fillfactor=25); + +-- a mix of NULLs and non-NULL values +INSERT INTO brin_parallel_test +SELECT (CASE WHEN (mod(i,231) = 0) OR (i BETWEEN 3500 AND 4000) THEN NULL ELSE i END), + (CASE WHEN (mod(i,233) = 0) OR (i BETWEEN 3750 AND 4250) THEN NULL ELSE md5(i::text) END) + FROM generate_series(1,10000) S(i); + +-- delete a couple pages, to make a range empty +DELETE FROM brin_parallel_test WHERE a BETWEEN 1000 and 2000; + +-- vacuum to make the ranges actually empty +VACUUM brin_parallel_test; + +-- We build index with different BRIN opclasses, but we can't use minmax-multi +-- because the summary is not deterministic (i.e. the order of values affects +-- how ranges get merged, and if workers see random subsets between runs, that +-- affects the resulting summaries too). The summaries are equivalent in the +-- sense that should give the same query results, but look different enough to +-- not be very useful/convenient for this test. +SET max_parallel_maintenance_workers = 0; +CREATE INDEX brin_test_serial_idx ON brin_parallel_test USING brin (a int4_minmax_ops, a int4_bloom_ops, b) WITH (pages_per_range=7); + +-- XXX we have to set some parameters to force parallel build, both because +-- there's a requirement for table size and plan_create_index_workers assumes +-- each worker will use work_mem=32MB for sorting (which works for btree, but +-- not really for BRIN) +SET min_parallel_table_scan_size = 0; +SET max_parallel_maintenance_workers = 4; +SET maintenance_work_mem = '128MB'; +CREATE INDEX brin_test_parallel_idx ON brin_parallel_test USING brin (a int4_minmax_ops, a int4_bloom_ops, b) WITH (pages_per_range=7); + +SELECT relname, relpages FROM pg_class WHERE relname IN ('brin_test_serial_idx', 'brin_test_parallel_idx') ORDER BY relname; + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx'); + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx'); + + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx'); + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx'); + +-- now do the same parallel build, but don't allow starting parallel workers +-- to force fallback to serial build, and repeat the checks + +DROP INDEX brin_test_parallel_idx; + +SET max_parallel_workers = 0; +CREATE INDEX brin_test_parallel_idx ON brin_parallel_test USING brin (a int4_minmax_ops, a int4_bloom_ops, b) WITH (pages_per_range=7); + +SELECT relname, relpages FROM pg_class WHERE relname IN ('brin_test_serial_idx', 'brin_test_parallel_idx') ORDER BY relname; + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx'); + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 2), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 2), 'brin_test_parallel_idx'); + + +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx'); + +SELECT * FROM brin_page_items(get_raw_page('brin_test_serial_idx', 3), 'brin_test_serial_idx') +EXCEPT +SELECT * FROM brin_page_items(get_raw_page('brin_test_parallel_idx', 3), 'brin_test_parallel_idx'); + + +DROP TABLE brin_parallel_test; +RESET min_parallel_table_scan_size; +RESET max_parallel_maintenance_workers; +RESET maintenance_work_mem; -- 2.44.0