Files
MaxScale/server/modules/routing/readwritesplit/rwsplit_select_backends.cc
Markus Mäkelä ae0e9b359d Fix use of zero-weight servers
The servers with a zero weight would be always used over ones that have a
weight. This means that the behavior was inverted and caused the
mxs2054_hybrid_cluster test to fail in 2.3.

Also fixed a typo in the deprecation message.
2018-11-12 10:13:59 +02:00

459 lines
14 KiB
C++

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2022-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "readwritesplit.hh"
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sstream>
#include <functional>
#include <random>
#include <iostream>
#include <maxbase/stopwatch.hh>
#include <maxscale/router.h>
using namespace maxscale;
// TODO, there should be a utility with the most common used random cases.
// FYI: rand() is about twice as fast as the below toss.
static std::mt19937 random_engine;
static std::uniform_real_distribution<> zero_to_one(0.0, 1.0);
double toss()
{
return zero_to_one(random_engine);
}
/**
* The functions that implement back end selection for the read write
* split router. All of these functions are internal to that router and
* not intended to be called from elsewhere.
*/
/**
* Check whether it's possible to use this server as a slave
*
* @param server The slave candidate
* @param master The master server or NULL if no master is available
*
* @return True if this server is a valid slave candidate
*/
static bool valid_for_slave(const SRWBackend& backend, const SRWBackend& master)
{
return (backend->is_slave() || backend->is_relay())
&& (!master || backend != master);
}
SRWBackendVector::iterator best_score(SRWBackendVector& sBackends,
std::function<double(SERVER_REF* server)> server_score)
{
double min {std::numeric_limits<double>::max()};
auto best = sBackends.end();
for (auto ite = sBackends.begin(); ite != sBackends.end(); ++ite)
{
double score = server_score((***ite).backend());
if (min > score)
{
min = score;
best = ite;
}
}
return best;
}
/** Compare number of connections from this router in backend servers */
SRWBackendVector::iterator backend_cmp_router_conn(SRWBackendVector& sBackends)
{
static auto server_score = [](SERVER_REF* server) {
return server->server_weight ? (server->connections + 1) / server->server_weight :
std::numeric_limits<double>::max();
};
return best_score(sBackends, server_score);
}
/** Compare number of global connections in backend servers */
SRWBackendVector::iterator backend_cmp_global_conn(SRWBackendVector& sBackends)
{
static auto server_score = [](SERVER_REF* server) {
return server->server_weight ? (server->server->stats.n_current + 1) / server->server_weight :
std::numeric_limits<double>::max();
};
return best_score(sBackends, server_score);
}
/** Compare replication lag between backend servers */
SRWBackendVector::iterator backend_cmp_behind_master(SRWBackendVector& sBackends)
{
static auto server_score = [](SERVER_REF* server) {
return server->server_weight ? server->server->rlag / server->server_weight :
std::numeric_limits<double>::max();
};
return best_score(sBackends, server_score);
}
/** Compare number of current operations in backend servers */
SRWBackendVector::iterator backend_cmp_current_load(SRWBackendVector& sBackends)
{
static auto server_score = [](SERVER_REF* server) {
return server->server_weight ? (server->server->stats.n_current_ops + 1) / server->server_weight :
std::numeric_limits<double>::max();
};
return best_score(sBackends, server_score);
}
SRWBackendVector::iterator backend_cmp_response_time(SRWBackendVector& sBackends)
{
const int SZ = sBackends.size();
double slot[SZ];
// fill slots with inverses of averages
double pre_total {0};
for (int i = 0; i < SZ; ++i)
{
SERVER_REF* server = (**sBackends[i]).backend();
double ave = server_response_time_average(server->server);
if (ave == 0)
{
constexpr double very_quick = 1.0 / 10000000; // arbitrary very short duration (0.1 us)
slot[i] = 1 / very_quick; // will be used and updated (almost) immediately.
}
else
{
slot[i] = 1 / ave;
}
slot[i] = slot[i] * slot[i] * slot[i]; // favor faster servers even more
pre_total += slot[i];
}
// make the slowest server(s) at least a good fraction of the total to guarantee
// some amount of sampling, should the slow server become faster.
double total = 0;
constexpr int divisor = 197; // ~0.5%, not exact because SZ>1
for (int i = 0; i < SZ; ++i)
{
slot[i] = std::max(slot[i], pre_total / divisor);
total += slot[i];
}
// turn slots into a roulette wheel, where sum of slots is 1.0
for (int i = 0; i < SZ; ++i)
{
slot[i] = slot[i] / total;
}
// Find the winner, role the ball:
double ball = toss();
double slot_walk {0};
int winner {0};
for (; winner < SZ; ++winner)
{
slot_walk += slot[winner];
if (ball < slot_walk)
{
break;
}
}
return sBackends.begin() + winner;
}
BackendSelectFunction get_backend_select_function(select_criteria_t sc)
{
switch (sc)
{
case LEAST_GLOBAL_CONNECTIONS:
return backend_cmp_global_conn;
case LEAST_ROUTER_CONNECTIONS:
return backend_cmp_router_conn;
case LEAST_BEHIND_MASTER:
return backend_cmp_behind_master;
case LEAST_CURRENT_OPERATIONS:
return backend_cmp_current_load;
case ADAPTIVE_ROUTING:
return backend_cmp_response_time;
}
assert(false && "incorrect use of select_criteria_t");
return backend_cmp_current_load;
}
/**
* @brief Find the best slave candidate for routing reads.
*
* @param backends All backends
* @param select Server selection function
* @param masters_accepts_reads
*
* @return iterator to the best slave or backends.end() if none found
*/
SRWBackendVector::iterator find_best_backend(SRWBackendVector& backends,
BackendSelectFunction select,
bool masters_accepts_reads)
{
// Group backends by priority. The set of highest priority backends will then compete.
std::map<int, SRWBackendVector> 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_accepts_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 = select(priority_map[best_priority]);
return std::find(backends.begin(), backends.end(), *best);
}
/**
* @brief Log server connections
*
* @param criteria Slave selection criteria
* @param rses Router client session
*/
static void log_server_connections(select_criteria_t criteria, const SRWBackendList& backends)
{
MXS_INFO("Servers and %s connection counts:",
criteria == LEAST_GLOBAL_CONNECTIONS ? "all MaxScale" : "router");
for (SRWBackendList::const_iterator it = backends.begin(); it != backends.end(); it++)
{
SERVER_REF* b = (*it)->backend();
switch (criteria)
{
case LEAST_GLOBAL_CONNECTIONS:
MXS_INFO("MaxScale connections : %d in \t[%s]:%d %s",
b->server->stats.n_current,
b->server->address,
b->server->port,
STRSRVSTATUS(b->server));
break;
case LEAST_ROUTER_CONNECTIONS:
MXS_INFO("RWSplit connections : %d in \t[%s]:%d %s",
b->connections,
b->server->address,
b->server->port,
STRSRVSTATUS(b->server));
break;
case LEAST_CURRENT_OPERATIONS:
MXS_INFO("current operations : %d in \t[%s]:%d %s",
b->server->stats.n_current_ops,
b->server->address,
b->server->port,
STRSRVSTATUS(b->server));
break;
case LEAST_BEHIND_MASTER:
MXS_INFO("replication lag : %d in \t[%s]:%d %s",
b->server->rlag,
b->server->address,
b->server->port,
STRSRVSTATUS(b->server));
break;
case ADAPTIVE_ROUTING:
{
maxbase::Duration response_ave(server_response_time_average(b->server));
std::ostringstream os;
os << response_ave;
MXS_INFO("adaptive avg. select time: %s from \t[%s]:%d %s",
os.str().c_str(),
b->server->address,
b->server->port,
STRSRVSTATUS(b->server));
}
break;
default:
mxb_assert(!true);
break;
}
}
}
SRWBackend get_root_master(const SRWBackendList& backends)
{
SRWBackend master;
for (auto candidate : backends)
{
if (candidate->is_master())
{
master = candidate;
break;
}
}
return master;
}
std::pair<int, int> get_slave_counts(SRWBackendList& backends, SRWBackend& master)
{
int slaves_found = 0;
int slaves_connected = 0;
/** Calculate how many connections we already have */
for (SRWBackendList::const_iterator it = backends.begin(); it != backends.end(); it++)
{
const SRWBackend& backend = *it;
if (backend->can_connect() && valid_for_slave(backend, master))
{
slaves_found += 1;
if (backend->in_use())
{
slaves_connected += 1;
}
}
}
return std::make_pair(slaves_found, slaves_connected);
}
/**
* Select and connect to backend servers
*
* @param inst Router instance
* @param session Client session
* @param backends List of backend servers
* @param current_master The current master server
* @param sescmd_list List of session commands to execute
* @param expected_responses Pointer where number of expected responses are written
* @param type Connection type, ALL for all types, SLAVE for slaves only
*
* @return True if session can continue
*/
bool RWSplit::select_connect_backend_servers(MXS_SESSION* session,
SRWBackendList& backends,
SRWBackend& current_master,
SessionCommandList* sescmd_list,
int* expected_responses,
connection_type type)
{
SRWBackend master = get_root_master(backends);
const Config& cnf {config()};
if (!master && cnf.master_failure_mode == RW_FAIL_INSTANTLY)
{
MXS_ERROR("Couldn't find suitable Master from %lu candidates.", backends.size());
return false;
}
auto select_criteria = cnf.slave_selection_criteria;
if (mxs_log_is_priority_enabled(LOG_INFO))
{
log_server_connections(select_criteria, backends);
}
if (type == ALL)
{
/** Find a master server */
for (SRWBackendList::const_iterator it = backends.begin(); it != backends.end(); it++)
{
const SRWBackend& backend = *it;
if (backend->can_connect() && master && backend == master)
{
if (backend->connect(session))
{
MXS_INFO("Selected Master: %s", backend->name());
current_master = backend;
}
break;
}
}
}
auto counts = get_slave_counts(backends, master);
int slaves_connected = counts.second;
int max_nslaves = max_slave_count();
mxb_assert(slaves_connected <= max_nslaves || max_nslaves == 0);
SRWBackendVector candidates;
for (auto& sBackend : backends)
{
if (!sBackend->in_use()
&& sBackend->can_connect()
&& valid_for_slave(sBackend, master))
{
candidates.push_back(&sBackend);
}
}
while (slaves_connected < max_nslaves && candidates.size())
{
auto ite = m_config->backend_select_fct(candidates);
if (ite == candidates.end())
{
break;
}
auto& backend = **ite;
if (backend->connect(session, sescmd_list))
{
MXS_INFO("Selected Slave: %s", backend->name());
if (sescmd_list && sescmd_list->size() && expected_responses)
{
(*expected_responses)++;
}
++slaves_connected;
}
candidates.erase(ite);
}
return true;
}