Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 0f96965

Browse files
committed
pgstat: add pg_stat_force_next_flush(), use it to simplify tests.
In the stats collector days it was hard to write tests for the stats system, because fundamentally delivery of stats messages over UDP was not synchronous (nor guaranteed). Now we easily can force pending stats updates to be flushed synchronously. This moves stats.sql into a parallel group, there isn't a reason for it to run in isolation anymore. And it may shake out some bugs. Bumps catversion. Author: Andres Freund <andres@anarazel.de> Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
1 parent 5e07d3d commit 0f96965

File tree

11 files changed

+103
-354
lines changed

11 files changed

+103
-354
lines changed

contrib/test_decoding/expected/stats.out

Lines changed: 10 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,6 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_stats', '
77
(1 row)
88

99
CREATE TABLE stats_test(data text);
10-
-- function to wait for counters to advance
11-
CREATE FUNCTION wait_for_decode_stats(check_reset bool, check_spill_txns bool) RETURNS void AS $$
12-
DECLARE
13-
start_time timestamptz := clock_timestamp();
14-
updated bool;
15-
BEGIN
16-
-- we don't want to wait forever; loop will exit after 30 seconds
17-
FOR i IN 1 .. 300 LOOP
18-
19-
IF check_spill_txns THEN
20-
21-
-- check to see if all updates have been reset/updated
22-
SELECT CASE WHEN check_reset THEN (spill_txns = 0)
23-
ELSE (spill_txns > 0)
24-
END
25-
INTO updated
26-
FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
27-
28-
ELSE
29-
30-
-- check to see if all updates have been reset/updated
31-
SELECT CASE WHEN check_reset THEN (total_txns = 0)
32-
ELSE (total_txns > 0)
33-
END
34-
INTO updated
35-
FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
36-
37-
END IF;
38-
39-
exit WHEN updated;
40-
41-
-- wait a little
42-
perform pg_sleep_for('100 milliseconds');
43-
44-
-- reset stats snapshot so we can test again
45-
perform pg_stat_clear_snapshot();
46-
47-
END LOOP;
48-
49-
-- report time waited in postmaster log (where it won't change test output)
50-
RAISE LOG 'wait_for_decode_stats delayed % seconds',
51-
extract(epoch from clock_timestamp() - start_time);
52-
END
53-
$$ LANGUAGE plpgsql;
5410
-- non-spilled xact
5511
SET logical_decoding_work_mem to '64MB';
5612
INSERT INTO stats_test values(1);
@@ -60,9 +16,9 @@ SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats', NULL,
6016
3
6117
(1 row)
6218

63-
SELECT wait_for_decode_stats(false, false);
64-
wait_for_decode_stats
65-
-----------------------
19+
SELECT pg_stat_force_next_flush();
20+
pg_stat_force_next_flush
21+
--------------------------
6622

6723
(1 row)
6824

@@ -73,19 +29,13 @@ SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count,
7329
(1 row)
7430

7531
RESET logical_decoding_work_mem;
76-
-- reset the slot stats, and wait for stats collector's total txn to reset
32+
-- reset the slot stats
7733
SELECT pg_stat_reset_replication_slot('regression_slot_stats');
7834
pg_stat_reset_replication_slot
7935
--------------------------------
8036

8137
(1 row)
8238

83-
SELECT wait_for_decode_stats(true, false);
84-
wait_for_decode_stats
85-
-----------------------
86-
87-
(1 row)
88-
8939
SELECT slot_name, spill_txns, spill_count, total_txns, total_bytes FROM pg_stat_replication_slots;
9040
slot_name | spill_txns | spill_count | total_txns | total_bytes
9141
-----------------------+------------+-------------+------------+-------------
@@ -102,12 +52,12 @@ SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot_stats', NULL,
10252
5002
10353
(1 row)
10454

105-
-- Check stats, wait for the stats collector to update. We can't test the
106-
-- exact stats count as that can vary if any background transaction (say by
107-
-- autovacuum) happens in parallel to the main transaction.
108-
SELECT wait_for_decode_stats(false, true);
109-
wait_for_decode_stats
110-
-----------------------
55+
-- Check stats. We can't test the exact stats count as that can vary if any
56+
-- background transaction (say by autovacuum) happens in parallel to the main
57+
-- transaction.
58+
SELECT pg_stat_force_next_flush();
59+
pg_stat_force_next_flush
60+
--------------------------
11161

11262
(1 row)
11363

@@ -133,7 +83,6 @@ SELECT slot_name FROM pg_stat_replication_slots;
13383
(1 row)
13484

13585
COMMIT;
136-
DROP FUNCTION wait_for_decode_stats(bool, bool);
13786
DROP TABLE stats_test;
13887
SELECT pg_drop_replication_slot('regression_slot_stats');
13988
pg_drop_replication_slot

contrib/test_decoding/sql/stats.sql

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,62 +5,16 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_stats', '
55

66
CREATE TABLE stats_test(data text);
77

8-
-- function to wait for counters to advance
9-
CREATE FUNCTION wait_for_decode_stats(check_reset bool, check_spill_txns bool) RETURNS void AS $$
10-
DECLARE
11-
start_time timestamptz := clock_timestamp();
12-
updated bool;
13-
BEGIN
14-
-- we don't want to wait forever; loop will exit after 30 seconds
15-
FOR i IN 1 .. 300 LOOP
16-
17-
IF check_spill_txns THEN
18-
19-
-- check to see if all updates have been reset/updated
20-
SELECT CASE WHEN check_reset THEN (spill_txns = 0)
21-
ELSE (spill_txns > 0)
22-
END
23-
INTO updated
24-
FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
25-
26-
ELSE
27-
28-
-- check to see if all updates have been reset/updated
29-
SELECT CASE WHEN check_reset THEN (total_txns = 0)
30-
ELSE (total_txns > 0)
31-
END
32-
INTO updated
33-
FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
34-
35-
END IF;
36-
37-
exit WHEN updated;
38-
39-
-- wait a little
40-
perform pg_sleep_for('100 milliseconds');
41-
42-
-- reset stats snapshot so we can test again
43-
perform pg_stat_clear_snapshot();
44-
45-
END LOOP;
46-
47-
-- report time waited in postmaster log (where it won't change test output)
48-
RAISE LOG 'wait_for_decode_stats delayed % seconds',
49-
extract(epoch from clock_timestamp() - start_time);
50-
END
51-
$$ LANGUAGE plpgsql;
52-
538
-- non-spilled xact
549
SET logical_decoding_work_mem to '64MB';
5510
INSERT INTO stats_test values(1);
5611
SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats', NULL, NULL, 'skip-empty-xacts', '1');
57-
SELECT wait_for_decode_stats(false, false);
12+
SELECT pg_stat_force_next_flush();
5813
SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots;
5914
RESET logical_decoding_work_mem;
6015

61-
-- reset the slot stats, and wait for stats collector's total txn to reset
16+
-- reset the slot stats
6217
SELECT pg_stat_reset_replication_slot('regression_slot_stats');
63-
SELECT wait_for_decode_stats(true, false);
6418
SELECT slot_name, spill_txns, spill_count, total_txns, total_bytes FROM pg_stat_replication_slots;
6519

6620
-- spilling the xact
@@ -69,10 +23,10 @@ INSERT INTO stats_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1
6923
COMMIT;
7024
SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot_stats', NULL, NULL, 'skip-empty-xacts', '1');
7125

72-
-- Check stats, wait for the stats collector to update. We can't test the
73-
-- exact stats count as that can vary if any background transaction (say by
74-
-- autovacuum) happens in parallel to the main transaction.
75-
SELECT wait_for_decode_stats(false, true);
26+
-- Check stats. We can't test the exact stats count as that can vary if any
27+
-- background transaction (say by autovacuum) happens in parallel to the main
28+
-- transaction.
29+
SELECT pg_stat_force_next_flush();
7630
SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots;
7731

7832
-- Ensure stats can be repeatedly accessed using the same stats snapshot. See
@@ -82,6 +36,5 @@ SELECT slot_name FROM pg_stat_replication_slots;
8236
SELECT slot_name FROM pg_stat_replication_slots;
8337
COMMIT;
8438

85-
DROP FUNCTION wait_for_decode_stats(bool, bool);
8639
DROP TABLE stats_test;
8740
SELECT pg_drop_replication_slot('regression_slot_stats');

src/backend/utils/activity/pgstat.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ static MemoryContext pgStatPendingContext = NULL;
219219
static dlist_head pgStatPending = DLIST_STATIC_INIT(pgStatPending);
220220

221221

222+
/*
223+
* Force the next stats flush to happen regardless of
224+
* PGSTAT_MIN_INTERVAL. Useful in test scripts.
225+
*/
226+
static bool pgStatForceNextFlush = false;
227+
222228
/*
223229
* For assertions that check pgstat is not used before initialization / after
224230
* shutdown.
@@ -560,6 +566,13 @@ pgstat_report_stat(bool force)
560566
pgstat_assert_is_up();
561567
Assert(!IsTransactionBlock());
562568

569+
/* "absorb" the forced flush even if there's nothing to flush */
570+
if (pgStatForceNextFlush)
571+
{
572+
force = true;
573+
pgStatForceNextFlush = false;
574+
}
575+
563576
/* Don't expend a clock check if nothing to do */
564577
if (dlist_is_empty(&pgStatPending) &&
565578
!have_slrustats &&
@@ -637,6 +650,16 @@ pgstat_report_stat(bool force)
637650
return 0;
638651
}
639652

653+
/*
654+
* Force locally pending stats to be flushed during the next
655+
* pgstat_report_stat() call. This is useful for writing tests.
656+
*/
657+
void
658+
pgstat_force_next_flush(void)
659+
{
660+
pgStatForceNextFlush = true;
661+
}
662+
640663
/*
641664
* Only for use by pgstat_reset_counters()
642665
*/

src/backend/utils/adt/pgstatfuncs.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,16 @@ pg_stat_clear_snapshot(PG_FUNCTION_ARGS)
20672067
}
20682068

20692069

2070+
/* Force statistics to be reported at the next occasion */
2071+
Datum
2072+
pg_stat_force_next_flush(PG_FUNCTION_ARGS)
2073+
{
2074+
pgstat_force_next_flush();
2075+
2076+
PG_RETURN_VOID();
2077+
}
2078+
2079+
20702080
/* Reset all counters for the current database */
20712081
Datum
20722082
pg_stat_reset(PG_FUNCTION_ARGS)

src/include/catalog/pg_proc.dat

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5744,6 +5744,11 @@
57445744
proname => 'pg_stat_clear_snapshot', proisstrict => 'f', provolatile => 'v',
57455745
proparallel => 'r', prorettype => 'void', proargtypes => '',
57465746
prosrc => 'pg_stat_clear_snapshot' },
5747+
{ oid => '2137',
5748+
descr => 'statistics: force stats to be flushed after the next commit',
5749+
proname => 'pg_stat_force_next_flush', proisstrict => 'f', provolatile => 'v',
5750+
proparallel => 'r', prorettype => 'void', proargtypes => '',
5751+
prosrc => 'pg_stat_force_next_flush' },
57475752
{ oid => '2274',
57485753
descr => 'statistics: reset collected statistics for current database',
57495754
proname => 'pg_stat_reset', proisstrict => 'f', provolatile => 'v',

src/include/pgstat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ extern void pgstat_initialize(void);
414414

415415
/* Functions called from backends */
416416
extern long pgstat_report_stat(bool force);
417+
extern void pgstat_force_next_flush(void);
417418

418419
extern void pgstat_reset_counters(void);
419420
extern void pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objectid);

src/test/regress/expected/brin.out

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,3 +603,25 @@ SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100;
603603
1
604604
(1 row)
605605

606+
-- test BRIN index doesn't block HOT update
607+
CREATE TABLE brin_hot (
608+
id integer PRIMARY KEY,
609+
val integer NOT NULL
610+
) WITH (autovacuum_enabled = off, fillfactor = 70);
611+
INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
612+
CREATE INDEX val_brin ON brin_hot using brin(val);
613+
UPDATE brin_hot SET val = -3 WHERE id = 42;
614+
-- ensure pending stats are flushed
615+
SELECT pg_stat_force_next_flush();
616+
pg_stat_force_next_flush
617+
--------------------------
618+
619+
(1 row)
620+
621+
SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
622+
pg_stat_get_tuples_hot_updated
623+
--------------------------------
624+
1
625+
(1 row)
626+
627+
DROP TABLE brin_hot;

0 commit comments

Comments
 (0)