Skip to content

Commit 360d994

Browse files
bnestereandrelkin
authored andcommitted
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>
1 parent 7864d95 commit 360d994

13 files changed

+434
-7
lines changed

mysql-test/main/mysqld--help.result

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,11 @@ The following specify which files/extra groups are read (specified before remain
12231223
--slave-max-allowed-packet=#
12241224
The maximum packet length to sent successfully from the
12251225
master to slave.
1226+
--slave-max-statement-time=#
1227+
A query that has taken more than slave_max_statement_time
1228+
seconds to run on the slave will be aborted. The argument
1229+
will be treated as a decimal value with microsecond
1230+
precision. A value of 0 (default) means no timeout
12261231
--slave-net-timeout=#
12271232
Number of seconds to wait for more data from any
12281233
master/slave connection before aborting the read
@@ -1800,6 +1805,7 @@ slave-ddl-exec-mode IDEMPOTENT
18001805
slave-domain-parallel-threads 0
18011806
slave-exec-mode STRICT
18021807
slave-max-allowed-packet 1073741824
1808+
slave-max-statement-time 0
18031809
slave-net-timeout 60
18041810
slave-parallel-max-queued 131072
18051811
slave-parallel-mode conservative
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#
2+
# Helper test file to ensure that an event running on a slave which executes
3+
# for longer than @@slave_max_statement_time will time out. By default, it will
4+
# use the sleep function to imitate a long running function. This can be
5+
# changed to use locks using the parameter with_lock.
6+
#
7+
# Parameters:
8+
# with_lock (boolean, in) : Changes the long running command from using SLEEP
9+
# to using locks. In particular, the MTR test will take the table level
10+
# write lock on the slave side, while the slave concurrently tries to
11+
# execute an insert statement.
12+
#
13+
# use_load_data (boolean, in) : If in row logging format, uses LOAD DATA
14+
# INFILLE command to create Load_log_events to create the events which
15+
# should time out
16+
#
17+
18+
--connection master
19+
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
20+
--source include/save_master_gtid.inc
21+
22+
--connection slave
23+
--source include/sync_with_master_gtid.inc
24+
--source include/stop_slave.inc
25+
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
26+
SET GLOBAL slave_max_statement_time=0.75;
27+
28+
--connection master
29+
--echo # Long running command due to a lock conflict
30+
if (!$use_load_data)
31+
{
32+
INSERT INTO t1(b) VALUES (1);
33+
}
34+
if ($use_load_data)
35+
{
36+
load data infile '../../std_data/rpl_loaddata.dat' into table t1;
37+
}
38+
--source include/save_master_gtid.inc
39+
40+
--connection slave1
41+
BEGIN; INSERT INTO t1(b) VALUES (1);
42+
43+
--connection slave
44+
45+
--echo # Starting slave to receive event which will take longer to execute
46+
--echo # than slave_max_statement_time
47+
START SLAVE;
48+
49+
# ER_SLAVE_STATEMENT_TIMEOUT
50+
--let $slave_sql_errno= 4192
51+
--source include/wait_for_slave_sql_error.inc
52+
53+
--echo # Ensuring event was not processed..
54+
--let $t1_count= `select count(*) from t1`
55+
if ($t1_count != 0)
56+
{
57+
--die Event should have timed out on the slave and not been executed
58+
}
59+
--echo # ..success
60+
61+
--echo # Remove slave timeout and catch up to master
62+
SET GLOBAL slave_max_statement_time=0;
63+
64+
--connection slave1
65+
ROLLBACK;
66+
67+
--source include/start_slave.inc
68+
--source include/sync_with_master_gtid.inc
69+
70+
--echo # Test case cleanup
71+
--connection master
72+
DROP TABLE t1;
73+
--source include/save_master_gtid.inc
74+
75+
--connection slave
76+
--source include/sync_with_master_gtid.inc
77+
--source include/stop_slave.inc
78+
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
79+
--source include/start_slave.inc
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
include/master-slave.inc
2+
[connection master]
3+
#
4+
# Set up
5+
#
6+
connection master;
7+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
8+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
9+
connection slave;
10+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Slave log event execution was interrupted");
11+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
12+
SET @save_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
13+
#
14+
# Test Case 1) Using a serial slave, the SQL thread should time out when
15+
# its underlying event executes for longer than @@slave_max_statement_time.
16+
#
17+
connection master;
18+
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
19+
include/save_master_gtid.inc
20+
connection slave;
21+
include/sync_with_master_gtid.inc
22+
include/stop_slave.inc
23+
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
24+
SET GLOBAL slave_max_statement_time=0.75;
25+
connection master;
26+
# Long running command due to a lock conflict
27+
INSERT INTO t1(b) VALUES (1);
28+
include/save_master_gtid.inc
29+
connection slave1;
30+
BEGIN;
31+
INSERT INTO t1(b) VALUES (1);
32+
connection slave;
33+
# Starting slave to receive event which will take longer to execute
34+
# than slave_max_statement_time
35+
START SLAVE;
36+
include/wait_for_slave_sql_error.inc [errno=4192]
37+
# Ensuring event was not processed..
38+
# ..success
39+
# Remove slave timeout and catch up to master
40+
SET GLOBAL slave_max_statement_time=0;
41+
connection slave1;
42+
ROLLBACK;
43+
include/start_slave.inc
44+
include/sync_with_master_gtid.inc
45+
# Test case cleanup
46+
connection master;
47+
DROP TABLE t1;
48+
include/save_master_gtid.inc
49+
connection slave;
50+
include/sync_with_master_gtid.inc
51+
include/stop_slave.inc
52+
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
53+
include/start_slave.inc
54+
#
55+
# Test Case 2) Using a parallel slave, a worker thread should time out
56+
# when its underlying event executes for longer than
57+
# @@slave_max_statement_time
58+
#
59+
include/stop_slave.inc
60+
SET @old_parallel_threads=@@GLOBAL.slave_parallel_threads;
61+
SET @old_parallel_mode=@@GLOBAL.slave_parallel_mode;
62+
SET GLOBAL slave_parallel_threads=2;
63+
SET GLOBAL slave_parallel_mode='optimistic';
64+
include/start_slave.inc
65+
connection master;
66+
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
67+
include/save_master_gtid.inc
68+
connection slave;
69+
include/sync_with_master_gtid.inc
70+
include/stop_slave.inc
71+
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
72+
SET GLOBAL slave_max_statement_time=0.75;
73+
connection master;
74+
# Long running command due to a lock conflict
75+
INSERT INTO t1(b) VALUES (1);
76+
include/save_master_gtid.inc
77+
connection slave1;
78+
BEGIN;
79+
INSERT INTO t1(b) VALUES (1);
80+
connection slave;
81+
# Starting slave to receive event which will take longer to execute
82+
# than slave_max_statement_time
83+
START SLAVE;
84+
include/wait_for_slave_sql_error.inc [errno=4192]
85+
# Ensuring event was not processed..
86+
# ..success
87+
# Remove slave timeout and catch up to master
88+
SET GLOBAL slave_max_statement_time=0;
89+
connection slave1;
90+
ROLLBACK;
91+
include/start_slave.inc
92+
include/sync_with_master_gtid.inc
93+
# Test case cleanup
94+
connection master;
95+
DROP TABLE t1;
96+
include/save_master_gtid.inc
97+
connection slave;
98+
include/sync_with_master_gtid.inc
99+
include/stop_slave.inc
100+
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
101+
include/start_slave.inc
102+
include/stop_slave.inc
103+
SET GLOBAL slave_parallel_mode=@old_parallel_mode;
104+
SET GLOBAL slave_parallel_threads=@old_parallel_threads;
105+
include/start_slave.inc
106+
#
107+
# Test Case 3) Load-based log events (from LOAD DATA INFILE) should time
108+
# out if their execution time exceeds @@slave_max_statement_time
109+
#
110+
connection master;
111+
create table t1(a int not null auto_increment, b int, primary key(a)) engine=InnoDB;
112+
include/save_master_gtid.inc
113+
connection slave;
114+
include/sync_with_master_gtid.inc
115+
include/stop_slave.inc
116+
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
117+
SET GLOBAL slave_max_statement_time=0.75;
118+
connection master;
119+
# Long running command due to a lock conflict
120+
load data infile '../../std_data/rpl_loaddata.dat' into table t1;
121+
include/save_master_gtid.inc
122+
connection slave1;
123+
BEGIN;
124+
INSERT INTO t1(b) VALUES (1);
125+
connection slave;
126+
# Starting slave to receive event which will take longer to execute
127+
# than slave_max_statement_time
128+
START SLAVE;
129+
include/wait_for_slave_sql_error.inc [errno=4192]
130+
# Ensuring event was not processed..
131+
# ..success
132+
# Remove slave timeout and catch up to master
133+
SET GLOBAL slave_max_statement_time=0;
134+
connection slave1;
135+
ROLLBACK;
136+
include/start_slave.inc
137+
include/sync_with_master_gtid.inc
138+
# Test case cleanup
139+
connection master;
140+
DROP TABLE t1;
141+
include/save_master_gtid.inc
142+
connection slave;
143+
include/sync_with_master_gtid.inc
144+
include/stop_slave.inc
145+
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
146+
include/start_slave.inc
147+
#
148+
# Test Case 4) Locally executed long running statements should not time
149+
# out due to @@slave_max_statement_time
150+
#
151+
connection slave;
152+
include/stop_slave.inc
153+
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
154+
SET @old_gtid_domain_id=@@GLOBAL.gtid_domain_id;
155+
SET @@GLOBAL.slave_max_statement_time=0.75;
156+
SET @@GLOBAL.gtid_domain_id=1;
157+
include/start_slave.inc
158+
CREATE TABLE t2 (a int);
159+
SET STATEMENT sql_log_bin=0 FOR INSERT INTO t2 SELECT SLEEP(1);
160+
DROP TABLE t2;
161+
include/stop_slave.inc
162+
SET GLOBAL gtid_domain_id=@old_gtid_domain_id;
163+
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
164+
include/start_slave.inc
165+
# Cleanup
166+
include/stop_slave.inc
167+
SET GLOBAL slave_max_statement_time=@save_slave_max_statement_time;
168+
include/start_slave.inc
169+
include/rpl_end.inc
170+
# End of rpl_slave_max_statement_time.test
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#
2+
# Purpose:
3+
# This test ensures that the slave can limit the execution time of its
4+
# events via the global system variable @@slave_max_statement_time.
5+
#
6+
# Methodology:
7+
# This test uses the following test cases to ensure that a slave will
8+
# correctly limit the execution time of its events:
9+
# 1) Using a serial slave, the SQL thread should time out when its underlying
10+
# event executes for longer than @@slave_max_statement_time.
11+
# 2) Using a parallel slave, a worker thread should time out when its
12+
# underlying event executes for longer than @@slave_max_statement_time.
13+
# 3) Load-based log events (from LOAD DATA INFILE) should time out if their
14+
# execution time exceeds @@slave_max_statement_time
15+
# 4) Locally executed long running statements should not time out due to
16+
# @@slave_max_statement_time.
17+
#
18+
# References:
19+
# MDEV-27161: Add option for SQL thread to limit maximum execution time per
20+
# query replicated
21+
#
22+
--source include/have_innodb.inc
23+
--source include/master-slave.inc
24+
25+
--echo #
26+
--echo # Set up
27+
--echo #
28+
--connection master
29+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
30+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
31+
--connection slave
32+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Slave log event execution was interrupted");
33+
SET STATEMENT sql_log_bin=0 FOR CALL mtr.add_suppression("Unsafe statement written to the binary log using statement format");
34+
SET @save_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
35+
36+
--let $with_lock= 1
37+
38+
--echo #
39+
--echo # Test Case 1) Using a serial slave, the SQL thread should time out when
40+
--echo # its underlying event executes for longer than @@slave_max_statement_time.
41+
--echo #
42+
--source include/rpl_slave_max_statement_time.inc
43+
44+
45+
--echo #
46+
--echo # Test Case 2) Using a parallel slave, a worker thread should time out
47+
--echo # when its underlying event executes for longer than
48+
--echo # @@slave_max_statement_time
49+
--echo #
50+
51+
--source include/stop_slave.inc
52+
SET @old_parallel_threads=@@GLOBAL.slave_parallel_threads;
53+
SET @old_parallel_mode=@@GLOBAL.slave_parallel_mode;
54+
SET GLOBAL slave_parallel_threads=2;
55+
SET GLOBAL slave_parallel_mode='optimistic';
56+
--source include/start_slave.inc
57+
58+
--source include/rpl_slave_max_statement_time.inc
59+
60+
--source include/stop_slave.inc
61+
SET GLOBAL slave_parallel_mode=@old_parallel_mode;
62+
SET GLOBAL slave_parallel_threads=@old_parallel_threads;
63+
--source include/start_slave.inc
64+
65+
66+
--echo #
67+
--echo # Test Case 3) Load-based log events (from LOAD DATA INFILE) should time
68+
--echo # out if their execution time exceeds @@slave_max_statement_time
69+
--echo #
70+
--let $use_load_data= 1
71+
--source include/rpl_slave_max_statement_time.inc
72+
--let $use_load_data=
73+
74+
75+
--echo #
76+
--echo # Test Case 4) Locally executed long running statements should not time
77+
--echo # out due to @@slave_max_statement_time
78+
--echo #
79+
80+
--connection slave
81+
--source include/stop_slave.inc
82+
SET @old_slave_max_statement_time=@@GLOBAL.slave_max_statement_time;
83+
SET @old_gtid_domain_id=@@GLOBAL.gtid_domain_id;
84+
SET @@GLOBAL.slave_max_statement_time=0.75;
85+
SET @@GLOBAL.gtid_domain_id=1;
86+
--source include/start_slave.inc
87+
88+
CREATE TABLE t2 (a int);
89+
SET STATEMENT sql_log_bin=0 FOR INSERT INTO t2 SELECT SLEEP(1);
90+
--let $t2_count= `SELECT COUNT(*) FROM t2`
91+
if ($t2_count != 1)
92+
{
93+
--die Local long running insert statement should have completed
94+
}
95+
DROP TABLE t2;
96+
97+
--source include/stop_slave.inc
98+
SET GLOBAL gtid_domain_id=@old_gtid_domain_id;
99+
SET GLOBAL slave_max_statement_time=@old_slave_max_statement_time;
100+
--source include/start_slave.inc
101+
102+
103+
--echo # Cleanup
104+
--source include/stop_slave.inc
105+
SET GLOBAL slave_max_statement_time=@save_slave_max_statement_time;
106+
--source include/start_slave.inc
107+
108+
--source include/rpl_end.inc
109+
110+
--echo # End of rpl_slave_max_statement_time.test

mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3722,6 +3722,16 @@ NUMERIC_BLOCK_SIZE 1024
37223722
ENUM_VALUE_LIST NULL
37233723
READ_ONLY NO
37243724
COMMAND_LINE_ARGUMENT REQUIRED
3725+
VARIABLE_NAME SLAVE_MAX_STATEMENT_TIME
3726+
VARIABLE_SCOPE GLOBAL
3727+
VARIABLE_TYPE DOUBLE
3728+
VARIABLE_COMMENT A query that has taken more than slave_max_statement_time seconds to run on the slave will be aborted. The argument will be treated as a decimal value with microsecond precision. A value of 0 (default) means no timeout
3729+
NUMERIC_MIN_VALUE 0
3730+
NUMERIC_MAX_VALUE 31536000
3731+
NUMERIC_BLOCK_SIZE NULL
3732+
ENUM_VALUE_LIST NULL
3733+
READ_ONLY NO
3734+
COMMAND_LINE_ARGUMENT REQUIRED
37253735
VARIABLE_NAME SLAVE_NET_TIMEOUT
37263736
VARIABLE_SCOPE GLOBAL
37273737
VARIABLE_TYPE INT UNSIGNED

0 commit comments

Comments
 (0)