1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-08 11:22:35 +03:00

MDEV-33921: Replication breaks when filtering two-phase XA transactions

There are two problems.

First, replication fails when XA transactions are used where the
slave has replicate_do_db set and the client has touched a different
database when running DML such as inserts. This is because XA
commands are not treated as keywords, and are thereby not exempt
from the replication filter. The effect of this is that during an XA
transaction, if its logged “use db” from the master is filtered out
by the replication filter, then XA END will be ignored, yet its
corresponding XA PREPARE will be executed in an invalid state,
thereby breaking replication.

Second, if the slave replicates an XA transaction which results in
an empty transaction, the XA START through XA PREPARE first phase of
the transaction won’t be binlogged, yet the XA COMMIT will be
binlogged. This will break replication in chain configurations.

The first problem is fixed by treating XA commands in
Query_log_event as keywords, thus allowing them to bypass the
replication filter. Note that Query_log_event::is_trans_keyword() is
changed to accept a new parameter to define its mode, to either
check for XA commands or regular transaction commands, but not both.
In addition, mysqlbinlog is adapted to use this mode so its
--database filter does not remove XA commands from its output.

The second problem fixed by overwriting the XA state in the XID
cache to be XA_ROLLBACK_ONLY, so at commit time, the server knows to
rollback the transaction and skip its binlogging. If the xid cache
is cleared before an XA transaction receives its completion command
(e.g. on server shutdown), then before reporting ER_XAER_NOTA when
the completion command is executed, the filter is first checked if
the database is ignored, and if so, the error is ignored.

Reviewed By:
============
Kristian Nielsen <knielsen@knielsen-hq.org>
Andrei Elkin <andrei.elkin@mariadb.com>
This commit is contained in:
Brandon Nesterenko
2024-06-20 12:21:48 -06:00
committed by Brandon Nesterenko
parent 9fdc0e5440
commit ea9869504d
11 changed files with 287 additions and 11 deletions

View File

@@ -1164,6 +1164,84 @@ include/sync_with_master_gtid.inc
connection server_1;
set @@binlog_format = @sav_binlog_format;
set @@global.binlog_format = @sav_binlog_format;
#
# MDEV-33921.1: If a slave's replication of an XA transaction results in
# an empty transaction, e.g. due to replication filters, the slave
# should not binlog any part of the XA transaction.
connection server_1;
create database db1;
create database db2;
create table db1.t1 (a int) engine=innodb;
include/save_master_gtid.inc
connection server_3;
include/sync_with_master_gtid.inc
include/stop_slave.inc
connection server_2;
include/stop_slave.inc
SET @@GLOBAL.replicate_ignore_db= "";
SET @@GLOBAL.replicate_do_db= "db2";
include/start_slave.inc
connection server_1;
use db1;
XA START "x1";
insert into db1.t1 values (1);
XA END "x1";
XA PREPARE "x1";
XA COMMIT "x1";
include/save_master_gtid.inc
connection server_2;
include/sync_with_master_gtid.inc
connection server_2;
include/save_master_gtid.inc
connection server_3;
include/start_slave.inc
include/sync_with_master_gtid.inc
#
# 33921.2: If the slave shuts down after "preparing" a filtered-to-empty
# XA transaction (and not completing it), then when the respective
# XA completion (COMMIT in this test) command is replicated, the slave
# should not throw ER_XAER_NOTA. Note that internally, the error is
# thrown, but it is ignored because the target db is filtered.
connection server_3;
include/stop_slave.inc
connection server_1;
use db1;
XA START "x2";
insert into db1.t1 values (2);
XA END "x2";
XA PREPARE "x2";
include/save_master_gtid.inc
connection server_2;
include/sync_with_master_gtid.inc
# Connection named slave is needed for reconnection
connect slave,localhost,root,,;
connect slave1,localhost,root,,;
include/rpl_restart_server.inc [server_number=2]
connection server_2;
include/stop_slave.inc
SET @@GLOBAL.replicate_do_db= "db2";
include/start_slave.inc
connection server_1;
XA COMMIT "x2";
connection server_2;
include/sync_with_master_gtid.inc
include/save_master_gtid.inc
connection server_3;
include/start_slave.inc
include/sync_with_master_gtid.inc
#
# 33921.3: Ensure XA commands are not considered by mysqlbinlog's
# --database filter
connection server_1;
# MYSQL_BINLOG datadir/binlog_file --start-position=pre_xa_pos --database=db2 --result-file=assert_file
include/assert_grep.inc [Mysqlbinlog should output all XA commands from the filtered transaction]
connection server_2;
include/stop_slave.inc
SET @@GLOBAL.replicate_do_db="";
include/start_slave.inc
connection server_1;
drop database db1;
drop database db2;
connection server_1;
include/rpl_end.inc
# End of rpl_xa_empty_transaction.test

View File

@@ -32,6 +32,10 @@
# MDEV-25616: Binlog event for XA COMMIT is generated without matching
# XA START, replication aborts
#
# MDEV-33921: Replication fails when XA transactions are used where the slave
# has replicate_do_db set and the client has touched a different
# database when running DML such as inserts.
#
--source include/have_log_bin.inc
--let $rpl_server_count= 3
@@ -167,6 +171,129 @@ set @@global.binlog_format = row;
set @@binlog_format = @sav_binlog_format;
set @@global.binlog_format = @sav_binlog_format;
--echo #
--echo # MDEV-33921.1: If a slave's replication of an XA transaction results in
--echo # an empty transaction, e.g. due to replication filters, the slave
--echo # should not binlog any part of the XA transaction.
#
# Note that the MDEV-33921 report is actually about that XA END is filtered
# out (not executed), and then its corresponding XA PREPARE errors because the
# XA state of the transaction is incorrect. This test case inherently tests
# both bugs.
--connection server_1
create database db1;
create database db2;
create table db1.t1 (a int) engine=innodb;
--source include/save_master_gtid.inc
--connection server_3
--source include/sync_with_master_gtid.inc
--source include/stop_slave.inc
--connection server_2
--source include/stop_slave.inc
SET @@GLOBAL.replicate_ignore_db= "";
SET @@GLOBAL.replicate_do_db= "db2";
--source include/start_slave.inc
--connection server_1
--let $pre_xa_gtid= `SELECT @@global.gtid_binlog_pos`
use db1;
XA START "x1";
insert into db1.t1 values (1);
XA END "x1";
XA PREPARE "x1";
XA COMMIT "x1";
--source include/save_master_gtid.inc
--connection server_2
--source include/sync_with_master_gtid.inc
--let $slave_binlogged_gtid= `SELECT @@global.gtid_binlog_pos`
if (`SELECT strcmp("$slave_binlogged_gtid","$pre_xa_gtid")`)
{
--die Slave binlogged an empty XA transaction yet should not have
}
--connection server_2
--source include/save_master_gtid.inc
--connection server_3
--source include/start_slave.inc
--source include/sync_with_master_gtid.inc
--echo #
--echo # 33921.2: If the slave shuts down after "preparing" a filtered-to-empty
--echo # XA transaction (and not completing it), then when the respective
--echo # XA completion (COMMIT in this test) command is replicated, the slave
--echo # should not throw ER_XAER_NOTA. Note that internally, the error is
--echo # thrown, but it is ignored because the target db is filtered.
--connection server_3
--source include/stop_slave.inc
--connection server_1
--let $pre_xa_gtid= `SELECT @@global.gtid_binlog_pos`
# Used by mysqlbinlog in part 3
--let $pre_xa_pos = query_get_value(SHOW MASTER STATUS, Position, 1)
use db1;
XA START "x2";
insert into db1.t1 values (2);
XA END "x2";
XA PREPARE "x2";
--source include/save_master_gtid.inc
--connection server_2
--source include/sync_with_master_gtid.inc
--let $rpl_server_number= 2
--echo # Connection named slave is needed for reconnection
--connect(slave,localhost,root,,)
--connect(slave1,localhost,root,,)
--source include/rpl_restart_server.inc
--connection server_2
--source include/stop_slave.inc
SET @@GLOBAL.replicate_do_db= "db2";
--source include/start_slave.inc
--connection server_1
XA COMMIT "x2";
--connection server_2
--source include/sync_with_master_gtid.inc
--source include/save_master_gtid.inc
--connection server_3
--source include/start_slave.inc
--source include/sync_with_master_gtid.inc
--echo #
--echo # 33921.3: Ensure XA commands are not considered by mysqlbinlog's
--echo # --database filter
--connection server_1
--let $datadir= `select @@datadir`
--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1)
--let assert_file= $MYSQLTEST_VARDIR/tmp/binlog_decoded.out
--echo # MYSQL_BINLOG datadir/binlog_file --start-position=pre_xa_pos --database=db2 --result-file=assert_file
--exec $MYSQL_BINLOG $datadir/$binlog_file --start-position=$pre_xa_pos --database=db2 --result-file=$assert_file
--let assert_text= Mysqlbinlog should output all XA commands from the filtered transaction
--let assert_count= 4
--let assert_select= XA START|XA END|XA PREPARE|XA COMMIT
--source include/assert_grep.inc
--connection server_2
--source include/stop_slave.inc
SET @@GLOBAL.replicate_do_db="";
--source include/start_slave.inc
--connection server_1
drop database db1;
drop database db2;
#
# Cleanup
--connection server_1