MXS-1744 Implement a Gtid-class which can store multi-domain gtid:s

The operations between Gtid:s are now more complicated so the class implements
them instead of the monitor. The Old Gtid-container has been renamed
GtidTriplet, and only stores the values for one triplet.
This commit is contained in:
Esa Korhonen
2018-03-29 10:49:49 +03:00
parent 8596fea62a
commit aaa8c92886
6 changed files with 314 additions and 31 deletions

View File

@ -811,7 +811,7 @@ bool MariaDBMonitor::failover_wait_relay_log(MXS_MONITORED_SERVER* new_master, i
new_master->server->unique_name, master_info->relay_log_events());
thread_millisleep(1000); // Sleep for a while before querying server again.
// Todo: check server version before entering failover.
Gtid old_gtid_io_pos = master_info->slave_status.gtid_io_pos;
GtidTriplet old_gtid_io_pos = master_info->slave_status.gtid_io_pos;
// Update gtid:s first to make sure Gtid_IO_Pos is the more recent value.
// It doesn't matter here, but is a general rule.
query_ok = master_info->update_gtids(m_master_gtid_domain) &&
@ -962,7 +962,7 @@ bool MariaDBMonitor::switchover_demote_master(MXS_MONITORED_SERVER* current_mast
* @param err_out json object for error printing. Can be NULL.
* @return True, if target gtid was reached within allotted time for all servers
*/
bool MariaDBMonitor::switchover_wait_slaves_catchup(const ServerVector& slaves, const Gtid& gtid,
bool MariaDBMonitor::switchover_wait_slaves_catchup(const ServerVector& slaves, const GtidTriplet& gtid,
int total_timeout, int read_timeout, json_t** err_out)
{
bool success = true;
@ -1003,7 +1003,7 @@ bool MariaDBMonitor::switchover_wait_slaves_catchup(const ServerVector& slaves,
* @param err_out json object for error printing. Can be NULL.
* @return True, if target gtid was reached within allotted time
*/
bool MariaDBMonitor::switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const Gtid& gtid,
bool MariaDBMonitor::switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const GtidTriplet& gtid,
int total_timeout, int read_timeout,
json_t** err_out)
{
@ -1076,7 +1076,7 @@ bool MariaDBMonitor::wait_cluster_stabilization(MXS_MONITORED_SERVER* new_master
int query_fails = 0;
int repl_fails = 0;
int successes = 0;
const Gtid target = new_master_info->gtid_current_pos;
const GtidTriplet target = new_master_info->gtid_current_pos;
ServerVector wait_list = slaves; // Check all the servers in the list
bool first_round = true;
bool time_is_up = false;
@ -1498,8 +1498,8 @@ bool MariaDBMonitor::can_replicate_from(MXS_MONITORED_SERVER* slave,
bool rval = false;
if (slave_info->update_gtids(m_master_gtid_domain))
{
Gtid slave_gtid = slave_info->gtid_current_pos;
Gtid master_gtid = master_info->gtid_binlog_pos;
GtidTriplet slave_gtid = slave_info->gtid_current_pos;
GtidTriplet master_gtid = master_info->gtid_binlog_pos;
// The following are not sufficient requirements for replication to work, they only cover the basics.
// If the servers have diverging histories, the redirection will seem to succeed but the slave IO
// thread will stop in error.

View File

@ -197,9 +197,9 @@ private:
bool failover_wait_relay_log(MXS_MONITORED_SERVER* new_master, int seconds_remaining, json_t** err_out);
bool switchover_demote_master(MXS_MONITORED_SERVER* current_master, MariaDBServer* info,
json_t** err_out);
bool switchover_wait_slaves_catchup(const ServerVector& slaves, const Gtid& gtid, int total_timeout,
bool switchover_wait_slaves_catchup(const ServerVector& slaves, const GtidTriplet& gtid, int total_timeout,
int read_timeout, json_t** err_out);
bool switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const Gtid& gtid,
bool switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const GtidTriplet& gtid,
int total_timeout, int read_timeout, json_t** err_out);
bool wait_cluster_stabilization(MXS_MONITORED_SERVER* new_master, const ServerVector& slaves,
int seconds_remaining);

View File

@ -203,11 +203,12 @@ bool MariaDBServer::do_show_slave_status(int64_t gtid_domain)
{
string gtid_io_pos = result->get_string(i_gtid_io_pos);
slave_status.gtid_io_pos = !gtid_io_pos.empty() ?
Gtid(gtid_io_pos.c_str(), gtid_domain) : Gtid();
GtidTriplet(gtid_io_pos.c_str(), gtid_domain) :
GtidTriplet();
}
else
{
slave_status.gtid_io_pos = Gtid();
slave_status.gtid_io_pos = GtidTriplet();
}
}
}

View File

@ -47,7 +47,7 @@ public:
* reading from. */
uint64_t read_master_log_pos; /**< Position up to which the I/O thread has read in the current master
* binary log file. */
Gtid gtid_io_pos; /**< Gtid I/O position of the slave thread. Only shows the triplet with
GtidTriplet gtid_io_pos; /**< Gtid I/O position of the slave thread. Only shows the triplet with
* the current master domain. */
std::string last_error; /**< Last IO or SQL error encountered. */
@ -92,9 +92,9 @@ public:
time_t latest_event; /**< Time when latest event was received from the master */
int64_t gtid_domain_id; /**< The value of gtid_domain_id, the domain which is used for
* new non-replicated events. */
Gtid gtid_current_pos; /**< Gtid of latest event. Only shows the triplet
GtidTriplet gtid_current_pos; /**< Gtid of latest event. Only shows the triplet
* with the current master domain. */
Gtid gtid_binlog_pos; /**< Gtid of latest event written to binlog. Only shows
GtidTriplet gtid_binlog_pos; /**< Gtid of latest event written to binlog. Only shows
* the triplet with the current master domain. */
SlaveStatusInfo slave_status; /**< Data returned from SHOW SLAVE STATUS */
ReplicationSettings rpl_settings; /**< Miscellaneous replication related settings */

View File

@ -13,6 +13,7 @@
#include "utilities.hh"
#include <algorithm>
#include <inttypes.h>
#include <limits>
#include <stdio.h>
@ -201,25 +202,204 @@ bool QueryResult::get_bool(int64_t column_ind) const
return data ? (strcmp(data,"Y") == 0 || strcmp(data, "1") == 0) : false;
}
Gtid QueryResult::get_gtid(int64_t column_ind, int64_t gtid_domain) const
GtidTriplet QueryResult::get_gtid(int64_t column_ind, int64_t gtid_domain) const
{
ss_dassert(column_ind < m_columns);
char* data = m_rowdata[column_ind];
Gtid rval;
GtidTriplet rval;
if (data && *data)
{
rval = Gtid(data, gtid_domain);
rval = GtidTriplet(data, gtid_domain);
}
return rval;
}
Gtid::Gtid()
Gtid Gtid::from_string(const std::string& gtid_string)
{
ss_dassert(gtid_string.size());
Gtid rval;
bool error = false;
bool have_more = false;
const char* str = gtid_string.c_str();
do
{
char* endptr = NULL;
auto new_triplet = GtidTriplet::parse_one_triplet(str, &endptr);
if (new_triplet.server_id == SERVER_ID_UNKNOWN)
{
error = true;
}
else
{
rval.m_triplets.push_back(new_triplet);
// The last number must be followed by ',' (another triplet) or \0 (last triplet)
if (*endptr == ',')
{
have_more = true;
str = endptr + 1;
}
else if (*endptr == '\0')
{
have_more = false;
}
else
{
error = true;
}
}
} while (have_more && !error);
if (error)
{
// If error occurred, clear the gtid as something is very wrong.
rval.m_triplets.clear();
}
else
{
// Usually the servers gives the triplets ordered by domain id:s, but this is not 100%.
std::sort(rval.m_triplets.begin(), rval.m_triplets.end(), GtidTriplet::compare_domains);
}
return rval;
}
string Gtid::to_string() const
{
string rval;
string separator;
for (auto iter = m_triplets.begin(); iter != m_triplets.end(); iter++)
{
rval += separator + iter->to_string();
separator = ",";
}
return rval;
}
bool Gtid::can_replicate_from(const Gtid& master_gtid)
{
/* The result of this function is false if the source and master have a common domain id where
* the source is ahead of the master. */
return (events_ahead(*this, master_gtid, MISSING_DOMAIN_IGNORE) == 0);
}
bool Gtid::empty() const
{
return m_triplets.empty();
}
bool Gtid::operator == (const Gtid& rhs) const
{
return m_triplets == rhs.m_triplets;
}
uint64_t Gtid::events_ahead(const Gtid& lhs, const Gtid& rhs, substraction_mode_t domain_substraction_mode)
{
const size_t n_lhs = lhs.m_triplets.size();
const size_t n_rhs = rhs.m_triplets.size();
size_t ind_lhs = 0, ind_rhs = 0;
uint64_t events = 0;
while (ind_lhs < n_lhs && ind_rhs < n_rhs)
{
auto lhs_triplet = lhs.m_triplets[ind_lhs];
auto rhs_triplet = rhs.m_triplets[ind_rhs];
// Server id -1 should never be saved in a real gtid variable.
ss_dassert(lhs_triplet.server_id != SERVER_ID_UNKNOWN &&
rhs_triplet.server_id != SERVER_ID_UNKNOWN);
// Search for matching domain_id:s, advance the smaller one.
if (lhs_triplet.domain < rhs_triplet.domain)
{
if (domain_substraction_mode == MISSING_DOMAIN_LHS_ADD)
{
// The domain on lhs does not exist on rhs. Add entire sequence number of lhs to the result.
events += lhs_triplet.sequence;
}
ind_lhs++;
}
else if (lhs_triplet.domain > rhs_triplet.domain)
{
ind_rhs++;
}
else
{
// Domains match, check sequences.
if (lhs_triplet.sequence > rhs_triplet.sequence)
{
/* Same domains, but lhs sequence is equal or ahead of rhs sequence. */
events += lhs_triplet.sequence - rhs_triplet.sequence;
}
// Continue to next domains.
ind_lhs++;
ind_rhs++;
}
}
return events;
}
GtidTriplet GtidTriplet::parse_one_triplet(const char* str, char** endptr)
{
/* Error checking the gtid string is a bit questionable, as having an error means that the server is
buggy or network has faults, in which case nothing can be trusted. But without error checking
MaxScale may crash if string is wrong. */
ss_dassert(endptr);
const char* ptr = str;
char* strtoull_endptr = NULL;
// Parse three numbers separated by -
uint64_t parsed_numbers[3];
bool error = false;
for (int i = 0; i < 3 && !error; i++)
{
errno = 0;
parsed_numbers[i] = strtoull(ptr, &strtoull_endptr, 10);
// No parse error
if (errno != 0)
{
error = true;
}
else if (i < 2)
{
// First two numbers must be followed by a -
if (*strtoull_endptr == '-')
{
ptr = strtoull_endptr + 1;
}
else
{
error = true;
}
}
}
// Check that none of the parsed numbers are unexpectedly large. This shouldn't really be possible unless
// server has a bug or network had an error.
if (!error && (parsed_numbers[0] > UINT32_MAX || parsed_numbers[1] > UINT32_MAX))
{
error = true;
}
if (!error)
{
*endptr = strtoull_endptr;
return GtidTriplet((uint32_t)parsed_numbers[0], parsed_numbers[1], parsed_numbers[2]);
}
else
{
return GtidTriplet();
}
}
GtidTriplet::GtidTriplet()
: domain(0)
, server_id(SERVER_ID_UNKNOWN)
, sequence(0)
{}
Gtid::Gtid(const char* str, int64_t search_domain)
GtidTriplet::GtidTriplet(uint32_t _domain, int64_t _server_id, uint64_t _sequence)
: domain(_domain)
, server_id(_server_id)
, sequence(_sequence)
{}
GtidTriplet::GtidTriplet(const char* str, int64_t search_domain)
: domain(0)
, server_id(SERVER_ID_UNKNOWN)
, sequence(0)
@ -245,14 +425,12 @@ Gtid::Gtid(const char* str, int64_t search_domain)
}
}
bool Gtid::operator == (const Gtid& rhs) const
bool GtidTriplet::eq(const GtidTriplet& rhs) const
{
return domain == rhs.domain &&
server_id != SERVER_ID_UNKNOWN && server_id == rhs.server_id &&
sequence == rhs.sequence;
return domain == rhs.domain && server_id == rhs.server_id && sequence == rhs.sequence;
}
string Gtid::to_string() const
string GtidTriplet::to_string() const
{
std::stringstream ss;
if (server_id != SERVER_ID_UNKNOWN)
@ -262,13 +440,13 @@ string Gtid::to_string() const
return ss.str();
}
void Gtid::parse_triplet(const char* str)
void GtidTriplet::parse_triplet(const char* str)
{
ss_debug(int rv = ) sscanf(str, "%" PRIu32 "-%" PRId64 "-%" PRIu64, &domain, &server_id, &sequence);
ss_dassert(rv == 3);
}
string Gtid::generate_master_gtid_wait_cmd(double timeout) const
string GtidTriplet::generate_master_gtid_wait_cmd(double timeout) const
{
std::stringstream query_ss;
query_ss << "SELECT MASTER_GTID_WAIT(\"" << to_string() << "\", " << timeout << ");";

View File

@ -72,25 +72,40 @@ string get_connection_errors(const ServerVector& servers);
*/
string monitored_servers_to_string(const ServerVector& array);
class Gtid
/**
* Class which encapsulates a gtid triplet (one <domain>-<server>-<sequence>)
*/
class GtidTriplet
{
public:
uint32_t domain;
int64_t server_id; // Is actually 32bit unsigned. 0 is only used by server versions <= 10.1
uint64_t sequence;
Gtid();
GtidTriplet();
GtidTriplet(uint32_t _domain, int64_t _server_id, uint64_t _sequence);
/**
* Parse a Gtid-triplet from a string. In case of a multi-triplet value, only the triplet with
* the given domain is returned.
* the given domain is returned. TODO: Remove once no longer used
*
* @param str Gtid string
* @param search_domain The Gtid domain whose triplet should be returned. Negative domain stands for
* autoselect, which is only allowed when the string contains one triplet.
*/
Gtid(const char* str, int64_t search_domain = -1);
GtidTriplet(const char* str, int64_t search_domain = -1);
bool operator == (const Gtid& rhs) const;
/**
* Parse one triplet from null-terminated string. Handles multi-domain gtid:s properly. Should be called
* repeatedly for a multi-domain gtid string by giving the value of @c endptr as @c str.
*
* @param str First number of a triplet in a gtid-string
* @param endptr A pointer to save the position at after the last parsed character.
* @return A new GtidTriplet. If an error occurs, the server_id of the returned triplet is -1.
*/
static GtidTriplet parse_one_triplet(const char* str, char** endptr);
bool eq(const GtidTriplet& rhs) const;
std::string to_string() const;
@ -102,10 +117,99 @@ public:
*/
std::string generate_master_gtid_wait_cmd(double timeout) const;
/**
* Comparator, used when sorting by domain id.
*
* @param triplet1 Left side
* @param triplet2 Right side
* @return True if left < right
*/
static bool compare_domains(const GtidTriplet& triplet1, const GtidTriplet& triplet2)
{
return triplet1.domain < triplet2.domain;
}
private:
void parse_triplet(const char* str);
};
inline bool operator == (const GtidTriplet& lhs, const GtidTriplet& rhs)
{
return lhs.eq(rhs);
}
class Gtid
{
public:
enum substraction_mode_t
{
MISSING_DOMAIN_IGNORE,
MISSING_DOMAIN_LHS_ADD
};
/**
* Parse the gtid string and return an object. Orders the triplets by domain id.
*
* @param gtid_string gtid as given by server. String must not be empty.
* @return The parsed (possibly multidomain) gtid. In case of error, the gtid will be empty.
*/
static Gtid from_string(const std::string& gtid_string);
/**
* Return a string version of the gtid.
*
* @return A string similar in form to how the server displays it
*/
std::string to_string() const;
/**
* Check if a server with this gtid can replicate from a master with a given gtid. Only considers
* gtid:s and only detects obvious errors. The non-detected errors will mostly be detected once
* the slave tries to start replicating.
*
* TODO: Add support for Replicate_Do/Ignore_Id:s
*
* @param master_gtid Master server gtid
* @return True if replication looks possible
*/
bool can_replicate_from(const Gtid& master_gtid);
/**
* Is the gtid empty.
*
* @return True if gtid has 0 triplets
*/
bool empty() const;
/**
* Full comparison.
*
* @param rhs Other gtid
* @return True if both gtid:s have identical triplets or both are empty
*/
bool operator == (const Gtid& rhs) const;
/**
* Calculate the number of events between two gtid:s with possibly multiple triplets. The
* result is always 0 or greater: if a sequence number of a domain on rhs is greater than on the same
* domain on lhs, the sequences are considered identical. Missing domains are handled depending on the
* value of @c domain_substraction_mode.
*
* @param lhs The value substracted from
* @param io_pos The value doing the substracting
* @param domain_substraction_mode How domains that exist on one side but not the other are handled. If
* MISSING_DOMAIN_IGNORE, these are simply ignored. If MISSING_DOMAIN_LHS_ADD, the sequence number on lhs
* is added to the total difference.
* @return The number of events between the two gtid:s
*/
static uint64_t events_ahead(const Gtid& lhs, const Gtid& rhs,
substraction_mode_t domain_substraction_mode);
private:
std::vector<GtidTriplet> m_triplets;
};
/**
* Helper class for simplifying working with resultsets. Used in MariaDBServer.
*/
@ -181,7 +285,7 @@ public:
* @param gtid_domain Which gtid domain to parse
* @return Value as a gtid.
*/
Gtid get_gtid(int64_t column_ind, int64_t gtid_domain) const;
GtidTriplet get_gtid(int64_t column_ind, int64_t gtid_domain) const;
private:
MYSQL_RES* m_resultset; // Underlying result set, freed at dtor.