PostgreSQL 17.10 on Azure Database for PostgreSQL Flexible Server. A large, high-write OLTP table with more than a dozen indexes, all plain btree, no GIN or other index types. Rows are inserted once, then updated repeatedly as they move through a state machine.
The dominant statement on this table:
UPDATE t
SET c1=$1, c2=$2,
status_col=$3, -- indexed
counter_col=$4, -- indexed
payload_col=$5 -- text, TOASTable
WHERE t.id=$6;
WHERE is an equality match on the primary key. Because two of the SET columns are indexed, this can never be a HOT update, every execution has to touch every index on the table, not just the heap.
Every so often, for roughly a minute, execution time for these UPDATEs rises significantly and then recovers on its own, with no matching spike in throughput. Query Store shows the direct cause: overall buffers touched per call (shared_blks_hit + shared_blks_read) increases, and most of that increase is in reads.
In the same window, primary-key lookups on this table is not affected, and the query plan itself doesn't change. No triggers fire for this statement. So the problem looks specific to this UPDATE's execution path, not to storage, WAL, or a load spike. Wait events during the episode are dominated by LWLock:BufferContent and IO:DataFileRead.
The only correlation I've found so far, not confident in it, could be coincidence, is that these episodes tend to start shortly after an autovacuum on the table finishes.
What can make the same UPDATE, with the same plan, temporarily need substantially more shared buffers, in a way that self-resolves after about a minute? My working guess is free-space-map churn right after vacuum releases space, but I'd like to know what else in the UPDATE path could cause this.