MXS-1777 Refactor selection.
This commit refactors slave selection. The compare is still pair-wise but isolated into a small run_comparison() function. The function get_slave_candidate() is used when new connections are created, which I both moved and modified (had to move due to scoping), so diff is off. The slave selection for routing: get_slave_backend() now has the filtering logic from old get_slave_backend() and compare_backends(), the latter of which is removed. Backend functions mostly take shared_ptr<SRWBackend> in various forms (as is, const ref, in a container). Ideally the shared_ptr would be used only to where it is really needed, and either naked ptrs or references to RWBackend would be used. This refactor does not address that issue, but compounds it by using even deeper shared_ptr structures. Fixing that in a future commit.
This commit is contained in:
@ -382,6 +382,25 @@ mxs::SRWBackend get_root_master(const mxs::SRWBackendList& backends);
|
||||
*/
|
||||
std::pair<int, int> get_slave_counts(mxs::SRWBackendList& backends, mxs::SRWBackend& master);
|
||||
|
||||
|
||||
/* TODO, hopefully temporary */
|
||||
using BackendSPtrVec = std::vector<mxs::SRWBackend*>;
|
||||
|
||||
/**
|
||||
* Find the best backend based on categorizing the servers, and then applying
|
||||
* selection criteria to the best category.
|
||||
*
|
||||
* @param backends: vector of SRWBackend
|
||||
* @param sc: which select_criteria_t to use
|
||||
* @param master_accept_reads: NOTE: even if this is false, in some cases a master can
|
||||
* still be selected for reads.
|
||||
*
|
||||
* @return Valid iterator into argument backends, or end(backends) if empty
|
||||
*/
|
||||
BackendSPtrVec::const_iterator find_best_backend(const BackendSPtrVec& backends,
|
||||
select_criteria_t sc,
|
||||
bool masters_accept_reads);
|
||||
|
||||
/*
|
||||
* The following are implemented in rwsplit_tmp_table_multi.c
|
||||
*/
|
||||
|
@ -49,7 +49,7 @@ void ResponseStat::query_ended()
|
||||
|
||||
if (++m_sample_count == m_num_filter_samples)
|
||||
{
|
||||
std::sort(begin(m_samples), end(m_samples));
|
||||
std::sort(m_samples.begin(), m_samples.end());
|
||||
maxbase::Duration new_sample = m_samples[m_num_filter_samples / 2];
|
||||
m_average.add(std::chrono::duration<double>(new_sample).count());
|
||||
m_sample_count = 0;
|
||||
|
@ -12,8 +12,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <maxscale/cppdefs.hh>
|
||||
|
||||
#include <maxscale/ccdefs.hh>
|
||||
#include <maxbase/stopwatch.hh>
|
||||
#include <maxbase/average.hh>
|
||||
|
||||
@ -22,7 +21,6 @@
|
||||
*/
|
||||
namespace maxscale
|
||||
{
|
||||
|
||||
/**
|
||||
* Query response statistics. Uses median of N samples to filter noise, then
|
||||
* uses those medians to calculate the average response time.
|
||||
@ -39,23 +37,24 @@ public:
|
||||
* @param num_synch_samples - this many medians before the average should be synced, or
|
||||
* @param sync_duration - this much time between syncs.
|
||||
*/
|
||||
ResponseStat(int ignore_first_n = 5, int num_filter_samples = 3,
|
||||
ResponseStat(int ignore_first_n = 5,
|
||||
int num_filter_samples = 3,
|
||||
maxbase::Duration sync_duration = std::chrono::seconds(5));
|
||||
void query_started();
|
||||
void query_ended(); // ok to call without a query_started
|
||||
bool is_valid() const;
|
||||
int num_samples() const;
|
||||
void query_started();
|
||||
void query_ended();// ok to call without a query_started
|
||||
bool is_valid() const;
|
||||
int num_samples() const;
|
||||
maxbase::Duration average() const;
|
||||
bool sync_time_reached(int num_synch_medians); // is it time to apply the average?
|
||||
void reset();
|
||||
bool sync_time_reached(int num_synch_medians); // is it time to apply the average?
|
||||
void reset();
|
||||
private:
|
||||
int m_ignore_first_n;
|
||||
const int m_num_filter_samples;
|
||||
const maxbase::Duration m_sync_duration;
|
||||
int m_sample_count;
|
||||
std::vector<maxbase::Duration> m_samples; // N sampels from which median is used
|
||||
maxbase::CumulativeAverage m_average;
|
||||
maxbase::TimePoint m_last_start;
|
||||
maxbase::TimePoint m_next_sync;
|
||||
int m_ignore_first_n;
|
||||
const int m_num_filter_samples;
|
||||
const maxbase::Duration m_sync_duration;
|
||||
int m_sample_count;
|
||||
std::vector<maxbase::Duration> m_samples; // N sampels from which median is used
|
||||
maxbase::CumulativeAverage m_average;
|
||||
maxbase::TimePoint m_last_start;
|
||||
maxbase::TimePoint m_next_sync;
|
||||
};
|
||||
}
|
||||
|
@ -38,47 +38,6 @@ using namespace maxscale;
|
||||
|
||||
extern int (*criteria_cmpfun[LAST_CRITERIA])(const SRWBackend&, const SRWBackend&);
|
||||
|
||||
/**
|
||||
* Find out which of the two backend servers has smaller value for select
|
||||
* criteria property.
|
||||
*
|
||||
* @param cand previously selected candidate
|
||||
* @param new challenger
|
||||
* @param sc select criteria
|
||||
*
|
||||
* @return pointer to backend reference of that backend server which has smaller
|
||||
* value in selection criteria. If either reference pointer is NULL then the
|
||||
* other reference pointer value is returned.
|
||||
*/
|
||||
static SRWBackend compare_backends(SRWBackend a, SRWBackend b, select_criteria_t sc)
|
||||
{
|
||||
int (*p)(const SRWBackend&, const SRWBackend&) = criteria_cmpfun[sc];
|
||||
|
||||
if (!a)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else if (!b)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
// Prefer servers that are not busy executing session commands
|
||||
bool a_busy = a->in_use() && a->has_session_commands();
|
||||
bool b_busy = b->in_use() && b->has_session_commands();
|
||||
|
||||
if (a_busy && !b_busy)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else if (!a_busy && b_busy)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
return p(a, b) <= 0 ? a : b;
|
||||
}
|
||||
|
||||
void RWSplitSession::handle_connection_keepalive(SRWBackend& target)
|
||||
{
|
||||
mxb_assert(target);
|
||||
@ -582,45 +541,36 @@ SRWBackend RWSplitSession::get_hinted_backend(char *name)
|
||||
|
||||
SRWBackend RWSplitSession::get_slave_backend(int max_rlag)
|
||||
{
|
||||
SRWBackend rval;
|
||||
// create a list of useable backends (includes masters, function name is a bit off),
|
||||
// then feed that list to compare.
|
||||
BackendSPtrVec candidates;
|
||||
auto counts = get_slave_counts(m_backends, m_current_master);
|
||||
|
||||
for (auto it = m_backends.begin(); it != m_backends.end(); it++)
|
||||
for (auto& backend : m_backends)
|
||||
{
|
||||
auto& backend = *it;
|
||||
bool can_take_slave_into_use = backend->is_slave()
|
||||
&& !backend->in_use()
|
||||
&& can_recover_servers()
|
||||
&& backend->can_connect()
|
||||
&& counts.second < m_router->max_slave_count();
|
||||
|
||||
if ((backend->is_master() || backend->is_slave()) && // Either a master or a slave
|
||||
rpl_lag_is_ok(backend, max_rlag)) // Not lagging too much
|
||||
bool master_or_slave = backend->is_master() || backend->is_slave();
|
||||
bool is_useable = backend->in_use() || can_take_slave_into_use;
|
||||
bool not_a_slacker = rpl_lag_is_ok(backend, max_rlag);
|
||||
|
||||
bool server_is_candidate = master_or_slave && is_useable && not_a_slacker;
|
||||
|
||||
if (server_is_candidate)
|
||||
{
|
||||
if (backend->in_use() || (can_recover_servers() && backend->can_connect()))
|
||||
{
|
||||
if (!rval)
|
||||
{
|
||||
// No previous candidate, accept any valid server (includes master)
|
||||
if ((backend->is_master() && backend == m_current_master) ||
|
||||
backend->is_slave())
|
||||
{
|
||||
rval = backend;
|
||||
}
|
||||
}
|
||||
else if (backend->in_use() || counts.second < m_router->max_slave_count())
|
||||
{
|
||||
if (!m_config.master_accept_reads && rval->is_master())
|
||||
{
|
||||
// Pick slaves over masters with master_accept_reads=false
|
||||
rval = backend;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compare the two servers and pick the best one
|
||||
rval = compare_backends(rval, backend, m_config.slave_selection_criteria);
|
||||
}
|
||||
}
|
||||
}
|
||||
candidates.push_back(&backend);
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
BackendSPtrVec::const_iterator rval = find_best_backend(candidates,
|
||||
m_config.slave_selection_criteria,
|
||||
m_config.master_accept_reads);
|
||||
|
||||
return (rval == candidates.end()) ? SRWBackend() : **rval;
|
||||
}
|
||||
|
||||
SRWBackend RWSplitSession::get_master_backend()
|
||||
|
@ -45,48 +45,6 @@ static bool valid_for_slave(const SRWBackend& backend, const SRWBackend& master)
|
||||
(!master || backend != master);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the best slave candidate
|
||||
*
|
||||
* This function iterates through @c backend and tries to find the best backend
|
||||
* reference that is not in use. @c cmpfun will be called to compare the backends.
|
||||
*
|
||||
* @param rses Router client session
|
||||
* @param master The master server
|
||||
* @param cmpfun qsort() compatible comparison function
|
||||
*
|
||||
* @return The best slave backend reference or NULL if no candidates could be found
|
||||
*/
|
||||
static SRWBackend get_slave_candidate(const SRWBackendList& backends, const SRWBackend& master,
|
||||
int (*cmpfun)(const SRWBackend&, const SRWBackend&))
|
||||
{
|
||||
SRWBackend candidate;
|
||||
|
||||
for (SRWBackendList::const_iterator it = backends.begin();
|
||||
it != backends.end(); it++)
|
||||
{
|
||||
const SRWBackend& backend = *it;
|
||||
|
||||
if (!backend->in_use() && backend->can_connect() &&
|
||||
valid_for_slave(backend, master))
|
||||
{
|
||||
if (candidate)
|
||||
{
|
||||
if (cmpfun(candidate, backend) > 0)
|
||||
{
|
||||
candidate = backend;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
candidate = backend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/** Compare number of connections from this router in backend servers */
|
||||
static int backend_cmp_router_conn(const SRWBackend& a, const SRWBackend& b)
|
||||
{
|
||||
@ -223,6 +181,104 @@ int (*criteria_cmpfun[LAST_CRITERIA])(const SRWBackend&, const SRWBackend&) =
|
||||
backend_cmp_response_time
|
||||
};
|
||||
|
||||
// This is still the current compare method. The response-time compare, along with anything
|
||||
// using weights, have to change to use the whole array at once to be correct. Id est, everything
|
||||
// will change to use the whole array in the next iteration.
|
||||
static BackendSPtrVec::const_iterator run_comparison(const BackendSPtrVec& candidates,
|
||||
select_criteria_t sc)
|
||||
{
|
||||
if (candidates.empty()) return candidates.end();
|
||||
|
||||
auto best = candidates.begin();
|
||||
|
||||
for (auto rival = std::next(best);
|
||||
rival != candidates.end();
|
||||
rival = std::next(rival))
|
||||
{
|
||||
if (criteria_cmpfun[sc](**best, **rival) > 0)
|
||||
{
|
||||
best = rival;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the best slave candidate for a new connection.
|
||||
*
|
||||
* @param bends backends
|
||||
* @param master the master server
|
||||
* @param sc which select_criteria_t to use
|
||||
*
|
||||
* @return The best slave backend reference or null if no candidates could be found
|
||||
*/
|
||||
static SRWBackend get_slave_candidate(const SRWBackendList& bends,
|
||||
const SRWBackend& master,
|
||||
select_criteria_t sc)
|
||||
{
|
||||
// TODO, nantti, see if this and get_slave_backend can be combined to a single function
|
||||
BackendSPtrVec backends;
|
||||
for (auto& b : bends) // match intefaces. TODO, should go away in the future.
|
||||
{
|
||||
backends.push_back(const_cast<SRWBackend*>(&b));
|
||||
}
|
||||
BackendSPtrVec candidates;
|
||||
|
||||
for (auto& backend : backends)
|
||||
{
|
||||
if (!(*backend)->in_use()
|
||||
&& (*backend)->can_connect()
|
||||
&& valid_for_slave(*backend, master))
|
||||
{
|
||||
candidates.push_back(backend);
|
||||
}
|
||||
}
|
||||
|
||||
return !candidates.empty() ? **run_comparison(candidates, sc) : SRWBackend();
|
||||
|
||||
}
|
||||
|
||||
BackendSPtrVec::const_iterator find_best_backend(const BackendSPtrVec& backends,
|
||||
select_criteria_t sc,
|
||||
bool masters_accept_reads)
|
||||
{
|
||||
// Divide backends to priorities. The set of highest priority backends will then compete.
|
||||
std::map<int, BackendSPtrVec> priority_map;;
|
||||
int best_priority {INT_MAX}; // low numbers are high priority
|
||||
|
||||
for (auto& pSBackend : backends)
|
||||
{
|
||||
auto& backend = **pSBackend;
|
||||
bool is_busy = backend.in_use() && backend.has_session_commands();
|
||||
bool acts_slave = backend.is_slave() || (backend.is_master() && masters_accept_reads);
|
||||
|
||||
int priority;
|
||||
if (acts_slave)
|
||||
{
|
||||
if (!is_busy)
|
||||
{
|
||||
priority = 1; // highest priority, idle servers
|
||||
}
|
||||
else
|
||||
{
|
||||
priority = 13; // lowest priority, busy servers
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
priority = 2; // idle masters with masters_accept_reads==false
|
||||
}
|
||||
|
||||
priority_map[priority].push_back(pSBackend);
|
||||
best_priority = std::min(best_priority, priority);
|
||||
}
|
||||
|
||||
auto best = run_comparison(priority_map[best_priority], sc);
|
||||
|
||||
return std::find(backends.begin(), backends.end(), *best);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Log server connections
|
||||
*
|
||||
@ -351,10 +407,7 @@ bool RWSplit::select_connect_backend_servers(MXS_SESSION *session,
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Check slave selection criteria and set compare function */
|
||||
select_criteria_t select_criteria = cnf.slave_selection_criteria;
|
||||
auto cmpfun = criteria_cmpfun[select_criteria];
|
||||
mxb_assert(cmpfun);
|
||||
auto select_criteria = cnf.slave_selection_criteria;
|
||||
|
||||
if (mxs_log_is_priority_enabled(LOG_INFO))
|
||||
{
|
||||
@ -389,9 +442,9 @@ bool RWSplit::select_connect_backend_servers(MXS_SESSION *session,
|
||||
if (slaves_connected < max_nslaves)
|
||||
{
|
||||
/** Connect to all possible slaves */
|
||||
for (SRWBackend backend(get_slave_candidate(backends, master, cmpfun));
|
||||
for (SRWBackend backend(get_slave_candidate(backends, master, select_criteria));
|
||||
backend && slaves_connected < max_nslaves;
|
||||
backend = get_slave_candidate(backends, master, cmpfun))
|
||||
backend = get_slave_candidate(backends, master, select_criteria))
|
||||
{
|
||||
if (backend->can_connect() && backend->connect(session, sescmd_list))
|
||||
{
|
||||
|
Reference in New Issue
Block a user