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

MDEV-27161: Add option for SQL thread to limit maximum execution time per query replicated

New Feature:
============
This patch adds a new system variable, @@slave_max_statement_time,
which limits the execution time of s slave’s events that implements
an equivalent to @@max_statement_time for slave applier.

Reviewed By:
============
Andrei Elkin <andrei.elkin@mariadb.com>
This commit is contained in:
Brandon Nesterenko
2022-06-27 12:29:10 -06:00
committed by Andrei
parent 7864d955f3
commit 360d99429c
13 changed files with 434 additions and 7 deletions

View File

@@ -0,0 +1,79 @@
#
# Helper test file to ensure that an event running on a slave which executes
# for longer than @@slave_max_statement_time will time out. By default, it will
# use the sleep function to imitate a long running function. This can be
# changed to use locks using the parameter with_lock.
#
# Parameters:
# with_lock (boolean, in) : Changes the long running command from using SLEEP
# to using locks. In particular, the MTR test will take the table level
# write lock on the slave side, while the slave concurrently tries to
# execute an insert statement.
#
# use_load_data (boolean, in) : If in row logging format, uses LOAD DATA
# INFILLE command to create Load_log_events to create the events which
# should time out
#
--connection master
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
--source include/save_master_gtid.inc
--connection slave
--source include/sync_with_master_gtid.inc
--source include/stop_slave.inc
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
SET GLOBAL slave_max_statement_time=0.75;
--connection master
--echo # Long running command due to a lock conflict
if (!$use_load_data)
{
INSERT INTO t1(b) VALUES (1);
}
if ($use_load_data)
{
load data infile '../../std_data/rpl_loaddata.dat' into table t1;
}
--source include/save_master_gtid.inc
--connection slave1
BEGIN; INSERT INTO t1(b) VALUES (1);
--connection slave
--echo # Starting slave to receive event which will take longer to execute
--echo # than slave_max_statement_time
START SLAVE;
# ER_SLAVE_STATEMENT_TIMEOUT
--let $slave_sql_errno= 4192
--source include/wait_for_slave_sql_error.inc
--echo # Ensuring event was not processed..
--let $t1_count= `select count(*) from t1`
if ($t1_count != 0)
{
--die Event should have timed out on the slave and not been executed
}
--echo # ..success
--echo # Remove slave timeout and catch up to master
SET GLOBAL slave_max_statement_time=0;
--connection slave1
ROLLBACK;
--source include/start_slave.inc
--source include/sync_with_master_gtid.inc
--echo # Test case cleanup
--connection master
DROP TABLE t1;
--source include/save_master_gtid.inc
--connection slave
--source include/sync_with_master_gtid.inc
--source include/stop_slave.inc
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
--source include/start_slave.inc

View File

@@ -0,0 +1,170 @@
include/master-slave.inc
[connection master]
#
# Set up
#
connection master;
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
connection slave;
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Slave log event execution was interrupted");
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
SET @save_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
#
# Test Case 1) Using a serial slave, the SQL thread should time out when
# its underlying event executes for longer than @@slave_max_statement_time.
#
connection master;
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/stop_slave.inc
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
SET GLOBAL slave_max_statement_time=0.75;
connection master;
# Long running command due to a lock conflict
INSERT INTO t1(b) VALUES (1);
include/save_master_gtid.inc
connection slave1;
BEGIN;
INSERT INTO t1(b) VALUES (1);
connection slave;
# Starting slave to receive event which will take longer to execute
# than slave_max_statement_time
START SLAVE;
include/wait_for_slave_sql_error.inc [errno=4192]
# Ensuring event was not processed..
# ..success
# Remove slave timeout and catch up to master
SET GLOBAL slave_max_statement_time=0;
connection slave1;
ROLLBACK;
include/start_slave.inc
include/sync_with_master_gtid.inc
# Test case cleanup
connection master;
DROP TABLE t1;
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/stop_slave.inc
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
include/start_slave.inc
#
# Test Case 2) Using a parallel slave, a worker thread should time out
# when its underlying event executes for longer than
# @@slave_max_statement_time
#
include/stop_slave.inc
SET @old_parallel_threads=@@GLOBAL.slave_parallel_threads;
SET @old_parallel_mode=@@GLOBAL.slave_parallel_mode;
SET GLOBAL slave_parallel_threads=2;
SET GLOBAL slave_parallel_mode='optimistic';
include/start_slave.inc
connection master;
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/stop_slave.inc
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
SET GLOBAL slave_max_statement_time=0.75;
connection master;
# Long running command due to a lock conflict
INSERT INTO t1(b) VALUES (1);
include/save_master_gtid.inc
connection slave1;
BEGIN;
INSERT INTO t1(b) VALUES (1);
connection slave;
# Starting slave to receive event which will take longer to execute
# than slave_max_statement_time
START SLAVE;
include/wait_for_slave_sql_error.inc [errno=4192]
# Ensuring event was not processed..
# ..success
# Remove slave timeout and catch up to master
SET GLOBAL slave_max_statement_time=0;
connection slave1;
ROLLBACK;
include/start_slave.inc
include/sync_with_master_gtid.inc
# Test case cleanup
connection master;
DROP TABLE t1;
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/stop_slave.inc
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
include/start_slave.inc
include/stop_slave.inc
SET GLOBAL slave_parallel_mode=@old_parallel_mode;
SET GLOBAL slave_parallel_threads=@old_parallel_threads;
include/start_slave.inc
#
# Test Case 3) Load-based log events (from LOAD DATA INFILE) should time
# out if their execution time exceeds @@slave_max_statement_time
#
connection master;
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/stop_slave.inc
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
SET GLOBAL slave_max_statement_time=0.75;
connection master;
# Long running command due to a lock conflict
load data infile '../../std_data/rpl_loaddata.dat' into table t1;
include/save_master_gtid.inc
connection slave1;
BEGIN;
INSERT INTO t1(b) VALUES (1);
connection slave;
# Starting slave to receive event which will take longer to execute
# than slave_max_statement_time
START SLAVE;
include/wait_for_slave_sql_error.inc [errno=4192]
# Ensuring event was not processed..
# ..success
# Remove slave timeout and catch up to master
SET GLOBAL slave_max_statement_time=0;
connection slave1;
ROLLBACK;
include/start_slave.inc
include/sync_with_master_gtid.inc
# Test case cleanup
connection master;
DROP TABLE t1;
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/stop_slave.inc
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
include/start_slave.inc
#
# Test Case 4) Locally executed long running statements should not time
# out due to @@slave_max_statement_time
#
connection slave;
include/stop_slave.inc
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
SET @old_gtid_domain_id=@@GLOBAL.gtid_domain_id;
SET @@GLOBAL.slave_max_statement_time=0.75;
SET @@GLOBAL.gtid_domain_id=1;
include/start_slave.inc
CREATE TABLE t2 (a int);
SET STATEMENT sql_log_bin=0 FOR INSERT INTO t2 SELECT SLEEP(1);
DROP TABLE t2;
include/stop_slave.inc
SET GLOBAL gtid_domain_id=@old_gtid_domain_id;
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
include/start_slave.inc
# Cleanup
include/stop_slave.inc
SET GLOBAL slave_max_statement_time=@save_slave_max_statement_time;
include/start_slave.inc
include/rpl_end.inc
# End of rpl_slave_max_statement_time.test

View File

@@ -0,0 +1,110 @@
#
# Purpose:
# This test ensures that the slave can limit the execution time of its
# events via the global system variable @@slave_max_statement_time.
#
# Methodology:
# This test uses the following test cases to ensure that a slave will
# correctly limit the execution time of its events:
# 1) Using a serial slave, the SQL thread should time out when its underlying
# event executes for longer than @@slave_max_statement_time.
# 2) Using a parallel slave, a worker thread should time out when its
# underlying event executes for longer than @@slave_max_statement_time.
# 3) Load-based log events (from LOAD DATA INFILE) should time out if their
# execution time exceeds @@slave_max_statement_time
# 4) Locally executed long running statements should not time out due to
# @@slave_max_statement_time.
#
# References:
# MDEV-27161: Add option for SQL thread to limit maximum execution time per
# query replicated
#
--source include/have_innodb.inc
--source include/master-slave.inc
--echo #
--echo # Set up
--echo #
--connection master
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
--connection slave
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Slave log event execution was interrupted");
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
SET @save_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
--let $with_lock= 1
--echo #
--echo # Test Case 1) Using a serial slave, the SQL thread should time out when
--echo # its underlying event executes for longer than @@slave_max_statement_time.
--echo #
--source include/rpl_slave_max_statement_time.inc
--echo #
--echo # Test Case 2) Using a parallel slave, a worker thread should time out
--echo # when its underlying event executes for longer than
--echo # @@slave_max_statement_time
--echo #
--source include/stop_slave.inc
SET @old_parallel_threads=@@GLOBAL.slave_parallel_threads;
SET @old_parallel_mode=@@GLOBAL.slave_parallel_mode;
SET GLOBAL slave_parallel_threads=2;
SET GLOBAL slave_parallel_mode='optimistic';
--source include/start_slave.inc
--source include/rpl_slave_max_statement_time.inc
--source include/stop_slave.inc
SET GLOBAL slave_parallel_mode=@old_parallel_mode;
SET GLOBAL slave_parallel_threads=@old_parallel_threads;
--source include/start_slave.inc
--echo #
--echo # Test Case 3) Load-based log events (from LOAD DATA INFILE) should time
--echo # out if their execution time exceeds @@slave_max_statement_time
--echo #
--let $use_load_data= 1
--source include/rpl_slave_max_statement_time.inc
--let $use_load_data=
--echo #
--echo # Test Case 4) Locally executed long running statements should not time
--echo # out due to @@slave_max_statement_time
--echo #
--connection slave
--source include/stop_slave.inc
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
SET @old_gtid_domain_id=@@GLOBAL.gtid_domain_id;
SET @@GLOBAL.slave_max_statement_time=0.75;
SET @@GLOBAL.gtid_domain_id=1;
--source include/start_slave.inc
CREATE TABLE t2 (a int);
SET STATEMENT sql_log_bin=0 FOR INSERT INTO t2 SELECT SLEEP(1);
--let $t2_count= `SELECT COUNT(*) FROM t2`
if ($t2_count != 1)
{
--die Local long running insert statement should have completed
}
DROP TABLE t2;
--source include/stop_slave.inc
SET GLOBAL gtid_domain_id=@old_gtid_domain_id;
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
--source include/start_slave.inc
--echo # Cleanup
--source include/stop_slave.inc
SET GLOBAL slave_max_statement_time=@save_slave_max_statement_time;
--source include/start_slave.inc
--source include/rpl_end.inc
--echo # End of rpl_slave_max_statement_time.test