Store Foreign Key metadata outside of InnoDB
(MDEV-16417)
|
|
| Status: | Stalled |
| Project: | MariaDB Server |
| Component/s: | Storage Engine - InnoDB |
| Affects Version/s: | None |
| Fix Version/s: | 11.5 |
| Type: | Technical task | Priority: | Major |
| Reporter: | Aleksey Midenkov | Assignee: | Aleksey Midenkov |
| Resolution: | Unresolved | Votes: | 0 |
| Labels: | None | ||
| Issue Links: |
|
||||||||||||||||||||||||||||
| Description |
|
Refactor dict_load_foreigns() for synchronising TABLE_SHARE foreign data with dict_table_t cache. Remove a number of routines working with SYS_FOREIGNS and SYS_FOREIGN_COLS. innobase_update_foreign_try() is now used solely for ER_FK_INCORRECT_OPTION check. Prelock parent tables as well as child tables. This is done for the case when parent table doesn't know about its children when they created before parent with foreign_key_checks=0. Opening the parent table initiates fk_resolve_referenced_keys() which updates its referenced_keys. Due to CREATE TABLE doesn't not know about "illegal" children it can not check for foreign consistency. F.ex. this would succeed:
In the above case dict_load_foreigns() deduces which tables are unknown to opened parent (tables_missing) and reloads their foreign data via recursion. Infinite recursion is not possible via test case: a table cannot be "parent after child" and "child before parent" simultaneously. Though infinite recursion is possible via malicously crafted FRM file, there is no protection from that at InnoDB level but there is protection at SQL level: thd->fk_circular_check. Later though it would not allow DML on child as well as on parent (see innodb.foreign_key ha_innobase::open() then synchronizes these referenced_keys with its referenced_set cache by calling dict_load_foreigns(). Disable self-references on same column. The Bug 12902967 restricted them on some condition of "same column/index" (see innodb_bug12902967.test), though such self-references were not completely disabled (see other self-ref cases changed in this patch). It is not clear why they worked if they are "self-refs on same column/index". |
| Comments |
| Comment by Ralf Gebhardt [ 2020-02-14 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
midenok, this task will now be moved to 10.6 as it, and the task MDEV-16417, cannot be finished for 10.5 anymore. This has been agreed with Sergei Golubchik | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2020-08-07 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Please review bb-10.5-midenok-MDEV-16417 | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-08-12 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
The branch is several hundred commits behind the latest 10.5, and an attempt to merge will flag several conflicted files:
I will merge 10.5 to 10.6, so that you can rebase the work to an up-to-date 10.6. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2020-09-08 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Updated branch is bb-10.6-midenok-MDEV-16417 | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-12-21 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sorry, this review was stalled for a long time. I see that there will be conflicts with the latest 10.6. Please try to rebase this to an up-to-date version soon. I think that proper testing of this will be blocked by If serg agrees that the functional change of disallowing self-references is acceptable, I do not mind if we disallow that. But I will not authorize that change myself. There is an error in btr_node_ptr_max_size(): if (!comp && table_name_is_SYS_FOREIGN) was wrongly replaced with if (!comp). If we declared that on upgrade, all tables will lose their FOREIGN KEY constraints, then the correct course of action would have been to remove the entire if statement and its body. Because we intend to support ugprades (MDEV-21652), that statement must be retained in its entirety for now. Until all tables have been upgraded to the new FOREIGN KEY metadata storage, the old metadata tables SYS_FOREIGN and SYS_FOREIGN_COLS may be accessed, without reintroducing Loading the foreign key metadata should be controlled by the SQL layer and not the storage engine. Until now, the reason why InnoDB was loading the FOREIGN KEY metadata was that it was responsible for the persistent storage. Now that the metadata is managed by the SQL layer, the SQL layer should be responsible for loading it, instead of the storage engine. Yes, until MDEV-22361, InnoDB must cache the foreign key metadata. To facilitate that, I think that we will need a handler API call to register any discovered foreign key metadata with the storage engine. In MDEV-22361 that API would be removed, along with any FOREIGN KEY processing code in InnoDB. With this, I am saying that a replacement of the function dict_load_foreigns() must be introduced in the SQL layer, and it must be called from the SQL layer, not any storage engine. To avoid losing FOREIGN KEY metadata on upgrade, we will need a handlerton function to let InnoDB load metadata in case no metadata exists in the SQL layer and the .frm file is old enough to not determine whether foreign keys exist. Currently, the rewritten function dict_load_foreigns() is repeatedly calling current_thd, which is not acceptable. Why does it not take THD* as a parameter? The function is formatted in a style that follows neither the common server formatting rules nor the old InnoDB style that we should not use for any new code. Many lines exceed 79 characters. The function seems to blindly assume that tables are not partitioned. Other than this, the commit is mostly removing code from InnoDB, which is fine. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2020-12-22 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Mandatory changes from Marko's review: 1. There is an error in btr_node_ptr_max_size(): if (!comp && table_name_is_SYS_FOREIGN) was wrongly replaced with if (!comp). If we declared that on upgrade, all tables will lose their FOREIGN KEY constraints, then the correct course of action would have been to remove the entire if statement and its body. Fixed 2. Currently, the rewritten function dict_load_foreigns() is repeatedly calling current_thd, which is not acceptable. Why does it not take THD* as a parameter? Fixed 3. The function is formatted in a style that follows neither the common server formatting rules nor the old InnoDB style that we should not use for any new code. Many lines exceed 79 characters. Fixed 4. The function seems to blindly assume that tables are not partitioned. Fixed marko Please complement this list if I missed something. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-02-10 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
The test innodb.innodb_bug60049 is failing massively. Please, also fix the compiler warnings before requesting another review:
and another type of warnings that occurs pretty frequently (CMAKE_BUILD_TYPE=RelWithDebInfo using clang++-11):
I think that the following code (which I understand is supposed to exist only for test purposes in special debug builds) should be disabled by default:
Enabling that code by default undermines the purpose of the task, which is to phase out the InnoDB internal metadata storage for FOREIGN KEY constraints. I would like to see a test case that exercises the upgrade logic. The following failed for me when I disabled the above:
I started the 10.6 server using the following:
Then, in the middle of the execution, I switched the executable:
First, the test would fail with the following result difference:
With a rebuild after setting WITH_INNODB_LEGACY_FOREIGN_STORAGE:BOOL=OFF I got the following result difference (after removing innodb_sys_foreign and innodb_sys_foreign_cols from the startup parameters, to allow the server to start):
This proves what I suspected based on a quick read of the code: Because the code fails to delete old metadata on DDL operations, bogus FOREIGN KEY relations may be discovered. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-02-14 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
No, this understanding is wrong. This is not for debugging-only, its purpose is described in commit message. Renamed WITH_INNODB_LEGACY_FOREIGN_STORAGE into more friendly WITH_INNODB_FOREIGN_UPGRADE. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-02-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I removed the test innodb.innodb_bug60049 because it was no longer testing what it was supposed to test. We have a much better test innodb.purge_secondary for that purpose. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-02-24 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
midenok, I am confused. In the other ticket MDEV-21652, which was filed for the upgrade aspect of this, I see the following claims:
But, even though the branch that I reviewed appears to contain the MDEV-21652 changes, Edit: Sorry, I misunderstood; we are checking if those tables exist and making some adjustments when they do exist. The current upgrade almost does what it should. I will post further comments about the upgrade in MDEV-21652. I think that the upgrade must work in a satisfactory way before the rest can be addressed. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-03-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
The commit message as well as the Description must explain the InnoDB changes in detail. It now is unclear why dict_foreign_add_to_cache() was changed in the way it was. The function dict_load_table_one() could now probably be merged to dict_load_table(). In ha_innobase::open(), we now invoke dict_load_foreigns(). It looks like we will prevent the eviction of connected tables from dict_sys as long as foreign key constraints exist. So, why do we have to invoke the function on every ha_innobase::open()? And what actually is protecting the dict_table_t in dict_load_foreigns()? It does not look like we are holding dict_sys.mutex or dict_table_t::lock_mutex there. Any TODO or FIXME comments should be mentioned in the commit message with a plan how to address them. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-03-16 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Added an explanation: The semantics of dict_foreign_add_to_cache() has been changed. Foreigns must be synchronized on every table open and cache must be updated. The old semantics was to keep old foreign while it is in cache.
Actually it is protected by a dict_sys mutex. Added assertion into dict_load_foreigns() and debug test case into innodb.foreign-keys.
I agree on that partially. All FIXMEs must be fixed or planned. Not all TODOs must be planned, only important ones. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-07-01 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Before a review can be conducted, these changes will need to be rebased to the 10.7 branch, once that branch has been properly created so that it includes the changes of the 10.6.3 release. |