|
| 1 | +LOAD 'pg_session_buffer_usage'; |
| 2 | +CREATE EXTENSION pg_session_buffer_usage; |
| 3 | +-- Verify all columns are non-negative |
| 4 | +SELECT count(*) = 1 AS ok FROM pg_session_buffer_usage() |
| 5 | +WHERE shared_blks_hit >= 0 AND shared_blks_read >= 0 |
| 6 | + AND shared_blks_dirtied >= 0 AND shared_blks_written >= 0 |
| 7 | + AND local_blks_hit >= 0 AND local_blks_read >= 0 |
| 8 | + AND local_blks_dirtied >= 0 AND local_blks_written >= 0 |
| 9 | + AND temp_blks_read >= 0 AND temp_blks_written >= 0 |
| 10 | + AND shared_blk_read_time >= 0 AND shared_blk_write_time >= 0 |
| 11 | + AND local_blk_read_time >= 0 AND local_blk_write_time >= 0 |
| 12 | + AND temp_blk_read_time >= 0 AND temp_blk_write_time >= 0; |
| 13 | + ok |
| 14 | +---- |
| 15 | + t |
| 16 | +(1 row) |
| 17 | + |
| 18 | +-- Verify counters increase after buffer activity |
| 19 | +SELECT pg_session_buffer_usage_reset(); |
| 20 | + pg_session_buffer_usage_reset |
| 21 | +------------------------------- |
| 22 | + |
| 23 | +(1 row) |
| 24 | + |
| 25 | +CREATE TEMP TABLE test_buf_activity (id int, data text); |
| 26 | +INSERT INTO test_buf_activity SELECT i, repeat('x', 100) FROM generate_series(1, 1000) AS i; |
| 27 | +SELECT count(*) FROM test_buf_activity; |
| 28 | + count |
| 29 | +------- |
| 30 | + 1000 |
| 31 | +(1 row) |
| 32 | + |
| 33 | +SELECT local_blks_hit + local_blks_read > 0 AS blocks_increased |
| 34 | +FROM pg_session_buffer_usage(); |
| 35 | + blocks_increased |
| 36 | +------------------ |
| 37 | + t |
| 38 | +(1 row) |
| 39 | + |
| 40 | +DROP TABLE test_buf_activity; |
| 41 | +-- Parallel query test |
| 42 | +CREATE TABLE par_dc_tab (a int, b char(200)); |
| 43 | +INSERT INTO par_dc_tab SELECT i, repeat('x', 200) FROM generate_series(1, 5000) AS i; |
| 44 | +SELECT count(*) FROM par_dc_tab; |
| 45 | + count |
| 46 | +------- |
| 47 | + 5000 |
| 48 | +(1 row) |
| 49 | + |
| 50 | +-- Measure serial scan delta (leader does all the work) |
| 51 | +SET max_parallel_workers_per_gather = 0; |
| 52 | +SELECT pg_session_buffer_usage_reset(); |
| 53 | + pg_session_buffer_usage_reset |
| 54 | +------------------------------- |
| 55 | + |
| 56 | +(1 row) |
| 57 | + |
| 58 | +SELECT count(*) FROM par_dc_tab; |
| 59 | + count |
| 60 | +------- |
| 61 | + 5000 |
| 62 | +(1 row) |
| 63 | + |
| 64 | +CREATE TEMP TABLE dc_serial_result AS |
| 65 | +SELECT shared_blks_hit AS serial_delta FROM pg_session_buffer_usage(); |
| 66 | +-- Measure parallel scan delta with leader NOT participating in scanning. |
| 67 | +-- Workers do all table scanning; leader only runs the Gather node. |
| 68 | +SET parallel_setup_cost = 0; |
| 69 | +SET parallel_tuple_cost = 0; |
| 70 | +SET min_parallel_table_scan_size = 0; |
| 71 | +SET max_parallel_workers_per_gather = 2; |
| 72 | +SET parallel_leader_participation = off; |
| 73 | +SELECT pg_session_buffer_usage_reset(); |
| 74 | + pg_session_buffer_usage_reset |
| 75 | +------------------------------- |
| 76 | + |
| 77 | +(1 row) |
| 78 | + |
| 79 | +SELECT count(*) FROM par_dc_tab; |
| 80 | + count |
| 81 | +------- |
| 82 | + 5000 |
| 83 | +(1 row) |
| 84 | + |
| 85 | +-- Confirm we got a similar hit counter through parallel worker accumulation |
| 86 | +SELECT shared_blks_hit > s.serial_delta / 2 AND shared_blks_hit < s.serial_delta * 2 |
| 87 | + AS leader_buffers_match |
| 88 | +FROM pg_session_buffer_usage(), dc_serial_result s; |
| 89 | + leader_buffers_match |
| 90 | +---------------------- |
| 91 | + t |
| 92 | +(1 row) |
| 93 | + |
| 94 | +RESET parallel_setup_cost; |
| 95 | +RESET parallel_tuple_cost; |
| 96 | +RESET min_parallel_table_scan_size; |
| 97 | +RESET max_parallel_workers_per_gather; |
| 98 | +RESET parallel_leader_participation; |
| 99 | +DROP TABLE par_dc_tab, dc_serial_result; |
| 100 | +-- |
| 101 | +-- Abort/exception tests: verify buffer usage survives various error paths. |
| 102 | +-- |
| 103 | +-- Rolled-back divide-by-zero under EXPLAIN ANALYZE |
| 104 | +CREATE TEMP TABLE exc_tab (a int, b char(20)); |
| 105 | +SELECT pg_session_buffer_usage_reset(); |
| 106 | + pg_session_buffer_usage_reset |
| 107 | +------------------------------- |
| 108 | + |
| 109 | +(1 row) |
| 110 | + |
| 111 | +EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) |
| 112 | + WITH ins AS (INSERT INTO exc_tab VALUES (1, 'aaa') RETURNING a) |
| 113 | + SELECT a / 0 FROM ins; |
| 114 | +ERROR: division by zero |
| 115 | +SELECT local_blks_dirtied > 0 AS exception_buffers_visible |
| 116 | +FROM pg_session_buffer_usage(); |
| 117 | + exception_buffers_visible |
| 118 | +--------------------------- |
| 119 | + t |
| 120 | +(1 row) |
| 121 | + |
| 122 | +DROP TABLE exc_tab; |
| 123 | +-- Unique constraint violation in regular query |
| 124 | +CREATE TEMP TABLE unique_tab (a int UNIQUE, b char(20)); |
| 125 | +INSERT INTO unique_tab VALUES (1, 'first'); |
| 126 | +SELECT pg_session_buffer_usage_reset(); |
| 127 | + pg_session_buffer_usage_reset |
| 128 | +------------------------------- |
| 129 | + |
| 130 | +(1 row) |
| 131 | + |
| 132 | +INSERT INTO unique_tab VALUES (1, 'duplicate'); |
| 133 | +ERROR: duplicate key value violates unique constraint "unique_tab_a_key" |
| 134 | +DETAIL: Key (a)=(1) already exists. |
| 135 | +SELECT local_blks_hit > 0 AS unique_violation_buffers_visible |
| 136 | +FROM pg_session_buffer_usage(); |
| 137 | + unique_violation_buffers_visible |
| 138 | +---------------------------------- |
| 139 | + t |
| 140 | +(1 row) |
| 141 | + |
| 142 | +DROP TABLE unique_tab; |
| 143 | +-- Caught exception in PL/pgSQL subtransaction (BEGIN...EXCEPTION) |
| 144 | +CREATE TEMP TABLE subxact_tab (a int, b char(20)); |
| 145 | +CREATE FUNCTION subxact_exc_func() RETURNS text AS $$ |
| 146 | +BEGIN |
| 147 | + BEGIN |
| 148 | + EXECUTE 'EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) |
| 149 | + WITH ins AS (INSERT INTO subxact_tab VALUES (1, ''aaa'') RETURNING a) |
| 150 | + SELECT a / 0 FROM ins'; |
| 151 | + EXCEPTION WHEN division_by_zero THEN |
| 152 | + RETURN 'caught'; |
| 153 | + END; |
| 154 | + RETURN 'not reached'; |
| 155 | +END; |
| 156 | +$$ LANGUAGE plpgsql; |
| 157 | +SELECT pg_session_buffer_usage_reset(); |
| 158 | + pg_session_buffer_usage_reset |
| 159 | +------------------------------- |
| 160 | + |
| 161 | +(1 row) |
| 162 | + |
| 163 | +SELECT subxact_exc_func(); |
| 164 | + subxact_exc_func |
| 165 | +------------------ |
| 166 | + caught |
| 167 | +(1 row) |
| 168 | + |
| 169 | +SELECT local_blks_dirtied > 0 AS subxact_buffers_visible |
| 170 | +FROM pg_session_buffer_usage(); |
| 171 | + subxact_buffers_visible |
| 172 | +------------------------- |
| 173 | + t |
| 174 | +(1 row) |
| 175 | + |
| 176 | +DROP FUNCTION subxact_exc_func; |
| 177 | +DROP TABLE subxact_tab; |
| 178 | +-- Cursor (FOR loop) in aborted subtransaction; verify post-exception tracking |
| 179 | +CREATE TEMP TABLE cursor_tab (a int, b char(200)); |
| 180 | +INSERT INTO cursor_tab SELECT i, repeat('x', 200) FROM generate_series(1, 500) AS i; |
| 181 | +CREATE FUNCTION cursor_exc_func() RETURNS text AS $$ |
| 182 | +DECLARE |
| 183 | + rec record; |
| 184 | + cnt int := 0; |
| 185 | +BEGIN |
| 186 | + BEGIN |
| 187 | + FOR rec IN SELECT * FROM cursor_tab LOOP |
| 188 | + cnt := cnt + 1; |
| 189 | + IF cnt = 250 THEN |
| 190 | + PERFORM 1 / 0; |
| 191 | + END IF; |
| 192 | + END LOOP; |
| 193 | + EXCEPTION WHEN division_by_zero THEN |
| 194 | + RETURN 'caught after ' || cnt || ' rows'; |
| 195 | + END; |
| 196 | + RETURN 'not reached'; |
| 197 | +END; |
| 198 | +$$ LANGUAGE plpgsql; |
| 199 | +SELECT pg_session_buffer_usage_reset(); |
| 200 | + pg_session_buffer_usage_reset |
| 201 | +------------------------------- |
| 202 | + |
| 203 | +(1 row) |
| 204 | + |
| 205 | +SELECT cursor_exc_func(); |
| 206 | + cursor_exc_func |
| 207 | +----------------------- |
| 208 | + caught after 250 rows |
| 209 | +(1 row) |
| 210 | + |
| 211 | +SELECT local_blks_hit + local_blks_read > 0 |
| 212 | + AS cursor_subxact_buffers_visible |
| 213 | +FROM pg_session_buffer_usage(); |
| 214 | + cursor_subxact_buffers_visible |
| 215 | +-------------------------------- |
| 216 | + t |
| 217 | +(1 row) |
| 218 | + |
| 219 | +DROP FUNCTION cursor_exc_func; |
| 220 | +DROP TABLE cursor_tab; |
| 221 | +-- Parallel worker abort: worker buffer activity is currently NOT propagated on abort. |
| 222 | +-- |
| 223 | +-- When a parallel worker aborts, InstrEndParallelQuery and |
| 224 | +-- ExecParallelReportInstrumentation never run, so the worker's buffer |
| 225 | +-- activity is never written to shared memory, despite the information having been |
| 226 | +-- captured by the res owner release instrumentation handling. |
| 227 | +CREATE TABLE par_abort_tab (a int, b char(200)); |
| 228 | +INSERT INTO par_abort_tab SELECT i, repeat('x', 200) FROM generate_series(1, 5000) AS i; |
| 229 | +-- Warm shared buffers so all reads become hits |
| 230 | +SELECT count(*) FROM par_abort_tab; |
| 231 | + count |
| 232 | +------- |
| 233 | + 5000 |
| 234 | +(1 row) |
| 235 | + |
| 236 | +-- Measure serial scan delta as a reference (leader reads all blocks) |
| 237 | +SET max_parallel_workers_per_gather = 0; |
| 238 | +SELECT pg_session_buffer_usage_reset(); |
| 239 | + pg_session_buffer_usage_reset |
| 240 | +------------------------------- |
| 241 | + |
| 242 | +(1 row) |
| 243 | + |
| 244 | +SELECT b::int2 FROM par_abort_tab WHERE a > 1000; |
| 245 | +ERROR: invalid input syntax for type smallint: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
| 246 | +CREATE TABLE par_abort_serial_result AS |
| 247 | +SELECT shared_blks_hit AS serial_delta FROM pg_session_buffer_usage(); |
| 248 | +-- Now force parallel with leader NOT participating in scanning |
| 249 | +SET parallel_setup_cost = 0; |
| 250 | +SET parallel_tuple_cost = 0; |
| 251 | +SET min_parallel_table_scan_size = 0; |
| 252 | +SET max_parallel_workers_per_gather = 2; |
| 253 | +SET parallel_leader_participation = off; |
| 254 | +SET debug_parallel_query = on; -- Ensure we get CONTEXT line consistently |
| 255 | +SELECT pg_session_buffer_usage_reset(); |
| 256 | + pg_session_buffer_usage_reset |
| 257 | +------------------------------- |
| 258 | + |
| 259 | +(1 row) |
| 260 | + |
| 261 | +SELECT b::int2 FROM par_abort_tab WHERE a > 1000; |
| 262 | +ERROR: invalid input syntax for type smallint: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
| 263 | +CONTEXT: parallel worker |
| 264 | +RESET debug_parallel_query; |
| 265 | +-- Workers scanned the table but aborted before reporting stats back. |
| 266 | +-- The leader's delta should be much less than a serial scan, documenting |
| 267 | +-- that worker buffer activity is lost on abort. |
| 268 | +SELECT shared_blks_hit < s.serial_delta / 2 |
| 269 | + AS worker_abort_buffers_not_propagated |
| 270 | +FROM pg_session_buffer_usage(), par_abort_serial_result s; |
| 271 | + worker_abort_buffers_not_propagated |
| 272 | +------------------------------------- |
| 273 | + t |
| 274 | +(1 row) |
| 275 | + |
| 276 | +RESET parallel_setup_cost; |
| 277 | +RESET parallel_tuple_cost; |
| 278 | +RESET min_parallel_table_scan_size; |
| 279 | +RESET max_parallel_workers_per_gather; |
| 280 | +RESET parallel_leader_participation; |
| 281 | +DROP TABLE par_abort_tab, par_abort_serial_result; |
| 282 | +-- Cleanup |
| 283 | +DROP EXTENSION pg_session_buffer_usage; |
0 commit comments