[MDEV-30103] FK constraint fails upon referenced table eviction Created: 2022-11-27 Updated: 2023-11-28 |
|
| Status: | Stalled |
| Project: | MariaDB Server |
| Component/s: | Storage Engine - InnoDB |
| Affects Version/s: | 10.6 |
| Fix Version/s: | 10.6, 10.11, 11.0 |
| Type: | Bug | Priority: | Major |
| Reporter: | Nikita Malyavin | Assignee: | Nikita Malyavin |
| Resolution: | Unresolved | Votes: | 0 |
| Labels: | None | ||
| Issue Links: |
|
||||||||||||||||||||||||||||
| Description |
|
With the following sync point added:
The following test fails to insert a row in a child table, however the opposite is expected:
The effect may be much more severe with real eviction, but I didn't succeed to simulate it with just innodb_evict_tables_on_commit_debug |
| Comments |
| Comment by Nikita Malyavin [ 2022-11-27 ] |
|
The test is constructed similarly to MDEV-30021, however the effect and the supposed fix are different. The difference between real eviction and innodb_evict_tables_on_commit_debug is that the latter also acquires lock_sys in addition to dict_sys. This problem is similar to the one fixed in There are a few ways of fixing it: 1. Add lock_sys acquisition to dict_sys_t::evict_table_LRU and adjust FK checksthis will follow n_ref_count is already protected by dict_sys lock. There can be other cases when lock_sys is required, but I'm not aware of them. Then, the referential constraint algorithm should be modified. Now lock_table (as of row_ins_check_foreign_constraint locking should be modified as following: Then the race with table eviction will be eliminated 2. Make correct MDL protection.What's worth to mention, the MDL prelocking itself is not suffitient in this regard. handler::open should also be And this is how this bug differs from MDEV-30021, by the way: even the correct prelocking could break there, as FLUSH Opening a stub for read-only references was an optimization in scope of user-reported Additionally, all referenced tables should be prelocked, in order to call ha_open and increase tables' reference counts. I think it's true that only query-affected tables could be prelocked, but it shouldn't affect the performance in a long run, Anyway, event if it could be in question, an optimization could be done: don't load frm and don't parse it for referential tables, but call ha_open. |
| Comment by Nikita Malyavin [ 2022-12-09 ] |
|
So far, I went approach 1 and I should describe why here.
At the same time, innodb_evict_tables_on_commit_debug is a debug-only option. Tables with FKs (both foreign and referenced) are never evicted on release. They could be dropped under TRUNCATE or DROP TABLE, but these operations are already improved by prelocking foreign tables, which can't happen during lock_release called when committing, which is doing the debug-only eviction. I don't want to make additional prelocking for DMLs in a minor version, since it will also increase the time taken to open tables. So, approach #1 is chosen. |
| Comment by Nikita Malyavin [ 2022-12-09 ] |
|
Please review commit 025cbbb3e, branch bb-10.6-MDEV-29181. The fix there supposes that table eviction doesn't remove the dict_foreign_t element during eviction, but only NULLs the related table and its related index, so it needs an MDEV-30021 fix, which does it. |
| Comment by Marko Mäkelä [ 2022-12-19 ] |
|
I think that the added function lock_fk_table() currently implements double-checked locking in an unsafe manner. If the underlying dict_table_t pointers were wrapped in Atomic_relaxed, then I think it should be safe. |
| Comment by Nikita Malyavin [ 2023-01-19 ] |
|
marko you are right about double-checked locking misuse. Link to the top commit: https://github.com/MariaDB/server/commit/14d7c451ce726fbd7d8bf2c5e051c6d4e95be76e |
| Comment by Marko Mäkelä [ 2023-01-26 ] |
|
I think that it would be simpler to rely on dict_sys.latch (and possibly MDL) for concurrency protection and use either regular or std::memory_order_relaxed loads and stores of dict_foreign_t::foreign_table and dict_foreign_t::referenced_table. It is hard to reason about release-acquire ordering for this complex data structures and interactions, and it does not help that we hardly run any stress tests outside x86-64, which implements Total Store Ordering. We could easily break things on ARM, POWER, RISC-V. In the added function lock_fk_table() we seem to need a relaxed load, to avoid acquiring dict_sys.latch in the optimistic case. If you change the pointers to atomic, please make sure to eliminate redundant loads, for example, in wsrep_append_foreign_key(). I wonder if it would be correct for lock_table() to validate the fktable pointer at the very end, after a lock on the table was successfully acquired. |