DO $$ DECLARE v_version numeric; BEGIN SELECT substring(setting from '\d+')::numeric INTO v_version FROM pg_settings WHERE name = 'server_version'; -- GUCs common to all versions PERFORM set_config('extra_float_digits', '0', false); PERFORM set_config('parallel_setup_cost', '0', false); PERFORM set_config('parallel_tuple_cost', '0', false); PERFORM set_config('max_parallel_workers_per_gather', '2', false); -- 9.6 used a different GUC name for relation size IF v_version < 10 THEN PERFORM set_config('min_parallel_relation_size', '1kB', false); ELSE PERFORM set_config('min_parallel_table_scan_size', '1kB', false); END IF; -- on 14 and later, disable Memoize nodes to make EXPLAIN more consistent IF v_version >= 14 THEN PERFORM set_config('enable_memoize', 'off', false); END IF; END; $$ LANGUAGE plpgsql; -- the user-facing functions must all be parallel safe, otherwise queries -- using them lose parallelism (the aggregate support functions take or -- return "internal" and cannot be called from SQL, so they are exempt) SELECT p.proname, p.proparallel FROM pg_proc p JOIN pg_depend d ON (d.objid = p.oid AND d.deptype = 'e') JOIN pg_extension e ON (e.oid = d.refobjid) WHERE e.extname = 'tdigest' AND p.proargtypes::oid[] && ARRAY['internal'::regtype]::oid[] IS NOT TRUE AND p.prorettype <> 'internal'::regtype AND p.proparallel <> 's' ORDER BY 1; proname | proparallel ---------+------------- (0 rows) -- test parallel query CREATE TABLE test_parallel (v double precision, c int, d int); INSERT INTO test_parallel SELECT 1000 * random(), 1 + mod(i,7), mod(i,113) FROM generate_series(1,100000) s(i); ANALYZE test_parallel; CREATE TABLE test_parallel_2 (d tdigest); INSERT INTO test_parallel_2 SELECT tdigest(v, 100) FROM test_parallel GROUP BY d; ANALYZE test_parallel_2; -- individual values EXPLAIN (COSTS OFF) WITH x AS (SELECT percentile_disc(0.95) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT 0.95, abs(a - b) / 1000 < 0.01 FROM ( SELECT (SELECT p FROM x) AS a, tdigest_percentile(v, 100, 0.95) AS b FROM test_parallel) foo; QUERY PLAN ------------------------------------------------ Subquery Scan on foo CTE x -> Aggregate -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel test_parallel_1 -> Aggregate InitPlan 2 (returns $2) -> CTE Scan on x -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel (12 rows) WITH x AS (SELECT percentile_disc(0.95) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT 0.95, abs(a - b) / 1000 < 0.01 FROM ( SELECT (SELECT p FROM x) AS a, tdigest_percentile(v, 100, 0.95) AS b FROM test_parallel) foo; ?column? | ?column? ----------+---------- 0.95 | t (1 row) EXPLAIN (COSTS OFF) SELECT 950, abs(a - b) < 0.01 FROM ( SELECT 0.95 AS a, tdigest_percentile_of(v, 100, 950) AS b FROM test_parallel) foo; QUERY PLAN ------------------------------------------------ Subquery Scan on foo -> Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel (6 rows) SELECT 950, abs(a - b) < 0.01 FROM ( SELECT 0.95 AS a, tdigest_percentile_of(v, 100, 950) AS b FROM test_parallel) foo; ?column? | ?column? ----------+---------- 950 | t (1 row) EXPLAIN (COSTS OFF) WITH x AS (SELECT percentile_disc(0.95) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT 0.95, abs(a - b) / 1000 < 0.01 FROM ( SELECT (SELECT p FROM x) AS a, tdigest_percentile(d, 0.95) AS b FROM test_parallel_2) foo; QUERY PLAN -------------------------------------------- Subquery Scan on foo CTE x -> Aggregate -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel -> Aggregate InitPlan 2 (returns $2) -> CTE Scan on x -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel_2 (12 rows) WITH x AS (SELECT percentile_disc(0.95) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT 0.95, abs(a - b) / 1000 < 0.01 FROM ( SELECT (SELECT p FROM x) AS a, tdigest_percentile(d, 0.95) AS b FROM test_parallel_2) foo; ?column? | ?column? ----------+---------- 0.95 | t (1 row) EXPLAIN (COSTS OFF) SELECT 950, abs(a - b) < 0.01 FROM ( SELECT 0.95 AS a, tdigest_percentile_of(d, 950) AS b FROM test_parallel_2) foo; QUERY PLAN ------------------------------------------------- Subquery Scan on foo -> Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 (6 rows) SELECT 950, abs(a - b) < 0.01 FROM ( SELECT 0.95 AS a, tdigest_percentile_of(d, 950) AS b FROM test_parallel_2) foo; ?column? | ?column? ----------+---------- 950 | t (1 row) -- array of percentiles / values EXPLAIN (COSTS OFF) WITH x AS (SELECT percentile_disc(ARRAY[0.0, 0.95, 0.99, 1.0]) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT p, abs(a - b) / 1000 < 0.01 FROM ( SELECT unnest(ARRAY[0.0, 0.95, 0.99, 1.0]) p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile(v, 100, ARRAY[0.0, 0.95, 0.99, 1.0])) AS b FROM test_parallel) foo; QUERY PLAN ------------------------------------------------------ Subquery Scan on foo CTE x -> Aggregate -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel test_parallel_1 -> ProjectSet InitPlan 2 (returns $2) -> CTE Scan on x -> Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel (14 rows) WITH x AS (SELECT percentile_disc(ARRAY[0.0, 0.95, 0.99, 1.0]) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT p, abs(a - b) / 1000 < 0.01 FROM ( SELECT unnest(ARRAY[0.0, 0.95, 0.99, 1.0]) p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile(v, 100, ARRAY[0.0, 0.95, 0.99, 1.0])) AS b FROM test_parallel) foo; p | ?column? ------+---------- 0.0 | t 0.95 | t 0.99 | t 1.0 | t (4 rows) EXPLAIN (COSTS OFF) WITH x AS (SELECT array_agg((SELECT percent_rank(f) WITHIN GROUP (ORDER BY v) FROM test_parallel)) AS p FROM unnest(ARRAY[950, 990]) f) SELECT p, abs(a - b) < 0.01 FROM ( SELECT unnest(ARRAY[950, 990]) AS p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile_of(v, 100, ARRAY[950, 990])) AS b FROM test_parallel) foo; QUERY PLAN -------------------------------------------------------- Subquery Scan on foo CTE x -> Aggregate -> Function Scan on unnest f SubPlan 1 -> Aggregate -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel test_parallel_1 -> ProjectSet InitPlan 3 (returns $3) -> CTE Scan on x -> Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel (17 rows) WITH x AS (SELECT array_agg((SELECT percent_rank(f) WITHIN GROUP (ORDER BY v) FROM test_parallel)) AS p FROM unnest(ARRAY[950, 990]) f) SELECT p, abs(a - b) < 0.01 FROM ( SELECT unnest(ARRAY[950, 990]) AS p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile_of(v, 100, ARRAY[950, 990])) AS b FROM test_parallel) foo; p | ?column? -----+---------- 950 | t 990 | t (2 rows) EXPLAIN (COSTS OFF) WITH x AS (SELECT percentile_disc(ARRAY[0.0, 0.95, 0.99, 1.0]) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT p, abs(a - b) / 1000 < 0.01 FROM ( SELECT unnest(ARRAY[0.0, 0.95, 0.99, 1.0]) p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile(d, ARRAY[0.0, 0.95, 0.99, 1.0])) AS b FROM test_parallel_2) foo; QUERY PLAN ------------------------------------------------------- Subquery Scan on foo CTE x -> Aggregate -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel -> ProjectSet InitPlan 2 (returns $2) -> CTE Scan on x -> Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 (14 rows) WITH x AS (SELECT percentile_disc(ARRAY[0.0, 0.95, 0.99, 1.0]) WITHIN GROUP (ORDER BY v) AS p FROM test_parallel) SELECT p, abs(a - b) / 1000 < 0.01 FROM ( SELECT unnest(ARRAY[0.0, 0.95, 0.99, 1.0]) p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile(d, ARRAY[0.0, 0.95, 0.99, 1.0])) AS b FROM test_parallel_2) foo; p | ?column? ------+---------- 0.0 | t 0.95 | t 0.99 | t 1.0 | t (4 rows) EXPLAIN (COSTS OFF) WITH x AS (SELECT array_agg((SELECT percent_rank(f) WITHIN GROUP (ORDER BY v) FROM test_parallel)) AS p FROM unnest(ARRAY[950, 990]) f) SELECT p, abs(a - b) < 0.01 FROM ( SELECT unnest(ARRAY[950, 990]) AS p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile_of(d, ARRAY[950, 990])) AS b FROM test_parallel_2) foo; QUERY PLAN ------------------------------------------------------- Subquery Scan on foo CTE x -> Aggregate -> Function Scan on unnest f SubPlan 1 -> Aggregate -> Gather Workers Planned: 2 -> Parallel Seq Scan on test_parallel -> ProjectSet InitPlan 3 (returns $3) -> CTE Scan on x -> Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 (17 rows) WITH x AS (SELECT array_agg((SELECT percent_rank(f) WITHIN GROUP (ORDER BY v) FROM test_parallel)) AS p FROM unnest(ARRAY[950, 990]) f) SELECT p, abs(a - b) < 0.01 FROM ( SELECT unnest(ARRAY[950, 990]) AS p, unnest((SELECT p FROM x)) AS a, unnest(tdigest_percentile_of(d, ARRAY[950, 990])) AS b FROM test_parallel_2) foo; p | ?column? -----+---------- 950 | t 990 | t (2 rows) -- trimmed aggregates EXPLAIN (COSTS OFF) SELECT tdigest_sum(v, 100, 0.05, 0.95) FROM test_parallel; QUERY PLAN ------------------------------------------------------ Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel (5 rows) -- trimming nothing has to reproduce the exact sum SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT sum(v) FROM test_parallel) AS a, tdigest_sum(v, 100, 0.0, 1.0) AS b FROM test_parallel) foo; ?column? ---------- t (1 row) EXPLAIN (COSTS OFF) SELECT tdigest_avg(v, 100, 0.05, 0.95) FROM test_parallel; QUERY PLAN ------------------------------------------------------ Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel (5 rows) SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT avg(v) FROM test_parallel) AS a, tdigest_avg(v, 100, 0.0, 1.0) AS b FROM test_parallel) foo; ?column? ---------- t (1 row) EXPLAIN (COSTS OFF) SELECT tdigest_sum(d, 0.05, 0.95) FROM test_parallel_2; QUERY PLAN -------------------------------------------------------- Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 (5 rows) SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT sum(v) FROM test_parallel) AS a, tdigest_sum(d, 0.0, 1.0) AS b FROM test_parallel_2) foo; ?column? ---------- t (1 row) EXPLAIN (COSTS OFF) SELECT tdigest_avg(d, 0.05, 0.95) FROM test_parallel_2; QUERY PLAN -------------------------------------------------------- Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 (5 rows) SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT avg(v) FROM test_parallel) AS a, tdigest_avg(d, 0.0, 1.0) AS b FROM test_parallel_2) foo; ?column? ---------- t (1 row) -- trimmed aggregates (on tdigest) EXPLAIN (COSTS OFF) SELECT tdigest_digest_sum(tdigest(v, 100), 0.05, 0.95) FROM test_parallel; QUERY PLAN ------------------------------------------------------ Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel (5 rows) -- trimming nothing has to reproduce the exact sum SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT sum(v) FROM test_parallel) AS a, tdigest_digest_sum(tdigest(v, 100), 0.0, 1.0) AS b FROM test_parallel) foo; ?column? ---------- t (1 row) EXPLAIN (COSTS OFF) SELECT tdigest_digest_avg(tdigest(v, 100), 0.05, 0.95) FROM test_parallel; QUERY PLAN ------------------------------------------------------ Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel (5 rows) SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT avg(v) FROM test_parallel) AS a, tdigest_digest_avg(tdigest(v, 100), 0.0, 1.0) AS b FROM test_parallel) foo; ?column? ---------- t (1 row) EXPLAIN (COSTS OFF) SELECT tdigest_digest_sum(tdigest(d), 0.05, 0.95) FROM test_parallel_2; QUERY PLAN -------------------------------------------------------- Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 (5 rows) SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT sum(v) FROM test_parallel) AS a, tdigest_digest_sum(tdigest(d), 0.0, 1.0) AS b FROM test_parallel_2) foo; ?column? ---------- t (1 row) EXPLAIN (COSTS OFF) SELECT tdigest_digest_avg(tdigest(d), 0.05, 0.95) FROM test_parallel_2; QUERY PLAN -------------------------------------------------------- Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 (5 rows) SELECT abs(a - b) / a < 0.01 FROM ( SELECT (SELECT avg(v) FROM test_parallel) AS a, tdigest_digest_avg(tdigest(d), 0.0, 1.0) AS b FROM test_parallel_2) foo; ?column? ---------- t (1 row) -- casting a digest to text must not force a serial plan EXPLAIN (COSTS OFF) SELECT count(*) FROM test_parallel_2 WHERE length(d::text) > 5; QUERY PLAN -------------------------------------------------------- Finalize Aggregate -> Gather Workers Planned: 2 -> Partial Aggregate -> Parallel Seq Scan on test_parallel_2 Filter: (length((d)::text) > 5) (6 rows) DROP TABLE test_parallel; DROP TABLE test_parallel_2;