1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

Binlog-in-engine: mariabackup and concurrent FLUSH, PRUGE, or RESET MASTER

The mariabackup copies innodb-implemented binlog files at the end of the
backup. This does not need to block any writes to the binlogs, as the
--prepare will use the InnoDB redo to recover them into a consistent state.

However, the copy might complain about a file missing if PURGE BINARY
LOGS or RESET MASTER is run in parallel; or about a file changing size
if FLUSH BINARY LOGS is run in parallel. So prevent that by taking a
low-impact BACKUP STAGE START lock during the binlog copy, and
blocking on that in PURGE, RESET MASTER and FLUSH BINARY LOGS.

Signed-off-by: Kristian Nielsen <knielsen@knielsen-hq.org>
This commit is contained in:
Kristian Nielsen
2025-10-19 03:10:54 +02:00
parent caaf6221a6
commit 794b1d0ec5
8 changed files with 270 additions and 15 deletions

View File

@@ -357,6 +357,12 @@ void BackupImpl::process_binlog_job(std::string src, std::string dst,
binlog_found= get_binlog_header(c_src, m_page_buf.get(), start_lsn, is_empty);
if (binlog_found > 0 && !is_empty && start_lsn <= backup_lsn) {
// Test binlog_in_engine.mariabackup_binlogs will try to inject
// RESET MASTER and PURGE BINARY LOGS here.
DBUG_EXECUTE_IF("binlog_copy_sleep_2",
if (src.find("binlog-000002.ibb") !=
std::string::npos)
my_sleep(2000000););
if (!m_ds->copy_file(c_src, dst.c_str(), thread_num))
goto exit;
}

View File

@@ -5430,6 +5430,12 @@ class BackupStages {
);
// Copy InnoDB binlog files.
// Going to BACKUP STAGE START protects against RESET
// MASTER deleting files during the copy, or FLUSH
// BINARY LOGS truncating them.
if (!opt_no_lock)
xb_mysql_query(mysql_connection, "BACKUP STAGE START",
false, false);
if (!m_common_backup.copy_engine_binlogs(opt_binlog_directory,
recv_sys.lsn)) {
msg("Error on copy InnoDB binlog files");
@@ -5439,6 +5445,9 @@ class BackupStages {
msg("InnoDB binlog file backup process is finished with error");
return false;
}
if (!opt_no_lock)
xb_mysql_query(mysql_connection, "BACKUP STAGE END",
false, false);
backup_finish(backup_datasinks.m_data);
return true;

View File

@@ -134,14 +134,15 @@ SET GLOBAL innodb_flush_log_at_trx_commit= @old_innodb_flush_log_at_trx_commit;
# restart
*** MDEV-7402: 'reset master' hangs, waits for signalled COND_xid_list ***
connect con3,localhost,root,,;
SET debug_sync="reset_logs_after_set_reset_master_pending SIGNAL reset_master_ready WAIT_FOR reset_master_cont";
SET debug_sync="reset_logs_after_set_reset_master_pending SIGNAL reset_master_ready WAIT_FOR reset_master_cont TIMEOUT 1";
RESET MASTER;
connection default;
SET debug_sync="now WAIT_FOR reset_master_ready";
RESET MASTER;
ERROR HY000: Cannot execute RESET MASTER as the binlog is in use by a connected slave or other RESET MASTER or binlog reader. Check SHOW PROCESSLIST for "Binlog Dump" commands and use KILL to stop such readers
SET debug_sync="now SIGNAL reset_master_cont";
connection con3;
Warnings:
Warning 1639 debug sync point wait timed out
connection default;
SET debug_sync = 'reset';
*** MDEV-24660: MYSQL_BIN_LOG::cleanup(): Assertion `b->xid_count == 0' failed in MYSQL_BIN_LOG::cleanup

View File

@@ -156,12 +156,14 @@ connect(con3,localhost,root,,);
# flag set by another RESET MASTER; this could cause the wakeup from the
# binlog background thread not to be sent, and thus the second RESET MASTER
# to wait infinitely.
SET debug_sync="reset_logs_after_set_reset_master_pending SIGNAL reset_master_ready WAIT_FOR reset_master_cont";
# After MDEV-34705, two RESET MASTERs are protected by MDL_BACKUP_START from
# running concurrently; we test this by having a debug_sync point time
# out while a second RESET MASTER is waiting for the first one.
SET debug_sync="reset_logs_after_set_reset_master_pending SIGNAL reset_master_ready WAIT_FOR reset_master_cont TIMEOUT 1";
send RESET MASTER;
--connection default
SET debug_sync="now WAIT_FOR reset_master_ready";
--error ER_BINLOG_IN_USE
RESET MASTER;
SET debug_sync="now SIGNAL reset_master_cont";

View File

@@ -0,0 +1,73 @@
include/reset_master.inc
*** Test mariabackup concurrent with RESET MASTER and FLUSH BINARY LOGS.
CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1);
INSERT INTO t1 SELECT a+1 FROM t1;
INSERT INTO t1 SELECT a+2 FROM t1;
INSERT INTO t1 SELECT a+4 FROM t1;
INSERT INTO t1 SELECT a+8 FROM t1;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+16 FROM t1;
INSERT INTO t1 SELECT a+32 FROM t1;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+64 FROM t1;
INSERT INTO t1 SELECT a+128 FROM t1;
SET @old_needed= @@GLOBAL.slave_connections_needed_for_purge;
SET GLOBAL slave_connections_needed_for_purge=0;
connect con$i,localhost,root,,;
BEGIN NOT ATOMIC
SELECT SLEEP(0.9);
FLUSH BINARY LOGS;
END //
connection default;
connection con1;
SLEEP(0.9)
0
disconnect con1;
connection default;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+256 FROM t1;
connect con$i,localhost,root,,;
BEGIN NOT ATOMIC
SELECT SLEEP(1.1);
PURGE BINARY LOGS TO 'binlog-000003.ibb';
END //
connection default;
connection con2;
SLEEP(1.1)
0
disconnect con2;
connection default;
RESET MASTER;
INSERT INTO t1 SELECT a+512 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+768 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
connect con$i,localhost,root,,;
BEGIN NOT ATOMIC
SELECT SLEEP(0.1);
RESET MASTER;
END //
connection default;
connection con3;
SLEEP(0.1)
0
disconnect con3;
connection default;
INSERT INTO t1 SELECT a+1024 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+1280 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
connect con$i,localhost,root,,;
BEGIN NOT ATOMIC
SELECT SLEEP(1.1);
RESET MASTER;
END //
connection default;
connection con4;
SLEEP(1.1)
0
disconnect con4;
connection default;
DROP TABLE t1;
SET GLOBAL slave_connections_needed_for_purge= @old_needed;

View File

@@ -0,0 +1,118 @@
--source include/not_embedded.inc
--source include/have_debug.inc
--source include/have_innodb_binlog.inc
--source include/reset_master.inc
let $basedir= $MYSQLTEST_VARDIR/tmp/backup;
let $datadir_2= $MYSQLTEST_VARDIR/tmp/restore;
--echo *** Test mariabackup concurrent with RESET MASTER and FLUSH BINARY LOGS.
CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1);
INSERT INTO t1 SELECT a+1 FROM t1;
INSERT INTO t1 SELECT a+2 FROM t1;
INSERT INTO t1 SELECT a+4 FROM t1;
INSERT INTO t1 SELECT a+8 FROM t1;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+16 FROM t1;
INSERT INTO t1 SELECT a+32 FROM t1;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+64 FROM t1;
INSERT INTO t1 SELECT a+128 FROM t1;
# Attempt to run a RESET MASTER, a FLUSH BINARY LOGS, and a PURGE concurrent
# with the copy of the binlog files, to test that backup locks will prevent
# the binlog files from changing during the copy.
# We test this by putting a sleep in mariabackup after getting the list
# of binlog files and before copying them. And then sending paralle
# RESET/FLUSH/PURGE with a delay that is shorter than the sleep but longer
# than the typical time to run the backup.
# This gives a good chance to hit the potential race, and missing it is
# not critical, it will at most cause a false negative, but never false
# positive.
SET @old_needed= @@GLOBAL.slave_connections_needed_for_purge;
SET GLOBAL slave_connections_needed_for_purge=0;
--let $i= 1
while ($i <= 4) {
--connect con$i,localhost,root,,
--delimiter //
if ($i == 1) {
# A FLUSH BINARY LOGS that truncates binlog-000002.ibb at around the same
# time that mariabackup tries to copy it.
send BEGIN NOT ATOMIC
SELECT SLEEP(0.9);
FLUSH BINARY LOGS;
END //
}
if ($i == 2) {
# A PURGE BINARY LOGS that removes binlog-000002.ibb at around the same
# time that mariabackup tries to copy it.
send BEGIN NOT ATOMIC
SELECT SLEEP(1.1);
PURGE BINARY LOGS TO 'binlog-000003.ibb';
END //
}
if ($i == 3) {
# A RESET MASTER that removes binlog-000002.ibb early during the first
# stage of backup.
send BEGIN NOT ATOMIC
SELECT SLEEP(0.1);
RESET MASTER;
END //
}
if ($i == 4) {
# A RESET MASTER that removes binlog-000002.ibb at around the same
# time that mariabackup tries to copy it.
send BEGIN NOT ATOMIC
SELECT SLEEP(1.1);
RESET MASTER;
END //
}
--delimiter ;
--connection default
--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$basedir --dbug=+d,binlog_copy_sleep_2
--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --prepare --target-dir=$basedir
--exec $XTRABACKUP --copy-back --datadir=$datadir_2 --target-dir=$basedir
--rmdir $basedir
--rmdir $datadir_2
--connection con$i
REAP;
--disconnect con$i
--connection default
if ($i == 1) {
# Another FLUSH, moving the active binlog file to binlog-000004.ibb, so
# that next round can PURGE to remove binlog-000002.ibb.
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+256 FROM t1;
}
if ($i == 2) {
# Re-create the binlog-000002.ibb for the following RESET MASTER test.
RESET MASTER;
INSERT INTO t1 SELECT a+512 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+768 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
}
if ($i == 3) {
INSERT INTO t1 SELECT a+1024 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
INSERT INTO t1 SELECT a+1280 FROM t1 WHERE a <= 256;
FLUSH BINARY LOGS;
}
inc $i;
}
DROP TABLE t1;
SET GLOBAL slave_connections_needed_for_purge= @old_needed;

View File

@@ -174,18 +174,37 @@ bool reload_acl_and_cache(THD *thd, unsigned long long options,
tmp_write_to_binlog= 0;
if (mysql_bin_log.is_open())
{
DYNAMIC_ARRAY *drop_gtid_domain=
(thd && (thd->lex->delete_gtid_domain.elements > 0)) ?
&thd->lex->delete_gtid_domain : NULL;
if (mysql_bin_log.flush_binlog(drop_gtid_domain))
*write_to_binlog= -1;
/* Note that WSREP(thd) might not be true here e.g. during
SST. */
if (WSREP_ON)
MDL_request mdl_request;
MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_START,
MDL_EXPLICIT);
if (thd &&
thd->mdl_context.acquire_lock(&mdl_request,
thd->variables.lock_wait_timeout))
result= 1;
else
{
/* Wait for last binlog checkpoint event to be logged. */
mysql_bin_log.wait_for_last_checkpoint_event();
if (thd)
thd->backup_commit_lock= &mdl_request;
DYNAMIC_ARRAY *drop_gtid_domain=
(thd && (thd->lex->delete_gtid_domain.elements > 0)) ?
&thd->lex->delete_gtid_domain : NULL;
if (mysql_bin_log.flush_binlog(drop_gtid_domain))
*write_to_binlog= -1;
/* Note that WSREP(thd) might not be true here e.g. during
SST. */
if (WSREP_ON)
{
/* Wait for last binlog checkpoint event to be logged. */
mysql_bin_log.wait_for_last_checkpoint_event();
}
if (thd)
{
if (mdl_request.ticket)
thd->mdl_context.release_lock(mdl_request.ticket);
thd->backup_commit_lock= 0;
}
}
}
}

View File

@@ -800,6 +800,14 @@ bool purge_master_logs(THD* thd, const char* to_log)
return FALSE;
}
MDL_request mdl_request;
MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_START,
MDL_EXPLICIT);
if (thd->mdl_context.acquire_lock(&mdl_request,
thd->variables.lock_wait_timeout))
return TRUE;
thd->backup_commit_lock= &mdl_request;
int res;
if (!opt_binlog_engine_hton)
{
@@ -840,6 +848,11 @@ bool purge_master_logs(THD* thd, const char* to_log)
give_purge_note(purge_info.nonpurge_reason,
purge_info.nonpurge_filename, true);
}
if (mdl_request.ticket)
thd->mdl_context.release_lock(mdl_request.ticket);
thd->backup_commit_lock= 0;
return purge_error_message(thd, res);
}
@@ -4870,11 +4883,25 @@ int reset_master(THD* thd, rpl_gtid *init_state, uint32 init_state_len,
}
#endif /* WITH_WSREP */
bool ret= 0;
MDL_request mdl_request;
MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_START,
MDL_EXPLICIT);
if (thd->mdl_context.acquire_lock(&mdl_request,
thd->variables.lock_wait_timeout))
return 1;
thd->backup_commit_lock= &mdl_request;
/* Temporarily disable master semisync before resetting master. */
repl_semisync_master.before_reset_master();
ret= mysql_bin_log.reset_logs(thd, 1, init_state, init_state_len,
next_log_number);
repl_semisync_master.after_reset_master();
if (mdl_request.ticket)
thd->mdl_context.release_lock(mdl_request.ticket);
thd->backup_commit_lock= 0;
DBUG_EXECUTE_IF("crash_after_reset_master", DBUG_SUICIDE(););
return ret;