Permalink
Browse files

BUG#16418100 ERROR "WHEN GTID_NEXT IS SET TO A GTID" ROW BASED REPLIC…

…ATION

Problem & Analysis:
===================
When relay log info repository is configured to be persisted in a
table, it is updated when transaction commits or explicit flush is called,
like on relay log rotate. If a transaction that uses non-transactional
engine is split across multiple relay logs, on relay log flush
it will be partially committed. If GTID_MODE=ON and when partial transaction
in first relay log is committed, it uses the current GTID value and sets
GTID value to UNDEFINED GROUP. Hence it causes 'ER_GTID_NEXT_TYPE_UNDEFINED_GROUP'
error when it is trying to commit the rest of the transaction.
It also causes many other problems if the repository is updated in between
the transaction and it should be avoided.

Fix: relay log info repository should be updated on relay log
rotate. But when the transaction is split across two relay logs,
update the repository will cause unexpected results and should
be postponed till the 'commit' of the transaction is executed.

Introduced a flag that set to 'true' when this type of 'forced flush'(at the
time of rotate relay log) is postponed due to transaction split
across the relay logs. And the flag will be checked in flush_info function
to see if there is a flush that got postponed earlier. If so, it will
force the flush now irrespective of sync_relay_log_info value.
  • Loading branch information...
Venkatesh Duggirala
Venkatesh Duggirala committed Aug 25, 2015
1 parent c285e65 commit 9b0e016889c73b8d9db1837f97e03f8ee20fbf19
@@ -252,12 +252,15 @@ if ($rpl_check_server_ids)
}
}
# $rpl_master_list must be set so that include/rpl_change_topology.inc
# knows which servers are initialized and not.
if (!`SELECT COUNT(*) = 0 OR VARIABLE_VALUE != 'ON' FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME = 'GTID_MODE'`)
if ($use_gtids == '')
{
--let $use_gtids=1
if (!`SELECT COUNT(*) = 0 OR VARIABLE_VALUE != 'ON' FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME = 'GTID_MODE'`)
{
--let $use_gtids=1
}
}
# $rpl_master_list must be set so that include/rpl_change_topology.inc
# knows which servers are initialized and not.
--let $rpl_master_list= `SELECT REPEAT('x', $rpl_server_count * LENGTH($rpl_server_count))`
--source include/rpl_change_topology.inc
@@ -0,0 +1,28 @@
include/rpl_init.inc [topology=1->2->3]
Warnings:
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
Warnings:
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
[connection server_1]
CREATE TABLE t1 (i VARCHAR(8192)) ENGINE=MyISAM;
INSERT INTO t1 VALUES(repeat('a',8192)), (repeat('b', 8192));
[connection server_2]
SET @saved_debug=@@GLOBAL.debug;
SET GLOBAL debug="d,stop_io_after_reading_write_rows_log_event";
START SLAVE IO_THREAD;
include/wait_for_slave_io_to_stop.inc
SET GLOBAL debug=@saved_debug;
START SLAVE;
[connection server_3]
START SLAVE;
[connection server_1]
include/rpl_sync.inc
include/diff_tables.inc [server_1:t1, server_2:t1, server_3:t1]
[connection server_2]
include/assert.inc [SQL thread position should be updated to after the split transaction]
include/assert.inc [SQL thread file should be updated to after the split transaction]
[connection server_1]
DROP TABLE t1;
include/rpl_end.inc
@@ -0,0 +1,16 @@
!include include/default_mysqld.cnf
[mysqld.1]
[mysqld.2]
log-slave-updates
relay-log-info-repository=table
[mysqld.3]
log-slave-updates
relay-log-info-repository=table
[ENV]
SERVER_MYPORT_1= @mysqld.1.port
SERVER_MYPORT_2= @mysqld.2.port
SERVER_MYPORT_3= @mysqld.3.port
@@ -0,0 +1,82 @@
###############################################################################
#BUG#16418100: ERROR "WHEN GTID_NEXT IS SET TO A GTID" ROW BASED REPLICATION
# Problem: When relay log info repository is configured to be persisted in a
# table and if a transaction that uses non-transactional engine is split across
# multiple relay logs, on relay log flush, partial transaction is
# committed. If the same situation occurs when gtid-mode=ON, it will cause that
# the same GTID will be used for more than one transaction and will cause
# ER_GTID_NEXT_TYPE_UNDEFINED_GROUP error.
#
# Steps to reproduce:
# 1) Generate two write_row events for non-transactional table (MyISAM)
# (Default binlog_row_max_event_size= 8192). Insert two tuples with tuple
# size as 8192 bytes each. This will generate two write_row events.
# 2) With Auto_position=0, stopping IO thread after receving one write_row
# event will split the transaction across two relay logs.
# 3) If relaylog_info_repository=table, SQL thread should be able to apply
# these type of relaylogs.
###############################################################################
# Problem happens only in gtid mode
--source include/have_gtid.inc
# Run only in row format
--source include/have_binlog_format_row.inc
# Disable auto position protocol
--let use_gtids=0
--let $rpl_skip_start_slave= 1
--let $rpl_topology= 1->2->3
--source include/rpl_init.inc
--let $rpl_connection_name= server_1
--source include/rpl_connection.inc
# Non-transactional table (MyISAM engine)
CREATE TABLE t1 (i VARCHAR(8192)) ENGINE=MyISAM;
# Two tuples that will generate two write_row events.
INSERT INTO t1 VALUES(repeat('a',8192)), (repeat('b', 8192));
--let $rpl_connection_name= server_2
--source include/rpl_connection.inc
# Set simulation point to restart IO thread, so that the transaction
# is split across the relay logs.
SET @saved_debug=@@GLOBAL.debug;
SET GLOBAL debug="d,stop_io_after_reading_write_rows_log_event";
START SLAVE IO_THREAD;
--source include/wait_for_slave_io_to_stop.inc
SET GLOBAL debug=@saved_debug;
# Make sure there are no replication issues
--disable_warnings
START SLAVE;
--enable_warnings
--let $rpl_connection_name= server_3
--source include/rpl_connection.inc
--disable_warnings
START SLAVE;
--enable_warnings
--let $rpl_connection_name= server_1
--source include/rpl_connection.inc
--source include/rpl_sync.inc
--let $diff_tables= server_1:t1, server_2:t1, server_3:t1
--source include/diff_tables.inc
# Assert that relay log info pos is stored on table
--let $rpl_connection_name= server_2
--source include/rpl_connection.inc
--let $sql_thread_pos=query_get_value('SHOW SLAVE STATUS', Exec_Master_Log_Pos, 1)
--let $sql_thread_file=query_get_value('SHOW SLAVE STATUS', Relay_Master_Log_File, 1)
--let $assert_cond= Master_log_pos = $sql_thread_pos FROM mysql.slave_relay_log_info
--let $assert_text= SQL thread position should be updated to after the split transaction
--source include/assert.inc
--let $assert_cond= Master_log_name = "$sql_thread_file" FROM mysql.slave_relay_log_info
--let $assert_text= SQL thread file should be updated to after the split transaction
--source include/assert.inc
# Cleanup
--let $rpl_connection_name= server_1
--source include/rpl_connection.inc
DROP TABLE t1;
--source include/rpl_end.inc
View
@@ -4098,9 +4098,17 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included)
rli->set_group_relay_log_name(rli->linfo.log_file_name);
rli->notify_group_relay_log_name_update();
}
/* Store where we are in the new file for the execution thread */
rli->flush_info(TRUE);
/*
Store where we are in the new file for the execution thread.
If we are in the middle of a group), then we should not store
the position in the repository, instead in that case set a flag
to true which indicates that a 'forced flush' is postponed due
to transaction split across the relaylogs.
*/
if (!rli->is_in_group())
rli->flush_info(TRUE);
else
rli->force_flush_postponed_due_to_split_trans= true;
DBUG_EXECUTE_IF("crash_before_purge_logs", DBUG_SUICIDE(););
View
@@ -141,6 +141,7 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery
relay_log.init_pthread_objects();
do_server_version_split(::server_version, slave_version_split);
last_retrieved_gtid.clear();
force_flush_postponed_due_to_split_trans= false;
DBUG_VOID_RETURN;
}
@@ -2033,9 +2034,10 @@ int Relay_log_info::flush_info(const bool force)
if (write_info(handler))
goto err;
if (handler->flush_info(force))
if (handler->flush_info(force || force_flush_postponed_due_to_split_trans))
goto err;
force_flush_postponed_due_to_split_trans= false;
DBUG_RETURN(0);
err:
View
@@ -902,6 +902,17 @@ class Relay_log_info : public Rpl_info
*/
void adapt_to_master_version(Format_description_log_event *fdle);
uchar slave_version_split[3]; // bytes of the slave server version
/*
relay log info repository should be updated on relay log
rotate. But when the transaction is split across two relay logs,
update the repository will cause unexpected results and should
be postponed till the 'commit' of the transaction is executed.
A flag that set to 'true' when this type of 'forced flush'(at the
time of rotate relay log) is postponed due to transaction split
across the relay logs.
*/
bool force_flush_postponed_due_to_split_trans;
protected:
Format_description_log_event *rli_description_event;

0 comments on commit 9b0e016

Please sign in to comment.