Skip to content

Commit

Permalink
MDEV-20511: Galera replication of events is not consistent
Browse files Browse the repository at this point in the history
After SST from master node (the one where event is ENABLED) - you will end up with the event enabled on two nodes, hence it's now being executed twice. It can be solved by comparing event's originator with server_id. if not equal, then change its status to 'SLAVESIDE_DISABLED'

Changes to be committed:
	new file:   mysql-test/suite/galera/r/galera_events2.result
	new file:   mysql-test/suite/galera/t/galera_events2.test
	modified:   sql/events.cc
  • Loading branch information
Jan Lindström committed Sep 9, 2019
1 parent efbfded commit 0fd5b11
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 0 deletions.
122 changes: 122 additions & 0 deletions mysql-test/suite/galera/r/galera_events2.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
connection node_1;
connection node_2;
connection node_1;
CREATE TABLE event_table(a int) engine=innodb;
CREATE EVENT event_2 ON SCHEDULE EVERY 1 SECOND
ENDS NOW() + INTERVAL 6 SECOND
ON COMPLETION NOT PRESERVE
DO
INSERT INTO event_table VALUES (1);
Warnings:
Warning 1105 Event scheduler is switched off, use SET GLOBAL event_scheduler=ON to enable it.
# node_1 event should be there
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def test event_2 root@localhost SQL INSERT INTO event_table VALUES (1) RECURRING NULL 1 SECOND ENABLED NOT PRESERVE
connection node_2;
# node_2 event should be there
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def test event_2 root@localhost SQL INSERT INTO event_table VALUES (1) RECURRING NULL 1 SECOND SLAVESIDE_DISABLED NOT PRESERVE
connection node_1;
SET GLOBAL event_scheduler=ON;
SHOW VARIABLES LIKE 'event_scheduler';
Variable_name Value
event_scheduler ON
# node_1 event should be removed
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
connection node_2;
# node_2 event should be removed
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
connection node_1;
SET GLOBAL event_scheduler=OFF;
DROP TABLE event_table;
connection node_1;
CREATE DATABASE IF NOT EXISTS events_test;
use events_test;
CREATE USER ev_test@localhost;
GRANT ALL ON events_test.* to ev_test@localhost;
connect ev_con1,localhost,ev_test,,events_test;
CREATE EVENT one_event ON SCHEDULE EVERY 10 SECOND DO SELECT 123;
Warnings:
Warning 1105 Event scheduler is switched off, use SET GLOBAL event_scheduler=ON to enable it.
SHOW EVENTS;
Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation
events_test one_event ev_test@localhost SYSTEM RECURRING NULL 10 # # NULL ENABLED 1 latin1 latin1_swedish_ci latin1_swedish_ci
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS ORDER BY EVENT_SCHEMA, EVENT_NAME;
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def events_test one_event ev_test@localhost SQL SELECT 123 RECURRING NULL 10 SECOND ENABLED NOT PRESERVE
ALTER EVENT one_event ON SCHEDULE EVERY 10 SECOND;
"The definer should be ev_test@localhost"
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def events_test one_event ev_test@localhost SQL SELECT 123 RECURRING NULL 10 SECOND ENABLED NOT PRESERVE
connection node_2;
use events_test;
"The definer should be ev_test@localhost"
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def events_test one_event ev_test@localhost SQL SELECT 123 RECURRING NULL 10 SECOND SLAVESIDE_DISABLED NOT PRESERVE
connection node_1;
disconnect ev_con1;
use test;
DROP EVENT events_test.one_event;
DROP USER ev_test@localhost;
DROP DATABASE events_test;
connection node_1;
use test;
CREATE EVENT one_event ON SCHEDULE EVERY 10 SECOND DO SELECT 123;
Warnings:
Warning 1105 Event scheduler is switched off, use SET GLOBAL event_scheduler=ON to enable it.
# node_1 Event should be enabled
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def test one_event root@localhost SQL SELECT 123 RECURRING NULL 10 SECOND ENABLED NOT PRESERVE
connection node_2;
use test;
# node_2 Event should be SERVERSIDE_DISABLED
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def test one_event root@localhost SQL SELECT 123 RECURRING NULL 10 SECOND SLAVESIDE_DISABLED NOT PRESERVE
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY, f2 CHAR(1)) engine=InnoDB;
INSERT INTO t1 VALUES (1, 'a'), (2, 'a'), (3, 'a');
SELECT * FROM t1;
f1 f2
1 a
2 a
3 a
Shutting down server ...
connection node_1;
UPDATE t1 SET f2 = 'b' WHERE f1 > 1;
UPDATE t1 SET f2 = 'c' WHERE f1 > 2;
SELECT * FROM t1;
f1 f2
1 a
2 b
3 c
connection node_2;
# Force SST from node_1 to node_2
Starting server ...
SELECT * FROM t1;
f1 f2
1 a
2 b
3 c
# node_2 Event should be SERVERSIDE_DISABLED
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def test one_event root@localhost SQL SELECT 123 RECURRING NULL 10 SECOND SLAVESIDE_DISABLED NOT PRESERVE
connection node_1;
SELECT * FROM t1;
f1 f2
1 a
2 b
3 c
# node_1 Event should be ENABLED
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';
EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_DEFINITION EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT
def test one_event root@localhost SQL SELECT 123 RECURRING NULL 10 SECOND ENABLED NOT PRESERVE
DROP TABLE t1;
DROP EVENT one_event;
144 changes: 144 additions & 0 deletions mysql-test/suite/galera/t/galera_events2.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
--source include/galera_cluster.inc

# Save original auto_increment_offset values.
--let $node_1=node_1
--let $node_2=node_2
--source include/auto_increment_offset_save.inc

#
# Test case 1: "ONE TIME" events should be dropped on slave nodes after expiring on master (event creator node)
#

--connection node_1

CREATE TABLE event_table(a int) engine=innodb;

CREATE EVENT event_2 ON SCHEDULE EVERY 1 SECOND
ENDS NOW() + INTERVAL 6 SECOND
ON COMPLETION NOT PRESERVE
DO
INSERT INTO event_table VALUES (1);

--echo # node_1 event should be there
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';

--connection node_2
--echo # node_2 event should be there
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';

--connection node_1
SET GLOBAL event_scheduler=ON;
SHOW VARIABLES LIKE 'event_scheduler';

# Let event_2 reach the end of its execution interval
let $wait_condition=select count(*) = 0 from information_schema.events where event_name='event_2';
--source include/wait_condition.inc

--echo # node_1 event should be removed
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';

--connection node_2
--echo # node_2 event should be removed
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='event_2';

--connection node_1
SET GLOBAL event_scheduler=OFF;
DROP TABLE event_table;

#
# Test case 2: After doing ALTER EVENT, slave nodes should have same definer as master
#

--connection node_1

CREATE DATABASE IF NOT EXISTS events_test;
use events_test;
CREATE USER ev_test@localhost;
GRANT ALL ON events_test.* to ev_test@localhost;
connect (ev_con1,localhost,ev_test,,events_test);

CREATE EVENT one_event ON SCHEDULE EVERY 10 SECOND DO SELECT 123;
--replace_column 8 # 9 #
SHOW EVENTS;
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS ORDER BY EVENT_SCHEMA, EVENT_NAME;
ALTER EVENT one_event ON SCHEDULE EVERY 10 SECOND;
--echo "The definer should be ev_test@localhost"
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';

--connection node_2
use events_test;
--echo "The definer should be ev_test@localhost"
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';

--connection node_1
--disconnect ev_con1
use test;
DROP EVENT events_test.one_event;
DROP USER ev_test@localhost;
DROP DATABASE events_test;

#
# Test case 3: After SST from master node (the one where event is ENABLED) , slave event status should be 'SLAVESIDE_DISABLED'
#

--connection node_1
use test;
CREATE EVENT one_event ON SCHEDULE EVERY 10 SECOND DO SELECT 123;

--echo # node_1 Event should be enabled
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';

--connection node_2
use test;
--echo # node_2 Event should be SERVERSIDE_DISABLED
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';

CREATE TABLE t1 (f1 INTEGER PRIMARY KEY, f2 CHAR(1)) engine=InnoDB;
INSERT INTO t1 VALUES (1, 'a'), (2, 'a'), (3, 'a');

SELECT * FROM t1;

# Initiate normal shutdown on the node 2 and wait until shutdown has been completed:

--echo Shutting down server ...
--source include/shutdown_mysqld.inc

--connection node_1

--let $wait_condition = SELECT VARIABLE_VALUE = 1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'
--source include/wait_condition.inc

UPDATE t1 SET f2 = 'b' WHERE f1 > 1;
UPDATE t1 SET f2 = 'c' WHERE f1 > 2;

SELECT * FROM t1;

--connection node_2

# Remove the "grastate.dat" file (to initiate new SST) and restart node 2

--remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat

--echo # Force SST from node_1 to node_2
--let $start_mysqld_params=
--echo Starting server ...
--source include/start_mysqld.inc

--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'
--source include/wait_condition.inc

# Sanity check (node 2 is running now and can perform SQL operators):

SELECT * FROM t1;
--echo # node_2 Event should be SERVERSIDE_DISABLED
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';

--connection node_1
SELECT * FROM t1;
--echo # node_1 Event should be ENABLED
SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_DEFINITION, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME='one_event';

DROP TABLE t1;
DROP EVENT one_event;

--source include/auto_increment_offset_restore.inc
40 changes: 40 additions & 0 deletions sql/events.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,46 @@ Events::load_events_from_db(THD *thd)
delete et;
goto end;
}

#ifdef WITH_WSREP
/**
IF SST is done from a galera node that is also acting as MASTER
newly synced node in galera eco-system will also copy-over the event state
enabling duplicate event in galera eco-system.
DISABLE such events if the current node is not event orginator.
(Also, make sure you skip disabling it if is already disabled to avoid
creation of redundant action)
NOTE:
This complete system relies on server-id. Ideally server-id should be
same for all nodes of galera eco-system but they aren't same.
Infact, based on galera use-case it seems like it recommends to have each
node with different server-id.
*/
if (et->originator != thd->variables.server_id)
{
if (et->status == Event_parse_data::SLAVESIDE_DISABLED)
continue;

store_record(table, record[1]);
table->field[ET_FIELD_STATUS]->
store((longlong) Event_parse_data::SLAVESIDE_DISABLED,
TRUE);

/* All the dmls to mysql.events tables are stmt bin-logged. */
bool save_binlog_row_based;
if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
thd->set_current_stmt_binlog_format_stmt();

(void) table->file->ha_update_row(table->record[1], table->record[0]);

if (save_binlog_row_based)
thd->set_current_stmt_binlog_format_row();

delete et;
continue;
}
#endif /* WITH_WSREP */

/**
Since the Event_queue_element object could be deleted inside
Event_queue::create_event we should save the value of dropped flag
Expand Down

0 comments on commit 0fd5b11

Please sign in to comment.