Re: Inval reliability, especially for inplace updates
Paul A Jungwirth <pj@illuminatedcomputing.com>
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Update .abi-compliance-history for PrepareToInvalidateCacheTuple().
- d88f4535d29f 15.16 landed
- a3e7bbd410d4 17.8 landed
- 2e58802f7332 14.21 landed
- 2655d2e47803 16.12 landed
-
Assert lack of hazardous buffer locks before possible catalog read.
- 0dfbd191a939 14.21 landed
- 86091202a8b1 15.16 landed
- 27e4fad9804c 16.12 landed
- bcb784e7d2f9 17.8 landed
- f4ece891fc2f 18.0 landed
-
For inplace update, send nontransactional invalidations.
- 811422471940 14.21 landed
- 1d7b02711f70 16.12 landed
- 05d605b6c69f 15.16 landed
- 0f69beddea11 17.8 landed
- 4cf948cbeedf 12.21 landed
- 0ea9d40a6679 13.17 landed
- e3914bd136e4 14.14 landed
- 4eac5a1fa78e 15.9 landed
- ce8c571d014e 16.5 landed
- 95c5acb3fc26 17.1 landed
- 243e9b40f1b2 18.0 landed
-
Update .abi-compliance-history for CacheInvalidateHeapTupleInplace().
- 06b030e8973f 18.2 landed
-
Revisit cosmetics of "For inplace update, send nontransactional invalidations."
- bae8ca82fd00 18.2 landed
- 64bf53dd61ea 19 (unreleased) landed
-
Correct comments of "Fix data loss at inplace update after heap_update()".
- 0839fbe400d7 19 (unreleased) landed
-
Move I/O before the index_update_stats() buffer lock region.
- d729f1ea5a46 12.21 landed
- 6b01cac0be6d 13.17 landed
- bb3054297661 14.14 landed
- 6d5b4031b927 15.9 landed
- 6c837c237bf8 16.5 landed
- 0bcb9d07940c 17.1 landed
- b412f402d1e0 18.0 landed
-
Revert "For inplace update, send nontransactional invalidations."
- 4b0f7d6c162e 12.21 landed
- fe8091c9e39e 13.17 landed
- 4c7088729503 14.14 landed
- 27642d89081e 15.9 landed
- 6f9dd2282e37 16.5 landed
- c1099dd745b0 17.1 landed
-
Revert "WAL-log inplace update before revealing it to other sessions."
- 5e503e10d13e 12.21 landed
- be74b943c920 13.17 landed
- 9a1c73636d60 14.14 landed
- e50f9de98d63 15.9 landed
- d5be10758b36 16.5 landed
- bc6bad885725 17.1 landed
-
Fix inplace update buffer self-deadlock.
- 0bada39c83a1 18.0 landed
-
Remove duplicate words in comments
- fb7e27abfbd4 18.0 cited
-
At end of recovery, reset all sinval-managed caches.
- da9950456463 12.21 landed
- 67f30c79a1c1 13.17 landed
- dca68242a81b 14.14 landed
- 3baf804b7295 15.9 landed
- d36b4d8ec322 16.5 landed
- a4668c99f0f8 17.1 landed
-
Fix data loss at inplace update after heap_update().
- a07e03fd8fa7 18.0 cited
-
Remove comment about xl_heap_inplace "AT END OF STRUCT".
- c63ceb2f0006 13.16 landed
- 77d0bc8001e6 12.20 landed
- c7f10df36806 14.13 landed
- 2ca8ca4827af 15.8 landed
- e352ba7b7509 16.4 landed
- 4a7f91b3d314 17.0 landed
-
Reduce memory consumption for pending invalidation messages.
- 3aafc030a536 15.0 cited
-
Add support for streaming to built-in logical replication.
- 464824323e57 14.0 cited
-
WAL Log invalidations at command end with wal_level=logical.
- c55040ccd017 14.0 cited
-
Introduce logical decoding.
- b89e151054a0 9.4.0 cited
-
Rename and document some invalidation routines to make it clearer that
- 81d08fcffeed 7.1.1 cited
Ian Ilyasov and I reviewed this patch. We think it is ready to commit
to back branches.
The attached patch applies to REL_17_STABLE but not other stable
branches, so we assume Noah will adjust it as needed.
We were able to reproduce Alexander Lakhin's hang when we tested
against 0a0a0f2c59 (i.e. before the previous version was reverted),
although adding the delay was necessary. With this patch applied, we
don't see the hang (even with the delay).
We agree that the new assertions are a good idea to prevent similar
errors in the future.
We couldn't devise other ways to break this patch. Surely more
experienced hackers could be more creative, but nonetheless it's
reassuring that the patch's twin has been in v18devel since November.
We assume you will also un-revert 8e7e672cda ("WAL-log inplace update
before revealing it to other sessions.")? We didn't look closely at
that patch, but it seems like there are no known problems with it. It
was just reverted because it depends on this patch.
Since this is a backpatch, it doesn't seem right to give non-essential
feedback. Here are a few thoughts anyway. Consider them notes for
future work rather than reasons to delay backpatching or drift from
the patch on master.
Is there any way to add more testing around non-transactional
invalidations? It is a new "feature" but it is not really tested
anywhere. I don't think we could do this with regress tests, but
perhaps isolation tests would be suitable.
Some of the comments felt a bit compressed. They make sense in the
context of this fix, but reading them cold seems like it will be
challenging. For example this took a lot of thinking to follow:
* Construct shared cache inval if necessary. Because we pass a
tuple
* version without our own inplace changes or inplace changes
other
* sessions complete while we wait for locks, inplace update
mustn't
* change catcache lookup keys. But we aren't bothering with
index
* updates either, so that's true a fortiori.
Or this:
* WAL contains likely-unnecessary commit-time invals from the
* CacheInvalidateHeapTuple() call in heap_inplace_update().
Why likely-unnecessary? I know you explain it at that callsite, but
some hint might help here.
It's a bit surprising that wrongly leaving relhasindex=t is safe (for
example after BEGIN; CREATE INDEX; ROLLBACK;). I guess this column is
just to save us a lookup for tables with no index, and no harm is done
if we do the lookup needlessly but find no indexes. And vacuum can
repair it later. Still it's a little unnerving.
On Thu, Oct 31, 2024 at 09:20:52PM -0700, Noah Misch wrote:
> Here, one of the autovacuum workers had the guilty stack trace, appearing at
> the end of this message. heap_inplace_update_and_unlock() calls
> CacheInvalidateHeapTupleInplace() while holding BUFFER_LOCK_EXCLUSIVE on a
> buffer of pg_class. CacheInvalidateHeapTupleInplace() may call
> CatalogCacheInitializeCache(), which opens the cache's rel. If there's not a
> valid relcache entry for the catcache's rel, we scan pg_class to make a valid
> relcache entry. The ensuing hang makes sense.
Personally I never expected that catcache could depend on relcache,
since it seems lower-level. But it makes sense that you need a
relcache of pg_class at least, so their relationship is more
complicated than just layers.
I'm struggling to understand how your explanation incorporates
*concurrency*, since a self-deadlock only involves locks from one
backend. But the point is that a concurrent invalidation causes the
relcache entry to vanish, so that we need to rebuild it. (We can't get
this far without having built the relcache for pg_class once already.)
Specifically, we drop the table while autovacuum is updating its
statistics. But how is that possible? Don't both those things
exclusive-lock the row in pg_class? I must be misunderstanding.
> Tomorrow, I'll think more about fixes. Two that might work:
>
> 1. Call CacheInvalidateHeapTupleInplace() before locking the buffer. Each
> time we need to re-find the tuple, discard the previous try's inplace
> invals and redo CacheInvalidateHeapTupleInplace(). That's because
> concurrent activity may have changed cache key fields like relname.
We agree that choice 1 is a good approach.
PrepareToInvalidateCacheTuple(Relation relation,
HeapTuple tuple,
HeapTuple newtuple,
- void (*function) (int, uint32, Oid))
+ void (*function) (int, uint32, Oid, void *),
+ void *context)
It's a little odd that PrepareToInvalidateCacheTuple takes a callback
function when it only has one caller, so it always calls
RegisterCatcacheInvalidation. Is it just to avoid adding dependencies
to inval.c? But it already #includes catcache.h and contains lots of
knowledge about catcache specifics. Maybe originally
PrepareToInvalidateCacheTuple was built to take
RegisterRelcacheInvalidation as well? Is it worth still passing the
callback?
@@ -6511,6 +6544,7 @@ heap_inplace_unlock(Relation relation,
{
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
UnlockTuple(relation, &oldtup->t_self, InplaceUpdateTupleLock);
+ ForgetInplace_Inval();
}
Is this the right place to add this? We think on balance yes, but the
question crossed my mind: Clearing the invals seems like a separate
responsibility from unlocking the buffer & tuple. After this patch,
our only remaining caller of heap_inplace_unlock is
systable_inplace_update_cancel, so perhaps it should call
ForgetInplace_Inval itself? OTOH we like that putting it here
guarantees it gets called, as a complement to building the invals in
heap_inplace_lock.
Yours,
--
Paul ~{:-)
pj@illuminatedcomputing.com