1 -- Perform tests on the Memoize node.
2 -- The cache hits/misses/evictions from the Memoize node can vary between
3 -- machines. Let's just replace the number with an 'N'. In order to allow us
4 -- to perform validation when the measure was zero, we replace a zero value
5 -- with "Zero". All other numbers are replaced with 'N'.
6 create function explain_memoize(query text, hide_hitmiss bool) returns setof text
13 execute format('explain (analyze, costs off, summary off, timing off) %s',
16 if hide_hitmiss = true then
17 ln := regexp_replace(ln, 'Hits: 0', 'Hits: Zero');
18 ln := regexp_replace(ln, 'Hits: \d+', 'Hits: N');
19 ln := regexp_replace(ln, 'Misses: 0', 'Misses: Zero');
20 ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N');
22 ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero');
23 ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N');
24 ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N');
25 ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
26 ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
31 -- Ensure we get a memoize node on the inner side of the nested loop
32 SET enable_hashjoin TO off;
33 SET enable_bitmapscan TO off;
34 SELECT explain_memoize('
35 SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
36 INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty
37 WHERE t2.unique1 < 1000;', false);
39 -------------------------------------------------------------------------------------------
40 Aggregate (actual rows=1 loops=N)
41 -> Nested Loop (actual rows=1000 loops=N)
42 -> Seq Scan on tenk1 t2 (actual rows=1000 loops=N)
43 Filter: (unique1 < 1000)
44 Rows Removed by Filter: 9000
45 -> Memoize (actual rows=1 loops=N)
48 Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
49 -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
50 Index Cond: (unique1 = t2.twenty)
54 -- And check we get the expected results.
55 SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
56 INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty
57 WHERE t2.unique1 < 1000;
59 -------+--------------------
60 1000 | 9.5000000000000000
63 -- Try with LATERAL joins
64 SELECT explain_memoize('
65 SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
66 LATERAL (SELECT t2.unique1 FROM tenk1 t2
67 WHERE t1.twenty = t2.unique1 OFFSET 0) t2
68 WHERE t1.unique1 < 1000;', false);
70 -------------------------------------------------------------------------------------------
71 Aggregate (actual rows=1 loops=N)
72 -> Nested Loop (actual rows=1000 loops=N)
73 -> Seq Scan on tenk1 t1 (actual rows=1000 loops=N)
74 Filter: (unique1 < 1000)
75 Rows Removed by Filter: 9000
76 -> Memoize (actual rows=1 loops=N)
79 Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
80 -> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1 loops=N)
81 Index Cond: (unique1 = t1.twenty)
85 -- And check we get the expected results.
86 SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
87 LATERAL (SELECT t2.unique1 FROM tenk1 t2
88 WHERE t1.twenty = t2.unique1 OFFSET 0) t2
89 WHERE t1.unique1 < 1000;
91 -------+--------------------
92 1000 | 9.5000000000000000
95 -- Reduce work_mem and hash_mem_multiplier so that we see some cache evictions
96 SET work_mem TO '64kB';
97 SET hash_mem_multiplier TO 1.0;
98 SET enable_mergejoin TO off;
99 -- Ensure we get some evictions. We're unable to validate the hits and misses
100 -- here as the number of entries that fit in the cache at once will vary
101 -- between different machines.
102 SELECT explain_memoize('
103 SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
104 INNER JOIN tenk1 t2 ON t1.unique1 = t2.thousand
105 WHERE t2.unique1 < 1200;', true);
107 -------------------------------------------------------------------------------------------
108 Aggregate (actual rows=1 loops=N)
109 -> Nested Loop (actual rows=1200 loops=N)
110 -> Seq Scan on tenk1 t2 (actual rows=1200 loops=N)
111 Filter: (unique1 < 1200)
112 Rows Removed by Filter: 8800
113 -> Memoize (actual rows=1 loops=N)
114 Cache Key: t2.thousand
116 Hits: N Misses: N Evictions: N Overflows: 0 Memory Usage: NkB
117 -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
118 Index Cond: (unique1 = t2.thousand)
122 CREATE TABLE flt (f float);
123 CREATE INDEX flt_f_idx ON flt (f);
124 INSERT INTO flt VALUES('-0.0'::float),('+0.0'::float);
126 SET enable_seqscan TO off;
127 -- Ensure memoize operates in logical mode
128 SELECT explain_memoize('
129 SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
131 -------------------------------------------------------------------------------
132 Nested Loop (actual rows=4 loops=N)
133 -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
135 -> Memoize (actual rows=2 loops=N)
138 Hits: 1 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB
139 -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
140 Index Cond: (f = f1.f)
144 -- Ensure memoize operates in binary mode
145 SELECT explain_memoize('
146 SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
148 -------------------------------------------------------------------------------
149 Nested Loop (actual rows=4 loops=N)
150 -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
152 -> Memoize (actual rows=2 loops=N)
155 Hits: 0 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB
156 -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
157 Index Cond: (f <= f1.f)
162 -- Exercise Memoize in binary mode with a large fixed width type and a
164 CREATE TABLE strtest (n name, t text);
165 CREATE INDEX strtest_n_idx ON strtest (n);
166 CREATE INDEX strtest_t_idx ON strtest (t);
167 INSERT INTO strtest VALUES('one','one'),('two','two'),('three',repeat(fipshash('three'),100));
168 -- duplicate rows so we get some cache hits
169 INSERT INTO strtest SELECT * FROM strtest;
171 -- Ensure we get 3 hits and 3 misses
172 SELECT explain_memoize('
173 SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
175 ----------------------------------------------------------------------------------
176 Nested Loop (actual rows=24 loops=N)
177 -> Seq Scan on strtest s1 (actual rows=6 loops=N)
178 -> Memoize (actual rows=4 loops=N)
181 Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
182 -> Index Scan using strtest_n_idx on strtest s2 (actual rows=4 loops=N)
183 Index Cond: (n <= s1.n)
186 -- Ensure we get 3 hits and 3 misses
187 SELECT explain_memoize('
188 SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
190 ----------------------------------------------------------------------------------
191 Nested Loop (actual rows=24 loops=N)
192 -> Seq Scan on strtest s1 (actual rows=6 loops=N)
193 -> Memoize (actual rows=4 loops=N)
196 Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
197 -> Index Scan using strtest_t_idx on strtest s2 (actual rows=4 loops=N)
198 Index Cond: (t <= s1.t)
202 -- Ensure memoize works with partitionwise join
203 SET enable_partitionwise_join TO on;
204 CREATE TABLE prt (a int) PARTITION BY RANGE(a);
205 CREATE TABLE prt_p1 PARTITION OF prt FOR VALUES FROM (0) TO (10);
206 CREATE TABLE prt_p2 PARTITION OF prt FOR VALUES FROM (10) TO (20);
207 INSERT INTO prt VALUES (0), (0), (0), (0);
208 INSERT INTO prt VALUES (10), (10), (10), (10);
209 CREATE INDEX iprt_p1_a ON prt_p1 (a);
210 CREATE INDEX iprt_p2_a ON prt_p2 (a);
212 SELECT explain_memoize('
213 SELECT * FROM prt t1 INNER JOIN prt t2 ON t1.a = t2.a;', false);
215 ------------------------------------------------------------------------------------------
216 Append (actual rows=32 loops=N)
217 -> Nested Loop (actual rows=16 loops=N)
218 -> Index Only Scan using iprt_p1_a on prt_p1 t1_1 (actual rows=4 loops=N)
220 -> Memoize (actual rows=4 loops=N)
223 Hits: 3 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB
224 -> Index Only Scan using iprt_p1_a on prt_p1 t2_1 (actual rows=4 loops=N)
225 Index Cond: (a = t1_1.a)
227 -> Nested Loop (actual rows=16 loops=N)
228 -> Index Only Scan using iprt_p2_a on prt_p2 t1_2 (actual rows=4 loops=N)
230 -> Memoize (actual rows=4 loops=N)
233 Hits: 3 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB
234 -> Index Only Scan using iprt_p2_a on prt_p2 t2_2 (actual rows=4 loops=N)
235 Index Cond: (a = t1_2.a)
239 -- Ensure memoize works with parameterized union-all Append path
240 SET enable_partitionwise_join TO off;
241 SELECT explain_memoize('
242 SELECT * FROM prt_p1 t1 INNER JOIN
243 (SELECT * FROM prt_p1 UNION ALL SELECT * FROM prt_p2) t2
244 ON t1.a = t2.a;', false);
246 -------------------------------------------------------------------------------------
247 Nested Loop (actual rows=16 loops=N)
248 -> Index Only Scan using iprt_p1_a on prt_p1 t1 (actual rows=4 loops=N)
250 -> Memoize (actual rows=4 loops=N)
253 Hits: 3 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB
254 -> Append (actual rows=4 loops=N)
255 -> Index Only Scan using iprt_p1_a on prt_p1 (actual rows=4 loops=N)
256 Index Cond: (a = t1.a)
258 -> Index Only Scan using iprt_p2_a on prt_p2 (actual rows=0 loops=N)
259 Index Cond: (a = t1.a)
264 RESET enable_partitionwise_join;
265 -- Exercise Memoize code that flushes the cache when a parameter changes which
266 -- is not part of the cache key.
267 -- Ensure we get a Memoize plan
269 SELECT unique1 FROM tenk1 t0
272 SELECT 1 FROM tenk1 t1
273 INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred
274 WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0);
276 ----------------------------------------------------------------
277 Index Scan using tenk1_unique1 on tenk1 t0
278 Index Cond: (unique1 < 3)
282 -> Index Scan using tenk1_hundred on tenk1 t2
283 Filter: (t0.two <> four)
285 Cache Key: t2.hundred
287 -> Index Scan using tenk1_unique1 on tenk1 t1
288 Index Cond: (unique1 = t2.hundred)
289 Filter: (t0.ten = twenty)
292 -- Ensure the above query returns the correct result
293 SELECT unique1 FROM tenk1 t0
296 SELECT 1 FROM tenk1 t1
297 INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred
298 WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0);
304 RESET enable_seqscan;
305 RESET enable_mergejoin;
307 RESET hash_mem_multiplier;
308 RESET enable_bitmapscan;
309 RESET enable_hashjoin;
310 -- Test parallel plans with Memoize
311 SET min_parallel_table_scan_size TO 0;
312 SET parallel_setup_cost TO 0;
313 SET parallel_tuple_cost TO 0;
314 SET max_parallel_workers_per_gather TO 2;
315 -- Ensure we get a parallel plan.
317 SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
318 LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
319 WHERE t1.unique1 < 1000;
321 -------------------------------------------------------------------------------
327 -> Parallel Bitmap Heap Scan on tenk1 t1
328 Recheck Cond: (unique1 < 1000)
329 -> Bitmap Index Scan on tenk1_unique1
330 Index Cond: (unique1 < 1000)
334 -> Index Only Scan using tenk1_unique1 on tenk1 t2
335 Index Cond: (unique1 = t1.twenty)
338 -- And ensure the parallel plan gives us the correct results.
339 SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
340 LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
341 WHERE t1.unique1 < 1000;
343 -------+--------------------
344 1000 | 9.5000000000000000
347 RESET max_parallel_workers_per_gather;
348 RESET parallel_tuple_cost;
349 RESET parallel_setup_cost;
350 RESET min_parallel_table_scan_size;