From 9fa718b1a144dc0d680becd2aabd8b620f444517 Mon Sep 17 00:00:00 2001 From: Kristian Nielsen Date: Fri, 22 Sep 2023 13:10:58 +0200 Subject: [PATCH] Fix mariabackup InnoDB recovered binlog position on server upgrade Before MariaDB 10.3.5, the binlog position was stored in the TRX_SYS page, while after it is stored in rollback segments. There is code to read the legacy position from TRX_SYS to handle upgrades. The problem was if the legacy position happens to compare larger than the position found in rollback segments; in this case, the old TRX_SYS position would incorrectly be preferred over the newer position from rollback segments. Fixed by always preferring a position from rollback segments over a legacy position. Signed-off-by: Kristian Nielsen --- mysql-test/suite/innodb/include/crc32.pl | 23 ++++++++ .../suite/innodb/include/innodb-util.pl | 19 +++++++ .../mariabackup/slave_provision_nolock.result | 1 + .../mariabackup/slave_provision_nolock.test | 52 +++++++++++++++++++ storage/innobase/include/trx0sys.h | 2 + storage/innobase/trx/trx0rseg.cc | 8 ++- 6 files changed, 104 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/innodb/include/crc32.pl b/mysql-test/suite/innodb/include/crc32.pl index c2bce09dd36..b26f1057e2a 100644 --- a/mysql-test/suite/innodb/include/crc32.pl +++ b/mysql-test/suite/innodb/include/crc32.pl @@ -31,3 +31,26 @@ sub mycrc32 { return $crc; } + + +# Fix the checksum of an InnoDB tablespace page. +# Inputs: +# $page A bytestring with the page data. +# $full_crc32 Checksum type, see get_full_crc32() in innodb-util.pl +# Returns: the modified page as a bytestring. +sub fix_page_crc { + my ($page, $full_crc32)= @_; + my $ps= length($page); + my $polynomial = 0x82f63b78; # CRC-32C + if ($full_crc32) { + my $ck = mycrc32(substr($page, 0, $ps - 4), 0, $polynomial); + substr($page, $ps - 4, 4) = pack("N", $ck); + } else { + my $ck= pack("N", + mycrc32(substr($page, 4, 22), 0, $polynomial) ^ + mycrc32(substr($page, 38, $ps - 38 - 8), 0, $polynomial)); + substr($page, 0, 4)= $ck; + substr($page, $ps-8, 4)= $ck; + } + return $page; +} diff --git a/mysql-test/suite/innodb/include/innodb-util.pl b/mysql-test/suite/innodb/include/innodb-util.pl index 241545dac18..328ce5c7f36 100644 --- a/mysql-test/suite/innodb/include/innodb-util.pl +++ b/mysql-test/suite/innodb/include/innodb-util.pl @@ -124,3 +124,22 @@ sub ib_restore_ibd_files { ib_restore_ibd_file($tmpd, $datadir, $db, $table); } } + +# Read the flag whether a tablespace is using full_crc32. +# Input: filehandle opened on the tablespace. +sub get_full_crc32 { + my ($TBLSPC)= @_; + my $old_pos= sysseek($TBLSPC, 0, 1); + die "tell() failed on tablespace filehandle: $!\n" + unless defined($old_pos); + sysseek($TBLSPC, 0, 0) + or die "sysseek() failed on tablespace filehandle: $!\n"; + my $tblspc_hdr; + sysread($TBLSPC, $tblspc_hdr, 58) + or die "Cannot read tablespace header: $!\n"; + sysseek($TBLSPC, $old_pos, 0) + or die "sysseek() failed on tablespace filehandle: $!\n"; + my $full_crc32= + unpack("N", substr($tblspc_hdr, 54, 4)) & 0x10; # FIL_SPACE_FLAGS + return $full_crc32; +} diff --git a/mysql-test/suite/mariabackup/slave_provision_nolock.result b/mysql-test/suite/mariabackup/slave_provision_nolock.result index c8124510385..0273686f704 100644 --- a/mysql-test/suite/mariabackup/slave_provision_nolock.result +++ b/mysql-test/suite/mariabackup/slave_provision_nolock.result @@ -1,3 +1,4 @@ +# restart RESET MASTER; CREATE TABLE t1(a varchar(60) PRIMARY KEY, b VARCHAR(60)) ENGINE INNODB; INSERT INTO t1 VALUES(1, NULL); diff --git a/mysql-test/suite/mariabackup/slave_provision_nolock.test b/mysql-test/suite/mariabackup/slave_provision_nolock.test index 9c47aebb0e6..49c2d0d66d1 100644 --- a/mysql-test/suite/mariabackup/slave_provision_nolock.test +++ b/mysql-test/suite/mariabackup/slave_provision_nolock.test @@ -3,6 +3,58 @@ # Test provisioning a slave from an existing server, using mariabackup --no-lock # and the binlog position recovered from InnoDB redo log. + +# Update the InnoDB system tablespace to simulate a pre-10.3.5 +# position in TRX_SYS. There was a bug that the wrong position could +# be recovered if the old filename in TRX_SYS compares newer than the +# newer filenames stored in rseg headers. +let MYSQLD_DATADIR=`select @@datadir`; +let INNODB_PAGE_SIZE=`select @@innodb_page_size`; + +--source include/shutdown_mysqld.inc + +--perl +use strict; +use warnings; +use Fcntl qw(:DEFAULT :seek); +do "$ENV{MTR_SUITE_DIR}/../innodb/include/crc32.pl"; +do "$ENV{MTR_SUITE_DIR}/../innodb/include/innodb-util.pl"; + +my $ps = $ENV{INNODB_PAGE_SIZE}; + +sysopen IBD_FILE, "$ENV{MYSQLD_DATADIR}/ibdata1", O_RDWR + or die "Cannot open ibdata1: $!\n"; + +# Read the TRX_SYS page. +my $page; +sysseek(IBD_FILE, $ps * 5, SEEK_SET) + or die "Cannot seek ibdata1: $!\n"; +sysread(IBD_FILE, $page, $ps) + or die "Cannot read ibdata1: $!\n"; + +# Put in an old binlog position that will compare larger than master-bin.000001 +my $old_name= '~~~-bin.999999' . chr(0); +my $old_off= 0xffff0000; +my $old_magic= 873422344; +my $binlog_offset= $ps - 1000 + 38; +substr($page, $binlog_offset, 4)= pack('N', $old_magic); +substr($page, $binlog_offset + 4, 4)= pack('N', ($old_off >> 32)); +substr($page, $binlog_offset + 8, 4)= pack('N', ($old_off & 0xffffffff)); +substr($page, $binlog_offset + 12, length($old_name))= $old_name; + +# Write back the modified page. +my $full_crc32= get_full_crc32(\*IBD_FILE); +my $page= fix_page_crc($page, $full_crc32); +sysseek(IBD_FILE, $ps * 5, SEEK_SET) + or die "Cannot seek ibdata1: $!\n"; +syswrite(IBD_FILE, $page, $ps) == $ps + or die "Cannot write ibdata1: $!\n"; +close IBD_FILE; +EOF + +--source include/start_mysqld.inc + + let $basedir=$MYSQLTEST_VARDIR/tmp/backup; RESET MASTER; diff --git a/storage/innobase/include/trx0sys.h b/storage/innobase/include/trx0sys.h index 5f518d799bd..41cc7934d43 100644 --- a/storage/innobase/include/trx0sys.h +++ b/storage/innobase/include/trx0sys.h @@ -867,6 +867,8 @@ public: uint64_t recovered_binlog_offset; /** Latest recovered binlog file name */ char recovered_binlog_filename[TRX_SYS_MYSQL_LOG_NAME_LEN]; + /** Set when latest position is from pre-version 10.3.5 TRX_SYS. */ + bool recovered_binlog_is_legacy_pos; /** diff --git a/storage/innobase/trx/trx0rseg.cc b/storage/innobase/trx/trx0rseg.cc index 10a863b0379..be52373d03b 100644 --- a/storage/innobase/trx/trx0rseg.cc +++ b/storage/innobase/trx/trx0rseg.cc @@ -468,7 +468,10 @@ static dberr_t trx_rseg_mem_restore(trx_rseg_t *rseg, trx_id_t &max_trx_id, compile_time_assert(TRX_RSEG_BINLOG_NAME_LEN == sizeof trx_sys.recovered_binlog_filename); - int cmp = *trx_sys.recovered_binlog_filename + /* Always prefer a position from rollback segment over + a legacy position from before version 10.3.5. */ + int cmp = *trx_sys.recovered_binlog_filename && + !trx_sys.recovered_binlog_is_legacy_pos ? strncmp(binlog_name, trx_sys.recovered_binlog_filename, TRX_RSEG_BINLOG_NAME_LEN) @@ -489,6 +492,7 @@ static dberr_t trx_rseg_mem_restore(trx_rseg_t *rseg, trx_id_t &max_trx_id, trx_sys.recovered_binlog_offset = binlog_offset; } + trx_sys.recovered_binlog_is_legacy_pos= false; } #ifdef WITH_WSREP @@ -564,6 +568,7 @@ static void trx_rseg_init_binlog_info(const page_t* page) trx_sys.recovered_binlog_offset = mach_read_from_8( TRX_SYS_MYSQL_LOG_INFO + TRX_SYS_MYSQL_LOG_OFFSET + TRX_SYS + page); + trx_sys.recovered_binlog_is_legacy_pos= true; } #ifdef WITH_WSREP @@ -578,6 +583,7 @@ dberr_t trx_rseg_array_init() *trx_sys.recovered_binlog_filename = '\0'; trx_sys.recovered_binlog_offset = 0; + trx_sys.recovered_binlog_is_legacy_pos= false; #ifdef WITH_WSREP trx_sys.recovered_wsrep_xid.null(); XID wsrep_sys_xid;