Sorry for the delay. This patch looks great to me, except for one thing: I think that we should write the corrected flags back to the data file. Given that 10.1 is already in GA status, we should consider our options carefully. My preference would be (1) below: (1) If we adjust 10.1.21 so that it writes back corrected flags for both old and new data files, then older 10.1.x would be unable to open the files (if compression, DATA_DIRECTORY or non-default innodb_page_size was used). (2) If we adjust 10.1.21 so that it writes correct flags for new data files (like the current patch does), then older 10.1.x would not be able to read those created-with-newer-version data files. (3) If we adjust 10.2 only, so that it writes the correct flags for new files and also adjusts the flags in old files, then we would seem to have a clear upgrade path. Whatever we choose, I would like to remove the flag-adjustment code from 10.3 or 10.4. This would mean that an upgrade (or import) directly from 10.1 is only possible if the problematic features (DATA_DIRECTORY, compression, non-standard innodb_page_size) were not used. Otherwise the upgrade would have to be done through 10.2. On Thu, Dec 29, 2016 at 10:41 PM, Jan Lindström <jan.lindstrom@mariadb.com> wrote:
revision-id: a2a42a9181729b661ba308f2d0a75d8b547ed09d (mariadb-10.1.20-15-ga2a42a9) parent(s): 23cc1be270c7304963643947d8e5ab88f4e312ee author: Jan Lindström committer: Jan Lindström timestamp: 2016-12-29 22:40:30 +0200 message:
MDEV-11623: MariaDB 10.1 fails to start datadir created with MariaDB 10.0/MySQL 5.6 using innodb-page-size!=16K
Problem was that in MariaDB 10.1 page size flag is on different position (13) on tablespace flags compared to MySQL 5.6 and MariaDB 10.0 or older position (6). DATA_DIR flag position difference was already handled.
/** Tablespace flags position and name in MySQL 5.6/MariaDB 10.0 or older and MariaDB 10.1.20 or older MariaDB 10.1 and in MariaDB 10.1.21 or newer. MySQL 5.6 MariaDB 10.1.x MariaDB 10.1.21 ==================================================================== Below flags in same offset ==================================================================== 0: POST_ANTELOPE 0:POST_ANTELOPE 0: POST_ANTELOPE 1: ZIP_SSIZE 1:ZIP_SSIZE 1: ZIP_SSIZE 5: ATOMIC_BLOBS 5:ATOMIC_BLOBS 5: ATOMIC_BLOBS
It could be clearer to explicitly identify multi-bit fields, e.g., 1..4 for ZIP_SSIZE.
===================================================================== Below note the difference in order ===================================================================== 6: PAGE_SSIZE 6:PAGE_COMPRESSION 6: PAGE_SSIZE 10: DATA_DIR 7:PAGE_COMPRESSION_LEVEL 10: DATA_DIR 11: UNUSED 11:ATOMIC_WRITES ===================================================================== Note that below flags have been moved ===================================================================== 13:PAGE_SSIZE 11: RESERVED (5.7 SHARED) 17:DATA_DIR 12: RESERVED (5.7 TEMPORARY) 18:UNUSED 13: RESERVED (5.7 ENCRYPTION) 14: PAGE_COMPRESSION 15: PAGE_COMPRESSION_LEVEL 19: ATOMIC_WRITES 21: UNUSED
s/have been moved/were in incorrect position in MariaDB 10.1.x/ It could be good to explain the impact of this. That is, what would happen if we upgrade from MariaDB 10.0 to the buggy MariaDB 10.1, or if we upgrade from the buggy MariaDB 10.1 to 10.1.21 where this has been fixed. As far as I can tell, with respect to the DATA_DIR flag we should be mostly OK, because that flag should only play a role in the table flags when determining where the data file is located, not so much in the tablespace flags inside the data file. (Moving files from MariaDB 10.1 to MySQL 5.7 could be hurt, because the DATA_DIR flag would be interpreted as the 5.7 ENCRYPTION flag.) Next, let us consider PAGE_SSIZE (bits 13..16 in the buggy MariaDB 10.1.x, 6..9 elsewhere). Upgrade from 10.0 or 5.6 to the buggy 10.1.x: We read the bits 13..16. These would always be 0 in the old data files, so we will interpret the data file as if it had innodb_page_size=16k. That is, upgrade or import from 5.6 or 10.0 to the buggy 10.1.x will be refused if innodb_page_size differs from 16k. Is this correct? (This is something that we cannot fix, because we cannot retroactively change old MariaDB 10.1.x versions.) Upgrade from the buggy 10.1.x to the fixed 10.1.x or 10.2: We read the bits 6..9 which were incorrectly used as PAGE_COMPRESSION, PAGE_COMPRESSION_LEVEL. That is, upgrade should work if innodb_page_size=16k (the flags are expected to be 0) and compression is not used (the bits 6..9 were written as 0 by the buggy 10.1.x). To help users who use the buggy 10.1.x with innodb_page_size!=16k or with compression, we can adjust the flags:
fsp0fsp.ic: fsp_flags_get_page_size() : By default read used page size from original position i.e. after ATOMIC_BLOBS. But if we detect that buggy MariaDB 10.1 flags are used, then we read it from FSP_FLAGS_PAGE_SSIZE_MARIADB101. [snip] Tested also with MySQL 5.7 datadir (after deleting redo logs and copying mysql/* files from 10.1) but that failed because SYS_INDEXES got an extra column in 5.7 SYS_INDEXES.MERGE_THRESHOLD.
You could theoretically have initialized the database in MySQL 5.6 and then upgraded it to 5.7. All tables that were created in 5.6 should be accessible. But would not have added much value.
After shutdown, started MariaDB 10.1.21 with fix and verified that all tables created are accessible.
Were the flags corrected in the data file afterwards? I think that we should correct the flags. Please add a mysql-test case that demonstrates that the flags are corrected. Something like this: --source include/have_innodb.inc --source include/not_embedded.inc # restart will restore the old values SET GLOBAL innodb_file_per_table=1; SET GLOBAL innodb_file_format=Barracuda; let INNODB_PAGE_SIZE=`select @@innodb_page_size`; let MYSQLD_DATADIR=`select @@datadir`; CREATE TABLE tr(a INT)ENGINE=InnoDB ROW_FORMAT=REDUNDANT; CREATE TABLE tc(a INT)ENGINE=InnoDB ROW_FORMAT=COMPACT; CREATE TABLE td(a INT)ENGINE=InnoDB ROW_FORMAT=DYNAMIC; CREATE TABLE tz(a INT)ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=1; # TODO: repeat the same with DATA_DIRECTORY and COMPRESSION --source include/shutdown_mysqld.inc perl; # corrupt the FSP_FLAGS in each file; write the page checksum as 0xdeadbeef # (see log_data_file_size.test or some recent test from MySQL) EOF; --let $restart_parameters = --innodb-read-only; --source include/start_mysqld.inc CHECK TABLE tr; CHECK TABLE tc; CHECK TABLE td; CHECK TABLE tz; --source include/shutdown_mysqld.inc perl; # check that the FSP_FLAGS are still corrupted # (they must not be adjusted in read-only mode) EOF; --let $restart_parameters=; --source include/start_mysqld.inc CHECK TABLE tr; CHECK TABLE tc; CHECK TABLE td; CHECK TABLE tz; --source include/shutdown_mysqld.inc perl; # check that the FSP_FLAGS now correspond to $ENV{INNODB_PAGE_SIZE} EOF; --source include/start_mysqld.inc DROP TABLE tr,tc,td,tz;
@@ -3859,21 +3837,11 @@ fil_open_single_table_tablespace( + /* Validate this single-table-tablespace with SYS_TABLES. */ + bool flags_correct = dict_compare_flags(def.flags, flags);
I think that the function name should be more descriptive, making clear that the first argument contains the table flags and the second the FIL_SPACE_FLAGS: dict_tf_match_space(def.flags, flags) The comment does not really add any value. I would omit it.
diff --git a/storage/innobase/fsp/fsp0fsp.cc b/storage/innobase/fsp/fsp0fsp.cc index 87aa5f7..7ff5244 100644 --- a/storage/innobase/fsp/fsp0fsp.cc +++ b/storage/innobase/fsp/fsp0fsp.cc @@ -4182,3 +4182,141 @@ fsp_page_is_free_func( return xdes_mtr_get_bit( descr, XDES_FREE_BIT, page_no % FSP_EXTENT_SIZE, mtr); } + +/********************************************************************//** +Verify that dictionary flags modified to tablespace flags +and actual tablespace flags stored to FSP header match. +@param[in] dict_flags dict_tf_to_fsp_flags(dict_table_t::flags) +@param[in] fsp_flags Actual flags stored to FSP header in page 0 +@return true if flags match, false if not */ +bool +fsp_verify_flags( + ulint dict_flags, + ulint fsp_flags) +{ + ulint dict_unused = FSP_FLAGS_GET_UNUSED(dict_flags); + ulint dict_antelope = FSP_FLAGS_GET_POST_ANTELOPE(dict_flags); + ulint dict_zssize = FSP_FLAGS_GET_ZIP_SSIZE(dict_flags); + ulint dict_ablobs = FSP_FLAGS_HAS_ATOMIC_BLOBS(dict_flags);
Add an early return before all these computations if dict_flags==fsp_flags. I think that the name dict_flags is misleading. Both flags are supposed to be tablespace flags, not data dictionary (or table) flags. How about this: s/dict_flags/expected/ s/fsp_flags/actual/ And then define the local variables with expected_ and actual_ prefixes, instead of dict_ and fsp_. That would make the code easier to follow.
+ DBUG_EXECUTE_IF("fsp_verify_flags_failure", + return(false););
Where is the test to exercise this? Do we even need this? I think it is better to inject faults into the data files using Perl code in mysql-test (see my previous suggestion). Other than this, the function looks OK to me.
diff --git a/storage/innobase/include/dict0dict.h b/storage/innobase/include/dict0dict.h index 42f93b5..479954d 100644 --- a/storage/innobase/include/dict0dict.h +++ b/storage/innobase/include/dict0dict.h @@ -1904,6 +1904,17 @@ dict_table_get_index_on_first_col( #endif /* !UNIV_HOTBACKUP */
+/** Compare tablespace flags. +@param[in] flags SYS_TABLES.flags +@param[in] mod_flags Tablespace flags. +@return true if flags match, false if not */ +UNIV_INLINE +bool +dict_compare_flags( + ulint flags, + ulint mod_flags) + MY_ATTRIBUTE((warn_unused_result)); +
Can we declare the inline function directly in the .h file, instead of splitting it into the .ic file? I would suggest different names: bool dict_tf_match_space(ulint table_flags, ulint fsp_flags);
@@ -980,12 +982,11 @@ dict_sys_tables_type_to_tf( PAGE_COMPRESSION_LEVEL, ATOMIC_WRITES are the same. */ flags |= type & (DICT_TF_MASK_ZIP_SSIZE | DICT_TF_MASK_ATOMIC_BLOBS - | DICT_TF_MASK_DATA_DIR - | DICT_TF_MASK_PAGE_COMPRESSION - | DICT_TF_MASK_PAGE_COMPRESSION_LEVEL - | DICT_TF_MASK_ATOMIC_WRITES + | DICT_TF_MASK_DATA_DIR + | DICT_TF_MASK_PAGE_COMPRESSION + | DICT_TF_MASK_PAGE_COMPRESSION_LEVEL + | DICT_TF_MASK_ATOMIC_WRITES);
- );
return(flags); }
This appears to be a white-space-only change. We could avoid it, unless this is actually fixing the indentation. The spaces and TABs got lost in transit, so I cannot tell..
@@ -1018,8 +1019,8 @@ dict_tf_to_sys_tables_type( type |= flags & (DICT_TF_MASK_ZIP_SSIZE | DICT_TF_MASK_ATOMIC_BLOBS | DICT_TF_MASK_DATA_DIR - | DICT_TF_MASK_PAGE_COMPRESSION - | DICT_TF_MASK_PAGE_COMPRESSION_LEVEL + | DICT_TF_MASK_PAGE_COMPRESSION + | DICT_TF_MASK_PAGE_COMPRESSION_LEVEL | DICT_TF_MASK_ATOMIC_WRITES);
return(type);
Same here.
DBUG_EXECUTE_IF("dict_tf_verify_flags_failure", - return(ULINT_UNDEFINED);); + return(false););
Nice catch. But is there any test to exercise this? If not, I would remove the DBUG_EXECUTE_IF altogether.
ut_a(page_ssize == 0 || page_ssize != 0); /* silence compiler */ ut_a(compact == 0 || compact == 1); /* silence compiler */ ut_a(data_dir == 0 || data_dir == 1); /* silence compiler */ - ut_a(post_antelope == 0 || post_antelope == 1); /* silence compiler */ + ut_a(post_antelope == 0 || post_antelope == 1); /* silence compiler + */
I think it would be better to declare these variables as __attribute__((unused)) or to remove the variables altogether if they are unused.
+ + if (table_unused || fsp_unused) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Table flags has unused %ld" + " in the data dictionary" + " but the flags in file has unused %ld\n", + table_unused, fsp_unused); + + return (false); + }
0x%lx would be more user-friendly here.
+ ib_logf(IB_LOG_LEVEL_ERROR, + "Table flags has page_compression %ld" + " in the data dictionary" + " but the flags in file ahas page_compression %ld\n", page_compression, fsp_page_compression);
s/ahas/has/
+/********************************************************************//** +Verify that dictionary flags modified to tablespace flags +and actual tablespace flags stored to FSP header match. +@param[in] dict_flags dict_tf_to_fsp_flags(dict_table_t::flags) +@param[in] fsp_flags Actual flags stored to FSP header in page 0 +@return true if flags match, false if not */ +bool +fsp_verify_flags( + ulint dict_flags, + ulint fsp_flags) + MY_ATTRIBUTE((warn_unused_result)); +
This seems to be missing UNIV_INTERN? I think we need it in 10.1 at least for innodb_plugin.
+ /** In MariaDB 10.1.20 or older MariaDB 10.1 PAGE_SSIZE + was stored after ATOMIC_WRITES not after ATOMIC_BLOBS + as in MySQL 5.6, MariaDB 10.0 or older. New tables + in MariDB 10.1.21 or newer store PAGE_SSIZE after ATOMIC_BLOBS. + */ + ssize = FSP_FLAGS_GET_PAGE_SSIZE(flags);
Do not use /** comments before local variables. I think that one of the ‘after’ in the comment should be ‘before’. Also fix the typo MariDB. -- DON’T MISS M|17 April 11 - 12, 2017 The Conrad Hotel New York City https://m17.mariadb.com/ Marko Mäkelä, Lead Developer InnoDB MariaDB Corporation