pgstat: add pg_stat_force_next_flush(), use it to simplify tests.
authorAndres Freund <andres@anarazel.de>
Thu, 7 Apr 2022 06:35:56 +0000 (23:35 -0700)
committerAndres Freund <andres@anarazel.de>
Thu, 7 Apr 2022 06:35:56 +0000 (23:35 -0700)
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

contrib/test_decoding/expected/stats.out
contrib/test_decoding/sql/stats.sql
src/backend/utils/activity/pgstat.c
src/backend/utils/adt/pgstatfuncs.c
src/include/catalog/pg_proc.dat
src/include/pgstat.h
src/test/regress/expected/brin.out
src/test/regress/expected/stats.out
src/test/regress/parallel_schedule
src/test/regress/sql/brin.sql
src/test/regress/sql/stats.sql

index 206c0a126e55de17cbccd70493fe41947f7f42c2..d9aeaa44264323683e002991c5877854754ee7c1 100644 (file)
@@ -7,50 +7,6 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_stats', '
 (1 row)
 
 CREATE TABLE stats_test(data text);
--- function to wait for counters to advance
-CREATE FUNCTION wait_for_decode_stats(check_reset bool, check_spill_txns bool) RETURNS void AS $$
-DECLARE
-  start_time timestamptz := clock_timestamp();
-  updated bool;
-BEGIN
-  -- we don't want to wait forever; loop will exit after 30 seconds
-  FOR i IN 1 .. 300 LOOP
-
-    IF check_spill_txns THEN
-
-      -- check to see if all updates have been reset/updated
-      SELECT CASE WHEN check_reset THEN (spill_txns = 0)
-                  ELSE (spill_txns > 0)
-             END
-      INTO updated
-      FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
-    ELSE
-
-      -- check to see if all updates have been reset/updated
-      SELECT CASE WHEN check_reset THEN (total_txns = 0)
-                  ELSE (total_txns > 0)
-             END
-      INTO updated
-      FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
-    END IF;
-
-    exit WHEN updated;
-
-    -- wait a little
-    perform pg_sleep_for('100 milliseconds');
-
-    -- reset stats snapshot so we can test again
-    perform pg_stat_clear_snapshot();
-
-  END LOOP;
-
-  -- report time waited in postmaster log (where it won't change test output)
-  RAISE LOG 'wait_for_decode_stats delayed % seconds',
-    extract(epoch from clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
 -- non-spilled xact
 SET logical_decoding_work_mem to '64MB';
 INSERT INTO stats_test values(1);
@@ -60,9 +16,9 @@ SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats', NULL,
      3
 (1 row)
 
-SELECT wait_for_decode_stats(false, false);
- wait_for_decode_stats 
------------------------
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
  
 (1 row)
 
@@ -73,19 +29,13 @@ SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count,
 (1 row)
 
 RESET logical_decoding_work_mem;
--- reset the slot stats, and wait for stats collector's total txn to reset
+-- reset the slot stats
 SELECT pg_stat_reset_replication_slot('regression_slot_stats');
  pg_stat_reset_replication_slot 
 --------------------------------
  
 (1 row)
 
-SELECT wait_for_decode_stats(true, false);
- wait_for_decode_stats 
------------------------
-(1 row)
-
 SELECT slot_name, spill_txns, spill_count, total_txns, total_bytes FROM pg_stat_replication_slots;
        slot_name       | spill_txns | spill_count | total_txns | total_bytes 
 -----------------------+------------+-------------+------------+-------------
@@ -102,12 +52,12 @@ SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot_stats', NULL,
   5002
 (1 row)
 
--- Check stats, wait for the stats collector to update. We can't test the
--- exact stats count as that can vary if any background transaction (say by
--- autovacuum) happens in parallel to the main transaction.
-SELECT wait_for_decode_stats(false, true);
- wait_for_decode_stats 
------------------------
+-- Check stats. We can't test the exact stats count as that can vary if any
+-- background transaction (say by autovacuum) happens in parallel to the main
+-- transaction.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
  
 (1 row)
 
@@ -133,7 +83,6 @@ SELECT slot_name FROM pg_stat_replication_slots;
 (1 row)
 
 COMMIT;
-DROP FUNCTION wait_for_decode_stats(bool, bool);
 DROP TABLE stats_test;
 SELECT pg_drop_replication_slot('regression_slot_stats');
  pg_drop_replication_slot 
index 67462ca27f70375bd39de2a656bf674ed35988c4..6592c36a4d9d65fd1207ebe8166fbf4d7b0dd85b 100644 (file)
@@ -5,62 +5,16 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_stats', '
 
 CREATE TABLE stats_test(data text);
 
--- function to wait for counters to advance
-CREATE FUNCTION wait_for_decode_stats(check_reset bool, check_spill_txns bool) RETURNS void AS $$
-DECLARE
-  start_time timestamptz := clock_timestamp();
-  updated bool;
-BEGIN
-  -- we don't want to wait forever; loop will exit after 30 seconds
-  FOR i IN 1 .. 300 LOOP
-
-    IF check_spill_txns THEN
-
-      -- check to see if all updates have been reset/updated
-      SELECT CASE WHEN check_reset THEN (spill_txns = 0)
-                  ELSE (spill_txns > 0)
-             END
-      INTO updated
-      FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
-    ELSE
-
-      -- check to see if all updates have been reset/updated
-      SELECT CASE WHEN check_reset THEN (total_txns = 0)
-                  ELSE (total_txns > 0)
-             END
-      INTO updated
-      FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
-    END IF;
-
-    exit WHEN updated;
-
-    -- wait a little
-    perform pg_sleep_for('100 milliseconds');
-
-    -- reset stats snapshot so we can test again
-    perform pg_stat_clear_snapshot();
-
-  END LOOP;
-
-  -- report time waited in postmaster log (where it won't change test output)
-  RAISE LOG 'wait_for_decode_stats delayed % seconds',
-    extract(epoch from clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
-
 -- non-spilled xact
 SET logical_decoding_work_mem to '64MB';
 INSERT INTO stats_test values(1);
 SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats', NULL, NULL, 'skip-empty-xacts', '1');
-SELECT wait_for_decode_stats(false, false);
+SELECT pg_stat_force_next_flush();
 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;
 RESET logical_decoding_work_mem;
 
--- reset the slot stats, and wait for stats collector's total txn to reset
+-- reset the slot stats
 SELECT pg_stat_reset_replication_slot('regression_slot_stats');
-SELECT wait_for_decode_stats(true, false);
 SELECT slot_name, spill_txns, spill_count, total_txns, total_bytes FROM pg_stat_replication_slots;
 
 -- spilling the xact
@@ -69,10 +23,10 @@ INSERT INTO stats_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1
 COMMIT;
 SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot_stats', NULL, NULL, 'skip-empty-xacts', '1');
 
--- Check stats, wait for the stats collector to update. We can't test the
--- exact stats count as that can vary if any background transaction (say by
--- autovacuum) happens in parallel to the main transaction.
-SELECT wait_for_decode_stats(false, true);
+-- Check stats. We can't test the exact stats count as that can vary if any
+-- background transaction (say by autovacuum) happens in parallel to the main
+-- transaction.
+SELECT pg_stat_force_next_flush();
 SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots;
 
 -- Ensure stats can be repeatedly accessed using the same stats snapshot. See
@@ -82,6 +36,5 @@ SELECT slot_name FROM pg_stat_replication_slots;
 SELECT slot_name FROM pg_stat_replication_slots;
 COMMIT;
 
-DROP FUNCTION wait_for_decode_stats(bool, bool);
 DROP TABLE stats_test;
 SELECT pg_drop_replication_slot('regression_slot_stats');
index db30c72073db25286c9d22e7c1d79d0e55e73279..111869b2115cdbe352f43b5d72e1ecd76956d130 100644 (file)
@@ -219,6 +219,12 @@ static MemoryContext pgStatPendingContext = NULL;
 static dlist_head pgStatPending = DLIST_STATIC_INIT(pgStatPending);
 
 
+/*
+ * Force the next stats flush to happen regardless of
+ * PGSTAT_MIN_INTERVAL. Useful in test scripts.
+ */
+static bool pgStatForceNextFlush = false;
+
 /*
  * For assertions that check pgstat is not used before initialization / after
  * shutdown.
@@ -560,6 +566,13 @@ pgstat_report_stat(bool force)
    pgstat_assert_is_up();
    Assert(!IsTransactionBlock());
 
+   /* "absorb" the forced flush even if there's nothing to flush */
+   if (pgStatForceNextFlush)
+   {
+       force = true;
+       pgStatForceNextFlush = false;
+   }
+
    /* Don't expend a clock check if nothing to do */
    if (dlist_is_empty(&pgStatPending) &&
        !have_slrustats &&
@@ -637,6 +650,16 @@ pgstat_report_stat(bool force)
    return 0;
 }
 
+/*
+ * Force locally pending stats to be flushed during the next
+ * pgstat_report_stat() call. This is useful for writing tests.
+ */
+void
+pgstat_force_next_flush(void)
+{
+   pgStatForceNextFlush = true;
+}
+
 /*
  * Only for use by pgstat_reset_counters()
  */
index 248d318f866d0445d5510cbb33e268bfef7420c7..5f1815727de75032e63893d5968a77d7add63422 100644 (file)
@@ -2067,6 +2067,16 @@ pg_stat_clear_snapshot(PG_FUNCTION_ARGS)
 }
 
 
+/* Force statistics to be reported at the next occasion */
+Datum
+pg_stat_force_next_flush(PG_FUNCTION_ARGS)
+{
+   pgstat_force_next_flush();
+
+   PG_RETURN_VOID();
+}
+
+
 /* Reset all counters for the current database */
 Datum
 pg_stat_reset(PG_FUNCTION_ARGS)
index 0f0f41b2f92d3a08c991237e80d37ca63348b727..2b4438bc3d56bc27735c02a195a45943a7a2c108 100644 (file)
   proname => 'pg_stat_clear_snapshot', proisstrict => 'f', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => '',
   prosrc => 'pg_stat_clear_snapshot' },
+{ oid => '2137',
+  descr => 'statistics: force stats to be flushed after the next commit',
+  proname => 'pg_stat_force_next_flush', proisstrict => 'f', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => '',
+  prosrc => 'pg_stat_force_next_flush' },
 { oid => '2274',
   descr => 'statistics: reset collected statistics for current database',
   proname => 'pg_stat_reset', proisstrict => 'f', provolatile => 'v',
index 8ac3860e8d4333ae6fbe250df337fe79deac3b9e..a0b853f4a6901ce3fa9b55b478fd2ba2fd2be2fa 100644 (file)
@@ -414,6 +414,7 @@ extern void pgstat_initialize(void);
 
 /* Functions called from backends */
 extern long pgstat_report_stat(bool force);
+extern void pgstat_force_next_flush(void);
 
 extern void pgstat_reset_counters(void);
 extern void pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objectid);
index 2d03d8e134eb23fe76fa4f8a784b020131aa78bd..ed7879f58310a8832b3d7519a290a827828d00ad 100644 (file)
@@ -603,3 +603,25 @@ SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100;
      1
 (1 row)
 
+-- test BRIN index doesn't block HOT update
+CREATE TABLE brin_hot (
+        id  integer PRIMARY KEY,
+        val integer NOT NULL
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
+CREATE INDEX val_brin ON brin_hot using brin(val);
+UPDATE brin_hot SET val = -3 WHERE id = 42;
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
+ pg_stat_get_tuples_hot_updated 
+--------------------------------
+                              1
+(1 row)
+
+DROP TABLE brin_hot;
index 64e2ff6b29666dd8a1ca5df5e7ded787ede35739..aa05b3b78af40ee6f0fd9cf2ff5ff13bacc9abca 100644 (file)
@@ -28,63 +28,6 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
        pg_catalog.pg_statio_user_tables AS b
  WHERE t.relname='tenk2' AND b.relname='tenk2';
 COMMIT;
--- function to wait for counters to advance
-create function wait_for_stats() returns void as $$
-declare
-  start_time timestamptz := clock_timestamp();
-  updated1 bool;
-  updated2 bool;
-  updated3 bool;
-  updated4 bool;
-begin
-  SET LOCAL stats_fetch_consistency = snapshot;
-
-  -- We don't want to wait forever.  No timeout suffices if the OS drops our
-  -- stats traffic because an earlier test file left a full UDP buffer.
-  -- Hence, don't use PG_TEST_TIMEOUT_DEFAULT, which may be large for
-  -- can't-happen timeouts.  Exit after 30 seconds.
-  for i in 1 .. 300 loop
-
-    -- With parallel query, the seqscan and indexscan on tenk2 might be done
-    -- in parallel worker processes, which will send their stats counters
-    -- asynchronously to what our own session does.  So we must check for
-    -- those counts to be registered separately from the update counts.
-
-    -- check to see if seqscan has been sensed
-    SELECT (st.seq_scan >= pr.seq_scan + 1) INTO updated1
-      FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
-     WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
-    -- check to see if indexscan has been sensed
-    SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated2
-      FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
-     WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
-    -- check to see if all updates have been sensed
-    SELECT (n_tup_ins > 0) INTO updated3
-      FROM pg_stat_user_tables WHERE relname='trunc_stats_test4';
-
-    -- We must also check explicitly that pg_stat_get_snapshot_timestamp has
-    -- advanced, because that comes from the global stats file which might
-    -- be older than the per-DB stats file we got the other values from.
-    SELECT (pr.snap_ts < pg_stat_get_snapshot_timestamp()) INTO updated4
-      FROM prevstats AS pr;
-
-    exit when updated1 and updated2 and updated3 and updated4;
-
-    -- wait a little
-    perform pg_sleep_for('100 milliseconds');
-
-    -- reset stats snapshot so we can test again
-    perform pg_stat_clear_snapshot();
-
-  end loop;
-
-  -- report time waited in postmaster log (where it won't change test output)
-  raise log 'wait_for_stats delayed % seconds',
-    extract(epoch from clock_timestamp() - start_time);
-end
-$$ language plpgsql;
 -- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
 CREATE TABLE trunc_stats_test(id serial);
 CREATE TABLE trunc_stats_test1(id serial, stuff text);
@@ -153,17 +96,10 @@ SELECT count(*) FROM tenk2 WHERE unique1 = 1;
 (1 row)
 
 RESET enable_bitmapscan;
--- We can't just call wait_for_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat().  But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session.  The old one will
--- then send its stats before dying.
-\c -
--- wait for stats collector to update
-SELECT wait_for_stats();
- wait_for_stats 
-----------------
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
  
 (1 row)
 
@@ -212,57 +148,6 @@ FROM prevstats AS pr;
 COMMIT;
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
--- test BRIN index doesn't block HOT update - we include this test here, as it
--- relies on statistics collector and so it may occasionally fail, especially
--- on slower systems
-CREATE TABLE brin_hot (
-        id  integer PRIMARY KEY,
-        val integer NOT NULL
-) WITH (autovacuum_enabled = off, fillfactor = 70);
-INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
-CREATE INDEX val_brin ON brin_hot using brin(val);
-CREATE FUNCTION wait_for_hot_stats() RETURNS void AS $$
-DECLARE
-        start_time timestamptz := clock_timestamp();
-        updated bool;
-BEGIN
-        -- we don't want to wait forever; loop will exit after 30 seconds
-        FOR i IN 1 .. 300 LOOP
-                SELECT (pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
-                EXIT WHEN updated;
-
-                -- wait a little
-                PERFORM pg_sleep_for('100 milliseconds');
-                -- reset stats snapshot so we can test again
-                PERFORM pg_stat_clear_snapshot();
-        END LOOP;
-        -- report time waited in postmaster log (where it won't change test output)
-        RAISE log 'wait_for_hot_stats delayed % seconds',
-          EXTRACT(epoch FROM clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
-UPDATE brin_hot SET val = -3 WHERE id = 42;
--- We can't just call wait_for_hot_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat().  But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session.  The old one will
--- then send its stats before dying.
-\c -
-SELECT wait_for_hot_stats();
- wait_for_hot_stats 
---------------------
-(1 row)
-
-SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
- pg_stat_get_tuples_hot_updated 
---------------------------------
-                              1
-(1 row)
-
-DROP TABLE brin_hot;
-DROP FUNCTION wait_for_hot_stats();
 -- ensure that stats accessors handle NULL input correctly
 SELECT pg_stat_get_replication_slot(NULL);
  pg_stat_get_replication_slot 
index 5030d19c03ef0f3376be4273519b3fed49bca4f9..1087b2c14f4ec42cb37d8a2d16926a50b3494ccc 100644 (file)
@@ -124,7 +124,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression memoize
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression memoize stats
 
 # event_trigger cannot run concurrently with any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
@@ -132,6 +132,3 @@ test: event_trigger oidjoins
 
 # this test also uses event triggers, so likewise run it by itself
 test: fast_default
-
-# run stats by itself because its delay may be insufficient under heavy load
-test: stats
index e12f3a0df98d987df13d70ec5ef7d6c2a01680c6..920e05324944148906627790e132fbc3dceaabcc 100644 (file)
@@ -526,3 +526,22 @@ SET enable_seqscan = off;
 
 EXPLAIN (COSTS OFF) SELECT * FROM brin_hot_2 WHERE a = 2 AND b = 100;
 SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100;
+
+
+-- test BRIN index doesn't block HOT update
+CREATE TABLE brin_hot (
+        id  integer PRIMARY KEY,
+        val integer NOT NULL
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
+CREATE INDEX val_brin ON brin_hot using brin(val);
+
+UPDATE brin_hot SET val = -3 WHERE id = 42;
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
+
+DROP TABLE brin_hot;
index 85a253bcd43230d92f4bf6a64abffe832020c92a..7c77c3312152a28e2642053ac0aadfbcb4701a7d 100644 (file)
@@ -27,64 +27,6 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
  WHERE t.relname='tenk2' AND b.relname='tenk2';
 COMMIT;
 
--- function to wait for counters to advance
-create function wait_for_stats() returns void as $$
-declare
-  start_time timestamptz := clock_timestamp();
-  updated1 bool;
-  updated2 bool;
-  updated3 bool;
-  updated4 bool;
-begin
-  SET LOCAL stats_fetch_consistency = snapshot;
-
-  -- We don't want to wait forever.  No timeout suffices if the OS drops our
-  -- stats traffic because an earlier test file left a full UDP buffer.
-  -- Hence, don't use PG_TEST_TIMEOUT_DEFAULT, which may be large for
-  -- can't-happen timeouts.  Exit after 30 seconds.
-  for i in 1 .. 300 loop
-
-    -- With parallel query, the seqscan and indexscan on tenk2 might be done
-    -- in parallel worker processes, which will send their stats counters
-    -- asynchronously to what our own session does.  So we must check for
-    -- those counts to be registered separately from the update counts.
-
-    -- check to see if seqscan has been sensed
-    SELECT (st.seq_scan >= pr.seq_scan + 1) INTO updated1
-      FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
-     WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
-    -- check to see if indexscan has been sensed
-    SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated2
-      FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
-     WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
-    -- check to see if all updates have been sensed
-    SELECT (n_tup_ins > 0) INTO updated3
-      FROM pg_stat_user_tables WHERE relname='trunc_stats_test4';
-
-    -- We must also check explicitly that pg_stat_get_snapshot_timestamp has
-    -- advanced, because that comes from the global stats file which might
-    -- be older than the per-DB stats file we got the other values from.
-    SELECT (pr.snap_ts < pg_stat_get_snapshot_timestamp()) INTO updated4
-      FROM prevstats AS pr;
-
-    exit when updated1 and updated2 and updated3 and updated4;
-
-    -- wait a little
-    perform pg_sleep_for('100 milliseconds');
-
-    -- reset stats snapshot so we can test again
-    perform pg_stat_clear_snapshot();
-
-  end loop;
-
-  -- report time waited in postmaster log (where it won't change test output)
-  raise log 'wait_for_stats delayed % seconds',
-    extract(epoch from clock_timestamp() - start_time);
-end
-$$ language plpgsql;
-
 -- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
 CREATE TABLE trunc_stats_test(id serial);
 CREATE TABLE trunc_stats_test1(id serial, stuff text);
@@ -151,16 +93,8 @@ SET enable_bitmapscan TO off;
 SELECT count(*) FROM tenk2 WHERE unique1 = 1;
 RESET enable_bitmapscan;
 
--- We can't just call wait_for_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat().  But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session.  The old one will
--- then send its stats before dying.
-\c -
-
--- wait for stats collector to update
-SELECT wait_for_stats();
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
 
 -- check effects
 BEGIN;
@@ -190,55 +124,6 @@ COMMIT;
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
 
--- test BRIN index doesn't block HOT update - we include this test here, as it
--- relies on statistics collector and so it may occasionally fail, especially
--- on slower systems
-CREATE TABLE brin_hot (
-        id  integer PRIMARY KEY,
-        val integer NOT NULL
-) WITH (autovacuum_enabled = off, fillfactor = 70);
-
-INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
-CREATE INDEX val_brin ON brin_hot using brin(val);
-
-CREATE FUNCTION wait_for_hot_stats() RETURNS void AS $$
-DECLARE
-        start_time timestamptz := clock_timestamp();
-        updated bool;
-BEGIN
-        -- we don't want to wait forever; loop will exit after 30 seconds
-        FOR i IN 1 .. 300 LOOP
-                SELECT (pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
-                EXIT WHEN updated;
-
-                -- wait a little
-                PERFORM pg_sleep_for('100 milliseconds');
-                -- reset stats snapshot so we can test again
-                PERFORM pg_stat_clear_snapshot();
-        END LOOP;
-        -- report time waited in postmaster log (where it won't change test output)
-        RAISE log 'wait_for_hot_stats delayed % seconds',
-          EXTRACT(epoch FROM clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
-
-UPDATE brin_hot SET val = -3 WHERE id = 42;
-
--- We can't just call wait_for_hot_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat().  But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session.  The old one will
--- then send its stats before dying.
-\c -
-
-SELECT wait_for_hot_stats();
-SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
-
-DROP TABLE brin_hot;
-DROP FUNCTION wait_for_hot_stats();
-
-
 -- ensure that stats accessors handle NULL input correctly
 SELECT pg_stat_get_replication_slot(NULL);
 SELECT pg_stat_get_subscription_stats(NULL);