Re: pgstat: Flush some statistics within running transactions, take 2

From: Bertrand Drouvot <bertranddrouvot(dot)pg(at)gmail(dot)com>
To: Sami Imseih <samimseih(at)gmail(dot)com>
Cc: pgsql-hackers <pgsql-hackers(at)postgresql(dot)org>, Kyotaro Horiguchi <horikyota(dot)ntt(at)gmail(dot)com>, Michael Paquier <michael(at)paquier(dot)xyz>, Lukas Fittl <lukas(at)fittl(dot)com>
Subject: Re: pgstat: Flush some statistics within running transactions, take 2
Date: 2026-08-06 12:47:12
Message-ID: anSCUOyzesTT/qwR@bdtpg
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On Wed, Aug 05, 2026 at 08:22:55PM -0500, Sami Imseih wrote:
> I also added an ample amount of test coverage in stats.sql and also testing
> the cascading cases in test_custom_var_stats.

I'm not sure this test exercises the need for more than one extra pass.

It creates the cascade entries in the order B, C, A. So, during the first
extra pass, B is processed before C and C can be flushed during that same
pass.

Also, B and C are checked only after COMMIT, so the transaction end flush
could hide a failure of the in transaction rescan.

What about creating the entries in the order C, B, A and checking B and C
before COMMIT?

> I do create 2 new kinds to
> test the cascade, which means we need to reserve 2 new Kind IDs.
> If that is a problem, we can leave these tests out perhaps?
>
> ```
> +/*
> + * Kind IDs for cascade flush test (A -> B -> C).
> + * Tests that pgStatPendingFlushExtra handles multi-level dependencies.
> + */
> +#define PGSTAT_KIND_CASCADE_B 27
> +#define PGSTAT_KIND_CASCADE_C 28
> ```

Do we need separate kinds? Could B and C be represented as two objects belonging
to one custom kind?

A few more comments:

=== 1

pgstat_relation_flush_cb() now does:

+ tabentry->dead_tuples += lstats->counts.delta_dead_tuples - lstats->flushed.delta_dead_tuples;

and then records the cumulative value as flushed:

+ /*
+ * Record what was flushed. Transactional counters are retained until the
+ * transaction boundary.
+ */
+ if (flush_txn)
+ {
+ lstats->flushed = lstats->counts;

But pgstat_report_analyze() still subtracts the whole counts.delta_dead_tuples
value, so it can remove dead tuple statistics that were already published.

I think that pgstat_report_analyze() should subtract only the unflushed delta,
means:

deadtuples -= rel->pgstat_info->counts.delta_dead_tuples - rel->pgstat_info->flushed.delta_dead_tuples;

=== 2

The truncate handling resets the flushed.changed_tuples value:

+if (lstats->counts.truncdropped && !lstats->flushed.truncdropped)
+{
+ tabentry->live_tuples = 0;
+ tabentry->dead_tuples = 0;
+ tabentry->ins_since_vacuum = 0;
+ lstats->flushed.delta_live_tuples = 0;
+ lstats->flushed.delta_dead_tuples = 0;
+ lstats->flushed.changed_tuples = 0;
+}

However, changed_tuples is cumulative and truncate does not reset the shared
mod_since_analyze counter. Resetting flushed.changed_tuples to zero can
therefore publish changes that were already published.

I don't think flushed.changed_tuples should be reset here.

=== 3

The relation statistics reset is prevented by:

+if (lstats->counts.truncdropped && !lstats->flushed.truncdropped)

After the first full flush, this copies counts into flushed:

+ if (flush_txn)
+ {
+ lstats->flushed = lstats->counts;
+ return PGSTAT_FLUSH_DONE;
+ }

Both truncdropped values are then true. A subsequent truncate leaves
counts.truncdropped true, so the condition does not become true again and the
statistics reset is skipped.

What about doing this instead?

"
if (flush_txn)
{
lstats->flushed = lstats->counts;

lstats->counts.truncdropped = false;
lstats->flushed.truncdropped = false;

return PGSTAT_FLUSH_DONE;
}
"

=== 4

+ if (result == PGSTAT_FLUSH_DONE && xact_boundary)
pgstat_delete_pending_entry(entry_ref);
else
have_pending = true;

that means that now due to the extra loop:

+ /*
+ * Second scan (see above) for dependent entries populated after they were
+ * already visited.
+ */
+ while (pgStatPendingFlushExtra && IsTransactionOrTransactionBlock())
+ {

it invokes every retained callback again. Because pending->count is not cleared,
the same value is published twice. I think that can be an issue for custom
stats: the new test clears pending values:

+ memset(pending_entry, 0, sizeof(*pending_entry));

but that would be a new requirement for all custom callbacks.

At minimum that should be documented but I think a cleaner fix would be to
separate pending lifetime from queue membership, so that PGSTAT_FLUSH_DONE removes
an entry from the work queue while retaining its storage.

=== 5

In the extra scan, next is set before the callback:

+ next = dlist_has_next(&pgStatPending, cur) ?
+ dlist_next_node(&pgStatPending, cur) : NULL;
+
+ kind_info->flush_pending_cb(entry_ref, nowait, xact_boundary);

so, if the current tail callback creates a new dependent entry then it is processed
only if the callback also sets pgStatPendingFlushExtra, although the flag is
documented for updating an entry already visited. I think that next should be
set after the callback call (like the first scan).

=== 6

+ /*
+ * Second scan (see above) for dependent entries populated after they were
+ * already visited.
+ */
+ while (pgStatPendingFlushExtra && IsTransactionOrTransactionBlock())
+ {

What if:

A flushes into B and requests another pass
B flushes into A and requests another pass

Wouldn't that loop forever? That's not the case for core stats, but a custom
stats callback could create such a loop.

I'm not sure we can do much with the current global flag except document that
the dependencies must be acyclic.

Also, does that loop need CFI?

=== 7

+extern bool pgStatPendingFlushExtra;

missing PGDLLIMPORT?

=== 8

- proparallel => 'r', prorettype => 'void', proargtypes => '',
+ proparallel => 'u', prorettype => 'void', proargtypes => '',

I think that would need a bump catalog version, add a XXX in the commit message
to not forget about it?

also "descr => 'statistics: force stats to be flushed after the next commit',"
should be updated?

=== 9

Should we also add test to verify that the function double counting bug is solved?

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Hannu Krosing 2026-08-06 12:56:47 Re: Patch: dumping tables data in multiple chunks in pg_dump
Previous Message Ayush Tiwari 2026-08-06 12:40:13 Error handling in after-startup shmem requests