From c5ba795331df7bb7a12cd55d689f2dfdb440038b Mon Sep 17 00:00:00 2001 From: Zsolt Date: Wed, 8 May 2024 09:31:05 +0200 Subject: [PATCH] Add sticky backend query hint Sometimes we want to make sure that queries in a single connection all hit the same DB replica. This helps us remain in a consistent state in the face of small replication delays between the different hosts. In many cases, it's preferable to read a consistent state from a single replica, even if that replica might not contain the most up to date version of the world. For example, let's say that we quickly insert two records (A and B) into the database and B causally depends on A. Once the application reads B, it assumes that A already exists and complains if it does not find it. This feature ensures read consistency by making sure that the application talks to the same replica during the lifetime of the connection. Sticky backends are disabled by default, and can be enabled by using the `/* sticky_backend=1 */` query hint. This is then persisted for the entire session, so the client only has to set the value once. The setting takes effect with the current query being executed: - when enabling sticky backends a load-balanced server will be selected for the query and the following queries will be executed against the same server - when disabling sticky backends, this and all subsequent queries will be executed against a load-balanced server We support three different sticky modes: - 0: no sticky backends (default) - 1: stick to the same replica as long as it is alive - 2: strict sticky, if the sticky replica is not alive no other replica will be chosen until it errors out --- include/MySQL_Session.h | 6 ++++++ include/query_processor.h | 2 ++ lib/MyHGC.cpp | 38 ++++++++++++++++++++++++++++++++++++++ lib/MySQL_Session.cpp | 6 ++++++ lib/Query_Processor.cpp | 6 ++++++ 5 files changed, 58 insertions(+) diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 2a593d3ce5..c625f9183b 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -305,6 +305,12 @@ class MySQL_Session PtrArray *mybes; MySQL_Data_Stream *client_myds; MySQL_Data_Stream *server_myds; + + // Storage for per-hostgroup backend server hints + std::map sticky_backend_hint; + + int sticky_backend; + /* * @brief Store the hostgroups that hold connections that have been flagged as 'expired' by the * maintenance thread. These values will be used to release the retained connections in the specific diff --git a/include/query_processor.h b/include/query_processor.h index 9d62330291..5082a00f0a 100644 --- a/include/query_processor.h +++ b/include/query_processor.h @@ -131,6 +131,7 @@ class Query_Processor_Output { int timeout; int retries; int delay; + int sticky_backend; char *error_msg; char *OK_msg; int sticky_conn; @@ -170,6 +171,7 @@ class Query_Processor_Output { timeout=-1; retries=-1; delay=-1; + sticky_backend=-1; sticky_conn=-1; multiplex=-1; gtid_from_hostgroup=-1; diff --git a/lib/MyHGC.cpp b/lib/MyHGC.cpp index 8742db240c..017074fad4 100644 --- a/lib/MyHGC.cpp +++ b/lib/MyHGC.cpp @@ -362,6 +362,36 @@ MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_ k++; New_sum=0; + // This is promoting the stickyness value in the query hint to be a session variable + if (sess->qpo->sticky_backend != -1 && 0 <= sess->qpo->sticky_backend && sess->qpo->sticky_backend <= 2) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "promoting sticky_backend with value %d\n", sess->qpo->sticky_backend); + sess->sticky_backend = sess->qpo->sticky_backend; + } + + if (sess->sticky_backend > 0) { + if (auto my_srv_hint = sess->sticky_backend_hint.find(this->hid); my_srv_hint != sess->sticky_backend_hint.end()) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "found prefered sticky backend\n"); + for (j=0; jsecond) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "matched prefered sticky backend\n"); + return mysrvc; + } + } + + //Abort if we can't find the backend in candidates and we are in strict mode. + if (sess->sticky_backend==2) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "didn't match prefered sticky backend in strict mode\n"); + return NULL; + } + + // We are clearing the server because .insert doesn't replace... + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "clearing prefered sticky backend in strict mode\n"); + sess->sticky_backend_hint.erase(this->hid); + } + } + + for (j=0; jweight; @@ -373,6 +403,14 @@ MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_ #ifdef TEST_AURORA array_mysrvc_cands += num_candidates; #endif // TEST_AURORA + if (sess->sticky_backend > 0) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "insert prefered sticky backend\n"); + sess->sticky_backend_hint.insert({this->hid, mysrvc}); + } else { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "erase prefered sticky backend\n"); + sess->sticky_backend_hint.erase(this->hid); + } + return mysrvc; } } diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index ae306e36e4..ef00073deb 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -728,6 +728,12 @@ MySQL_Session::MySQL_Session() { use_ssl = false; change_user_auth_switch = false; + // Session-level status of the sticky_backend + // 0 means non-sticky + // 1 means sticky, fallback to new server in case of server failure + // 2 means strictly sticky, throw an error in case of server failure + sticky_backend=0; + //gtid_trxid = 0; gtid_hid = -1; memset(gtid_buf,0,sizeof(gtid_buf)); diff --git a/lib/Query_Processor.cpp b/lib/Query_Processor.cpp index 2918104821..8618feed83 100644 --- a/lib/Query_Processor.cpp +++ b/lib/Query_Processor.cpp @@ -2600,6 +2600,12 @@ bool Query_Processor::query_parser_first_comment(Query_Processor_Output *qpo, ch remove_spaces(value); if (strlen(key)) { char c=value[0]; + if (!strcasecmp(key,"sticky_backend")) { + if (c >= '0' && c <= '9') { // it is a digit + int t=atoi(value); + qpo->sticky_backend=t; + } + } if (!strcasecmp(key,"cache_ttl")) { if (c >= '0' && c <= '9') { // it is a digit int t=atoi(value);