Commit bdbce30d authored by unknown's avatar unknown

MDEV-26: Global transaction id: Intermediate commit.

Now slave can connect to master, sending start position as slave state
rather than old-style binlog name/position.

This enables to switch to a new master by changing just connection
information, replication slave GTID state ensures that slave starts
at the correct point in the new master.
parent 3e798b03
...@@ -20,6 +20,12 @@ ...@@ -20,6 +20,12 @@
-- --
set sql_mode=''; set sql_mode='';
-- We want this to be created with the default storage engine.
-- This way, if InnoDB is used we get crash safety, and if MyISAM is used
-- we avoid mixed-engine transactions.
CREATE TABLE IF NOT EXISTS rpl_slave_state (domain_id INT UNSIGNED NOT NULL, sub_id BIGINT UNSIGNED NOT NULL, server_id INT UNSIGNED NOT NULL, seq_no BIGINT UNSIGNED NOT NULL, PRIMARY KEY (domain_id, sub_id)) comment='Replication slave GTID state';
set storage_engine=myisam; set storage_engine=myisam;
flush tables; flush tables;
......
...@@ -3270,9 +3270,12 @@ bool MYSQL_BIN_LOG::open(const char *log_name, ...@@ -3270,9 +3270,12 @@ bool MYSQL_BIN_LOG::open(const char *log_name,
there had been an entry (domain_id, server_id, 0). there had been an entry (domain_id, server_id, 0).
*/ */
Gtid_list_log_event gl_ev(&rpl_global_gtid_binlog_state); if (rpl_global_gtid_binlog_state.count())
if (gl_ev.write(&log_file)) {
goto err; Gtid_list_log_event gl_ev(&rpl_global_gtid_binlog_state);
if (gl_ev.write(&log_file))
goto err;
}
/* Output a binlog checkpoint event at the start of the binlog file. */ /* Output a binlog checkpoint event at the start of the binlog file. */
......
...@@ -6279,6 +6279,67 @@ rpl_slave_state::next_subid(uint32 domain_id) ...@@ -6279,6 +6279,67 @@ rpl_slave_state::next_subid(uint32 domain_id)
#endif #endif
/*
Prepare the current slave state as a string, suitable for sending to the
master to request to receive binlog events starting from that GTID state.
The state consists of the most recently applied GTID for each domain_id,
ie. the one with the highest sub_id within each domain_id.
*/
int
rpl_slave_state::tostring(String *dest)
{
bool first= true;
uint32 i;
int err= 1;
lock();
for (i= 0; i < hash.records; ++i)
{
uint64 best_sub_id;
rpl_gtid best_gtid;
element *e= (element *)my_hash_element(&hash, i);
list_element *l= e->list;
DBUG_ASSERT(l /* We should never have empty list in element. */);
if (!l)
goto err;
best_gtid.domain_id= e->domain_id;
best_gtid.server_id= l->server_id;
best_gtid.seq_no= l->seq_no;
best_sub_id= l->sub_id;
while ((l= l->next))
{
if (l->sub_id > best_sub_id)
{
best_sub_id= l->sub_id;
best_gtid.server_id= l->server_id;
best_gtid.seq_no= l->seq_no;
}
}
if (first)
first= false;
else
dest->append("-",1);
dest->append_ulonglong(best_gtid.domain_id);
dest->append("-",1);
dest->append_ulonglong(best_gtid.server_id);
dest->append("-",1);
dest->append_ulonglong(best_gtid.seq_no);
}
err= 0;
err:
unlock();
return err;
}
rpl_binlog_state::rpl_binlog_state() rpl_binlog_state::rpl_binlog_state()
{ {
my_hash_init(&hash, &my_charset_bin, 32, my_hash_init(&hash, &my_charset_bin, 32,
...@@ -6410,6 +6471,119 @@ rpl_binlog_state::read_from_iocache(IO_CACHE *src) ...@@ -6410,6 +6471,119 @@ rpl_binlog_state::read_from_iocache(IO_CACHE *src)
} }
return 0; return 0;
} }
slave_connection_state::slave_connection_state()
{
my_hash_init(&hash, &my_charset_bin, 32,
offsetof(rpl_gtid, domain_id), sizeof(uint32), NULL, my_free,
HASH_UNIQUE);
}
slave_connection_state::~slave_connection_state()
{
my_hash_free(&hash);
}
/*
Create a hash from the slave GTID state that is sent to master when slave
connects to start replication.
The state is sent as <GTID>,<GTID>,...,<GTID>, for example:
0-2-112,1-4-1022
The state gives for each domain_id the GTID to start replication from for
the corresponding replication stream. So domain_id must be unique.
Returns 0 if ok, non-zero if error due to malformed input.
Note that input string is built by slave server, so it will not be incorrect
unless bug/corruption/malicious server. So we just need basic sanity check,
not fancy user-friendly error message.
*/
int
slave_connection_state::load(char *slave_request, size_t len)
{
char *p, *q, *end;
uint64 v;
uint32 domain_id, server_id;
uint64 seq_no;
uchar *rec;
rpl_gtid *gtid;
int err= 0;
my_hash_reset(&hash);
p= slave_request;
end= slave_request + len;
for (;;)
{
q= end;
v= (uint64)my_strtoll10(p, &q, &err);
if (err != 0 || v > (uint32)0xffffffff || *q != '-')
return 1;
domain_id= (uint32)v;
p= q+1;
q= end;
v= (uint64)my_strtoll10(p, &q, &err);
if (err != 0 || v > (uint32)0xffffffff || *q != '-')
return 1;
server_id= (uint32)v;
p= q+1;
q= end;
seq_no= (uint64)my_strtoll10(p, &q, &err);
if (err != 0)
return 1;
if (!(rec= (uchar *)my_malloc(sizeof(*gtid), MYF(MY_WME))))
return 1;
gtid= (rpl_gtid *)rec;
gtid->domain_id= domain_id;
gtid->server_id= server_id;
gtid->seq_no= seq_no;
if (my_hash_insert(&hash, rec))
{
my_free(rec);
return 1;
}
if (q == end)
break; /* Finished. */
if (*q != ',')
return 1;
p= q+1;
}
return 0;
}
rpl_gtid *
slave_connection_state::find(uint32 domain_id)
{
return (rpl_gtid *) my_hash_search(&hash, (const uchar *)(&domain_id), 0);
}
void
slave_connection_state::remove(const rpl_gtid *in_gtid)
{
bool err;
uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0);
#ifndef DBUG_OFF
rpl_gtid *slave_gtid= (rpl_gtid *)rec;
DBUG_ASSERT(rec /* We should never try to remove not present domain_id. */);
DBUG_ASSERT(slave_gtid->server_id == in_gtid->server_id);
DBUG_ASSERT(slave_gtid->seq_no == in_gtid->seq_no);
#endif
err= my_hash_delete(&hash, rec);
DBUG_ASSERT(!err);
}
#endif /* MYSQL_SERVER */ #endif /* MYSQL_SERVER */
...@@ -6443,6 +6617,28 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg, ...@@ -6443,6 +6617,28 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg,
{ {
} }
/*
Used to record GTID while sending binlog to slave, without having to
fully contruct every Gtid_log_event() needlessly.
*/
bool
Gtid_log_event::peek(const char *event_start, size_t event_len,
uint32 *domain_id, uint32 *server_id, uint64 *seq_no,
uchar *flags2)
{
const char *p;
if (event_len < LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN)
return true;
*server_id= uint4korr(event_start + SERVER_ID_OFFSET);
p= event_start + LOG_EVENT_HEADER_LEN;
*seq_no= uint8korr(p);
p+= 8;
*domain_id= uint4korr(p);
return false;
}
bool bool
Gtid_log_event::write(IO_CACHE *file) Gtid_log_event::write(IO_CACHE *file)
{ {
......
...@@ -3008,6 +3008,7 @@ struct rpl_slave_state ...@@ -3008,6 +3008,7 @@ struct rpl_slave_state
int record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, int record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
bool in_transaction); bool in_transaction);
uint64 next_subid(uint32 domain_id); uint64 next_subid(uint32 domain_id);
int tostring(String *dest);
void lock() { DBUG_ASSERT(inited); mysql_mutex_lock(&LOCK_slave_state); } void lock() { DBUG_ASSERT(inited); mysql_mutex_lock(&LOCK_slave_state); }
void unlock() { DBUG_ASSERT(inited); mysql_mutex_unlock(&LOCK_slave_state); } void unlock() { DBUG_ASSERT(inited); mysql_mutex_unlock(&LOCK_slave_state); }
...@@ -3044,6 +3045,26 @@ struct rpl_binlog_state ...@@ -3044,6 +3045,26 @@ struct rpl_binlog_state
int read_from_iocache(IO_CACHE *src); int read_from_iocache(IO_CACHE *src);
}; };
/*
Represent the GTID state that a slave connection to a master requests
the master to start sending binlog events from.
*/
struct slave_connection_state
{
/* Mapping from domain_id to the GTID requested for that domain. */
HASH hash;
slave_connection_state();
~slave_connection_state();
int load(char *slave_request, size_t len);
rpl_gtid *find(uint32 domain_id);
void remove(const rpl_gtid *gtid);
ulong count() const { return hash.records; }
};
/** /**
@class Gtid_log_event @class Gtid_log_event
...@@ -3134,6 +3155,9 @@ public: ...@@ -3134,6 +3155,9 @@ public:
bool write(IO_CACHE *file); bool write(IO_CACHE *file);
static int make_compatible_event(String *packet, bool *need_dummy_event, static int make_compatible_event(String *packet, bool *need_dummy_event,
ulong ev_offset, uint8 checksum_alg); ulong ev_offset, uint8 checksum_alg);
static bool peek(const char *event_start, size_t event_len,
uint32 *domain_id, uint32 *server_id, uint64 *seq_no,
uchar *flags2);
#endif #endif
}; };
......
...@@ -1784,6 +1784,42 @@ past_checksum: ...@@ -1784,6 +1784,42 @@ past_checksum:
after_set_capability: after_set_capability:
#endif #endif
/* Request dump start from slave replication GTID state. */
/* ToDo: This needs to be configurable somehow in a useful way ... */
if (rpl_global_gtid_slave_state.count())
{
int rc;
char str_buf[256];
String connect_state(str_buf, sizeof(str_buf), system_charset_info);
connect_state.length(0);
connect_state.append(STRING_WITH_LEN("SET @slave_connect_state='"),
system_charset_info);
rpl_global_gtid_slave_state.tostring(&connect_state);
connect_state.append(STRING_WITH_LEN("'"), system_charset_info);
rc= mysql_real_query(mysql, connect_state.ptr(), connect_state.length());
if (rc)
{
err_code= mysql_errno(mysql);
if (is_network_error(err_code))
{
mi->report(ERROR_LEVEL, err_code,
"Setting @slave_connect_state failed with error: %s",
mysql_error(mysql));
goto network_err;
}
else
{
/* Fatal error */
errmsg= "The slave I/O thread stops because a fatal error is "
"encountered when it tries to set @slave_connect_state.";
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
goto err;
}
}
}
err: err:
if (errmsg) if (errmsg)
{ {
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment