Re: Atomic operations within spinlocks
Andres Freund <andres@anarazel.de>
From: Andres Freund <andres@anarazel.de>
To: Tom Lane <tgl@sss.pgh.pa.us>
Cc: pgsql-hackers@lists.postgresql.org
Date: 2020-06-05T03:03:10Z
Lists: pgsql-hackers
Hi,
On 2020-06-04 15:13:29 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2020-06-04 14:50:40 -0400, Tom Lane wrote:
> >> 2. The computed completePasses value would go backwards. I bet
> >> that wouldn't matter too much either, or at least we could teach
> >> BgBufferSync to cope. (I notice the comments therein suggest that
> >> it is already designed to cope with completePasses wrapping around,
> >> so maybe nothing needs to be done.)
>
> > If we're not concerned about that, then we can remove the
> > atomic-inside-spinlock, I think. The only reason for that right now is
> > to avoid assuming a wrong pass number.
>
> Hmm. That might be a less-invasive path to a solution. I can take
> a look, if you don't want to.
First, I think it would be problematic:
/*
* Find out where the freelist clock sweep currently is, and how many
* buffer allocations have happened since our last call.
*/
strategy_buf_id = StrategySyncStart(&strategy_passes, &recent_alloc);
...
/*
* Compute strategy_delta = how many buffers have been scanned by the
* clock sweep since last time. If first time through, assume none. Then
* see if we are still ahead of the clock sweep, and if so, how many
* buffers we could scan before we'd catch up with it and "lap" it. Note:
* weird-looking coding of xxx_passes comparisons are to avoid bogus
* behavior when the passes counts wrap around.
*/
if (saved_info_valid)
{
int32 passes_delta = strategy_passes - prev_strategy_passes;
strategy_delta = strategy_buf_id - prev_strategy_buf_id;
strategy_delta += (long) passes_delta * NBuffers;
Assert(strategy_delta >= 0);
ISTM that if we can get an out-of-sync strategy_passes and
strategy_buf_id we'll end up with a pretty wrong strategy_delta. Which,
I think, can cause to reset bgwriter's position:
else
{
/*
* We're behind, so skip forward to the strategy point and start
* cleaning from there.
*/
#ifdef BGW_DEBUG
elog(DEBUG2, "bgwriter behind: bgw %u-%u strategy %u-%u delta=%ld",
next_passes, next_to_clean,
strategy_passes, strategy_buf_id,
strategy_delta);
#endif
next_to_clean = strategy_buf_id;
next_passes = strategy_passes;
bufs_to_lap = NBuffers;
}
While I think that the whole logic in BgBufferSync doesn't make a whole
lot of sense, it does seem to me this has a fair potential to make it
worse. In a scenario with a decent cache hit ratio (leading to high
usagecounts) and a not that large NBuffers, we can end up up doing quite
a few passes (as in many a second), so it might not be that hard to hit
this.
I am not immediately coming up with a cheap solution that doesn't do the
atomics-within-spinlock thing. The best I can come up with is using a
64bit atomic, with the upper 32bit containing the number of passes, and
the lower 32bit containing the current buffer. Where the lower 32bit /
the buffer is handled like it currently is, i.e. we "try" to keep it
below NBuffers. So % is only used for the "cold" path. That'd just add a
64->32 bit cast in the hot path, which shouldn't be measurable. But it'd
regress platforms without 64bit atomics substantially.
We could obviously also just rewrite the BgBufferSync() logic, so it
doesn't care about things like "lapping", but that's not an easy change.
So the best I can really suggest, unless we were to agree on atomics
being ok inside spinlocks, is probably to just replace the spinlock with
an lwlock. That'd perhaps cause a small slowdown for a few cases, but
it'd make workload that e.g. use the freelist a lot (e.g. when tables
are dropped regularly) scale better.
Greetings,
Andres Freund
Commits
-
Convert SpinLock* macros to static inline functions.
- bfc321b4723e 19 (unreleased) landed
-
Clean up includes of s_lock.h.
- f219167910ad 14.0 landed
-
Fix deadlock danger when atomic ops are done under spinlock.
- 5fffa8fce37b 13.0 landed
- 6cc2866c4cdf 12.4 landed
- 825d89dda5ad 11.9 landed
- c2a84bee1228 10.14 landed
- 8bc13287ed3e 9.6.19 landed
- 3e69bf3b142b 9.5.23 landed
- cf1234a10e50 14.0 landed
-
Add basic spinlock tests to regression tests.
- 3f66e1c5a576 10.14 landed
- e027219f2131 11.9 landed
- 008c119928a3 12.4 landed
- 59225dcefef2 13.0 landed
- 3b37a6de027c 14.0 landed
- e8302f107af9 9.6.19 landed
- 7e91f90a8ed4 9.5.23 landed
-
spinlock emulation: Fix bug when more than INT_MAX spinlocks are initialized.
- dcf3cb60cbef 9.5.23 landed
- 660622053419 9.6.19 landed
- b295f666b7f0 10.14 landed
- 34d29222a0bb 11.9 landed
- b91cfaa34f44 12.4 landed
- 276bdc93924a 13.0 landed
- 4d4ca24efe8e 14.0 landed
-
Avoid potential spinlock in a signal handler as part of global barriers.
- 09bff91b316e 13.0 landed
- fd49d5380757 14.0 landed
-
Make pg_stat_wal_receiver consistent with the WAL receiver's shmem info
- 2c8dd05d6cbc 13.0 cited
-
Improve 64bit atomics support.
- e8fdbd58fe56 10.0 cited