Re: race condition in pg_class
Noah Misch <noah@leadboat.com>
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Replace tests of ALTER DATABASE RESET TABLESPACE.
- 4adb0380b9bf 19 (unreleased) landed
-
meson: Flush stdout in testwrap
- d9bc60705274 16.9 landed
- 9de86e367a67 17.5 landed
- 5941946d0934 18.0 landed
-
Fix catcache invalidation of a list entry that's being built
- af8cd1639ab2 18.0 cited
-
Stop reading uninitialized memory in heap_inplace_lock().
- a0c0078b1355 12.21 landed
- ad24b7565910 14.14 landed
- 3e5ea478d875 13.17 landed
- d34ffbaa1029 15.9 landed
- 2d63c964f010 16.5 landed
- e11907682846 17.1 landed
- e947224cbbce 18.0 landed
-
Fix use of uninitialized value in previous commit.
- 0cf3d41cbb42 15.9 landed
- 90f5412a9a51 16.5 landed
-
Back-patch "Refactor code in tablecmds.c to check and process tablespace moves"
- dc845383cd12 12.21 landed
- 1299564cb9c7 13.17 landed
-
Fix data loss at inplace update after heap_update().
- 7354b680ab64 12.21 landed
- a8ad1929d2ec 13.17 landed
- 8590c942c1a6 15.9 landed
- 82c2d9e02206 14.14 landed
- 63f01980560a 16.5 landed
- fd27b878c2ea 17.1 landed
- a07e03fd8fa7 18.0 landed
-
For inplace update durability, make heap_update() callers wait.
- cafcc3ad0ed3 12.21 landed
- 14c57cb63907 13.17 landed
- f51b34b3eddb 14.14 landed
- 5c837f8fa022 15.9 landed
- 51ff46de29f6 16.5 landed
- 3b7a689e1a80 17.1 landed
- aac2c9b4fde8 18.0 landed
-
Warn if LOCKTAG_TUPLE is held at commit, under debug_assertions.
- b779d37a3db2 12.21 landed
- 5ef9b4a2f796 13.17 landed
- c9de1b8ac8df 14.14 landed
- 41e0ba33d5ab 15.9 landed
- acc63ef4fbe6 16.5 landed
- f33bf1c7d501 17.1 landed
- dbf3f974ee04 18.0 landed
-
Don't lose partitioned table reltuples=0 after relhassubclass=f.
- e81deeefcac2 16.4 landed
- 2b4a2a79edd2 15.8 landed
- 2b415e95a8a7 14.13 landed
- f5bb46fb2e65 17.0 landed
- 7102070329d8 18.0 landed
-
Fix new assertion for MERGE view_name ... DO NOTHING.
- 0d3b35c367c2 17.0 landed
- d5e6891502ca 18.0 landed
-
Remove configuration-dependent output from new inplace-inval test.
- 79567599cd21 12.20 landed
- f31c1e34bc80 14.13 landed
- ca30e8ba958a 13.16 landed
- 7857c8dc4481 16.4 landed
- 458fada72b32 15.8 landed
- b99414de9052 17.0 landed
-
AccessExclusiveLock new relations just after assigning the OID.
- 7af775bbae57 12.20 landed
- e038b7756df8 14.13 landed
- b899cde0b86a 13.16 landed
- 0f2835ea6fc1 15.8 landed
- fc8c25806e84 16.4 landed
- 5b823b179e5e 17.0 landed
-
Cope with inplace update making catcache stale during TOAST fetch.
- 7a21306aee0a 13.16 landed
- 11f3815d6af8 12.20 landed
- af73e37fa181 14.13 landed
- b08a4b6163eb 15.8 landed
- e4afd7153bd8 16.4 landed
- f9f47f0d93d1 17.0 landed
-
Expand comments and add an assertion in nodeModifyTable.c.
- fea47de3016a 12.20 landed
- c3437fb7255b 13.16 landed
- ad4653268c12 14.13 landed
- f0544432c3cc 15.8 landed
- f8135d8b54ff 16.4 landed
- d5f788b41dc2 17.0 landed
-
Improve test coverage for changes to inplace-updated catalogs.
- 796d191028bb 12.20 landed
- 65aae93c456a 13.16 landed
- 2d06bdbc0cc3 14.13 landed
- 3c6becd3d180 15.8 landed
- dd8008e8ec95 16.4 landed
- 0844b3968985 17.0 landed
-
Lock before setting relhassubclass on RELKIND_PARTITIONED_INDEX.
- e2eefb97e31a 12.20 landed
- 1a6d65b64e84 13.16 landed
- 578db9c9257d 14.13 landed
- a338e41374e4 15.8 landed
- 480b58fabd35 16.4 landed
- 0cecc908e974 17.0 landed
-
Lock owned sequences during ALTER TABLE SET { LOGGED | UNLOGGED }.
- 24561b498f11 15.8 landed
- 112d055706bf 16.4 landed
- f88cdb36c457 17.0 landed
-
Make TAP todo_start effects the same under Meson and prove_check.
- 288426902ee5 16.4 landed
- 22a4b104ba70 17.0 landed
-
Add an injection_points isolation test suite.
- c35f419d6efb 17.0 landed
-
Add wait event type "InjectionPoint", a custom type like "Extension".
- bb93640a681c 17.0 landed
-
Create waitfuncs.c for pg_isolation_test_session_is_blocked().
- abfbd13af0e9 17.0 landed
-
Rework planning and execution of UPDATE and DELETE.
- 86dc90056dfd 14.0 cited
Attachments
- intra-grant-inplace-via-ctid-v0.patch (text/plain) patch v0
- intra-grant-inplace-via-tuplock-v0.patch (text/plain) patch v0
I prototyped two ways, one with a special t_ctid and one with LockTuple(). On Fri, Oct 27, 2023 at 04:26:12PM -0700, Noah Misch wrote: > On Fri, Oct 27, 2023 at 06:40:55PM -0400, Tom Lane wrote: > > Noah Misch <noah@leadboat.com> writes: > > > On Fri, Oct 27, 2023 at 03:32:26PM -0400, Tom Lane wrote: > > > I anticipate a new locktag per catalog that can receive inplace updates, > > > i.e. LOCKTAG_RELATION_DEFINITION and LOCKTAG_DATABASE_DEFINITION. > > > > We could perhaps make this work by using the existing tuple-lock > > infrastructure, rather than inventing new locktags (a choice that > > spills to a lot of places including clients that examine pg_locks). > > That could be okay. It would be weird to reuse a short-term lock like that > one as something held till end of transaction. But the alternative of new > locktags ain't perfect, as you say. That worked. > > I would prefer though to find a solution that only depends on making > > heap_inplace_update protect itself, without high-level cooperation > > from the possibly-interfering updater. This is basically because > > I'm still afraid that we're defining the problem too narrowly. > > For one thing, I have nearly zero confidence that GRANT et al are > > the only problematic source of conflicting transactional updates. > > Likewise here, but I have fair confidence that an assertion would flush out > the rest. heap_inplace_update() would assert that the backend holds one of > the acceptable locks. It could even be an elog; heap_inplace_update() can > tolerate that cost. That check would fall in both heap_inplace_update() and heap_update(). After all, a heap_inplace_update() check won't detect an omission in GRANT. > > For another, I'm worried that some extension may be using > > heap_inplace_update against a catalog we're not considering here. > > A pgxn search finds "citus" using heap_inplace_update(). > > > I'd also like to find a solution that fixes the case of a conflicting > > manual UPDATE (although certainly that's a stretch goal we may not be > > able to reach). > > It would be nice. I expect most approaches could get there by having ExecModifyTable() arrange for the expected locking or other actions. That's analogous to how heap_update() takes care of sinval even for a manual UPDATE. > > I wonder if there's a way for heap_inplace_update to mark the tuple > > header as just-updated in a way that regular heap_update could > > recognize. (For standard catalog updates, we'd then end up erroring > > in simple_heap_update, which I think is fine.) We can't update xmin, > > because the VACUUM callers don't have an XID; but maybe there's some > > other way? I'm speculating about putting a funny value into xmax, > > or something like that, and having heap_update check that what it > > sees in xmax matches what was in the tuple the update started with. > > Hmmm. Achieving it without an XID would be the real trick. (With an XID, we > could use xl_heap_lock like heap_update() does.) Thinking out loud, what if > heap_inplace_update() sets HEAP_XMAX_INVALID and xmax = > TransactionIdAdvance(xmax)? Or change t_ctid in a similar way. Then regular > heap_update() could complain if the field changed vs. last seen value. This > feels like something to regret later in terms of limiting our ability to > harness those fields for more-valuable ends or compact them away in a future > page format. I can't pinpoint a specific loss, so the idea might have legs. > Nontransactional data in separate tables or in new metapages smells like the > right long-term state. A project wanting to reuse the tuple header bits could > introduce such storage to unblock its own bit reuse. heap_update() does not have the pre-modification xmax today, so I used t_ctid. heap_modify_tuple() preserves t_ctid, so heap_update() already has the pre-modification t_ctid in key cases. For details of how the prototype uses t_ctid, see comment at "#define InplaceCanaryOffsetNumber". The prototype doesn't prevent corruption in the following scenario, because the aborted ALTER TABLE RENAME overwrites the special t_ctid: == session 1 drop table t; create table t (c int); begin; -- in gdb, set breakpoint on heap_modify_tuple grant select on t to public; == session 2 alter table t add primary key (c); begin; alter table t rename to t2; rollback; == back in session 1 -- release breakpoint -- want error (would get it w/o the begin;alter;rollback) commit; I'm missing how to mark the tuple in a fashion accessible to a second heap_update() after a rolled-back heap_update(). The mark needs enough bits "N" so it's implausible for 2^N inplace updates to happen between GRANT fetching the old tuple and GRANT completing heap_update(). Looking for bits that could persist across a rolled-back heap_update(), we have 3 in t_ctid, 2 in t_infomask2, and 0 in xmax. I definitely don't want to paint us into a corner by spending the t_infomask2 bits on this. Even if I did, 2^(3+2)=32 wouldn't clearly be enough inplace updates. Is there a way to salvage the goal of fixing the bug without modifying code like ExecGrant_common()? If not, I'm inclined to pick from one of the following designs: - Acquire ShareUpdateExclusiveLock in GRANT ((B1) from previous list). It does make GRANT more intrusive; e.g. GRANT will cancel autovacuum. I'm leaning toward this one for two reasons. First, it doesn't slow heap_update() outside of assert builds. Second, it makes the GRANT experience more like the rest our DDL, in that concurrent DDL will make GRANT block, not fail. - GRANT passes to heapam the fixed-size portion of the pre-modification tuple. heap_update() compares those bytes to the oldtup in shared buffers to see if an inplace update happened. (HEAD could get the bytes from a new heap_update() parameter, while back branches would need a different passing approach.) - LockTuple(), as seen in its attached prototype. I like this least at the moment, because it changes pg_locks content without having a clear advantage over the previous option. Also, the prototype has enough FIXME markers that I expect this to get hairy before it's done. I might change my preference after further prototypes. Does anyone have a strong preference between those? Briefly, I did consider these additional alternatives: - Just accept the yet-rarer chance of corruption from this message's test procedure. - Hold a buffer lock long enough to solve things. - Remember the tuples where we overwrote a special t_ctid, and reverse the overwrite during abort processing. But I/O in the abort path sounds bad. Thanks, nm