From da5291ee5e977c5df10392316bd18682eedabf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 8 Apr 2022 20:36:14 +0200 Subject: [PATCH 1/2] Add new variable 'mysql-auto_increment_delay_multiplex_timeout_ms' --- include/MySQL_Thread.h | 1 + include/proxysql_structs.h | 2 ++ lib/MySQL_Thread.cpp | 12 ++++++++++++ 3 files changed, 15 insertions(+) diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index d426d92fd..74979c576 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -534,6 +534,7 @@ class MySQL_Threads_Handler int set_query_lock_on_hostgroup; int reset_connection_algorithm; int auto_increment_delay_multiplex; + int auto_increment_delay_multiplex_timeout_ms; int long_query_time; int hostgroup_manager_verbose; int binlog_reader_connect_retry_msec; diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index 4665bbef0..7bbc77d7e 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -794,6 +794,7 @@ __thread int mysql_thread___set_query_lock_on_hostgroup; __thread int mysql_thread___reset_connection_algorithm; __thread uint32_t mysql_thread___server_capabilities; __thread int mysql_thread___auto_increment_delay_multiplex; +__thread int mysql_thread___auto_increment_delay_multiplex_timeout_ms; __thread int mysql_thread___handle_unknown_charset; __thread int mysql_thread___poll_timeout; __thread int mysql_thread___poll_timeout_on_failure; @@ -953,6 +954,7 @@ extern __thread int mysql_thread___set_query_lock_on_hostgroup; extern __thread int mysql_thread___reset_connection_algorithm; extern __thread uint32_t mysql_thread___server_capabilities; extern __thread int mysql_thread___auto_increment_delay_multiplex; +extern __thread int mysql_thread___auto_increment_delay_multiplex_timeout_ms; extern __thread int mysql_thread___handle_unknown_charset; extern __thread int mysql_thread___poll_timeout; extern __thread int mysql_thread___poll_timeout_on_failure; diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 17fbc0024..d338ea2c8 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -513,6 +513,7 @@ static char * mysql_thread_variables_names[]= { (char *)"set_query_lock_on_hostgroup", (char *)"reset_connection_algorithm", (char *)"auto_increment_delay_multiplex", + (char *)"auto_increment_delay_multiplex_timeout_ms", (char *)"long_query_time", (char *)"query_cache_size_MB", (char *)"ping_interval_server_msec", @@ -1111,6 +1112,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.set_query_lock_on_hostgroup=1; variables.reset_connection_algorithm=2; variables.auto_increment_delay_multiplex=5; + variables.auto_increment_delay_multiplex_timeout_ms=10000; variables.long_query_time=1000; variables.query_cache_size_MB=256; variables.init_connect=NULL; @@ -2155,6 +2157,7 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_int["mirror_max_queue_length"] = make_tuple(&variables.mirror_max_queue_length, 0, 1024*1024, false); // query processor and query digest VariablesPointers_int["auto_increment_delay_multiplex"] = make_tuple(&variables.auto_increment_delay_multiplex, 0, 1000000, false); + VariablesPointers_int["auto_increment_delay_multiplex_timeout_ms"] = make_tuple(&variables.auto_increment_delay_multiplex_timeout_ms, 0, 3600*1000, false); VariablesPointers_int["default_query_delay"] = make_tuple(&variables.default_query_delay, 0, 3600*1000, false); VariablesPointers_int["default_query_timeout"] = make_tuple(&variables.default_query_timeout, 1000,20*24*3600*1000, false); VariablesPointers_int["query_digests_grouping_limit"] = make_tuple(&variables.query_digests_grouping_limit, 1, 2089, false); @@ -3711,6 +3714,14 @@ void MySQL_Thread::ProcessAllSessions_MaintenanceLoop(MySQL_Session *sess, unsig } } } + + if (sess->mybe && sess->mybe->server_myds && sess->mybe->server_myds->myconn) { + MySQL_Connection* myconn = sess->mybe->server_myds->myconn; + + if (mysql_thread___auto_increment_delay_multiplex_timeout_ms != 0 && (sess_time/1000 > mysql_thread___auto_increment_delay_multiplex_timeout_ms)) { + myconn->auto_increment_delay_token = 0; + } + } } void MySQL_Thread::process_all_sessions() { @@ -3860,6 +3871,7 @@ void MySQL_Thread::refresh_variables() { mysql_thread___set_query_lock_on_hostgroup=GloMTH->get_variable_int((char *)"set_query_lock_on_hostgroup"); mysql_thread___reset_connection_algorithm=GloMTH->get_variable_int((char *)"reset_connection_algorithm"); mysql_thread___auto_increment_delay_multiplex=GloMTH->get_variable_int((char *)"auto_increment_delay_multiplex"); + mysql_thread___auto_increment_delay_multiplex_timeout_ms=GloMTH->get_variable_int((char *)"auto_increment_delay_multiplex_timeout_ms"); mysql_thread___default_max_latency_ms=GloMTH->get_variable_int((char *)"default_max_latency_ms"); mysql_thread___long_query_time=GloMTH->get_variable_int((char *)"long_query_time"); mysql_thread___query_cache_size_MB=GloMTH->get_variable_int((char *)"query_cache_size_MB"); From ea0a0521d7ea0db33709d705ca3ba8758009c7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 8 Apr 2022 20:42:58 +0200 Subject: [PATCH 2/2] Add new test for 'auto_increment_delay_multiplex*' variables --- .../test_auto_increment_delay_multiplex-t.cpp | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 test/tap/tests/test_auto_increment_delay_multiplex-t.cpp diff --git a/test/tap/tests/test_auto_increment_delay_multiplex-t.cpp b/test/tap/tests/test_auto_increment_delay_multiplex-t.cpp new file mode 100644 index 000000000..b995ee606 --- /dev/null +++ b/test/tap/tests/test_auto_increment_delay_multiplex-t.cpp @@ -0,0 +1,327 @@ +/** + * @file test_auto_increment_delay_multiplex-t.cpp + * @brief This test verifies the features 'mysql-auto_increment_delay_multiplex' and + * 'mysql-auto_increment_delay_multiplex_timeout_ms' is working properly. + * @details This test checks that: + * 1. Variables 'mysql-auto_increment_delay_multiplex' and 'mysql-auto_increment_delay_multiplex_timeout_ms' + * are present. + * 2. 'auto_increment_delay_multiplex' behaves properly for different values. + * 3. 'auto_increment_delay_multiplex_timeout_ms' behaves properly for different values. + * 4. 'auto_increment_delay_multiplex_timeout_ms' behaves properly for value '0' (disabled). + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "json.hpp" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using std::string; +using std::vector; + +int get_query_result(MYSQL* mysql, const string& query, uint64_t& out_val) { + int rc = mysql_query(mysql, query.c_str()); + if (rc != EXIT_SUCCESS) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); + return EXIT_FAILURE; + } + + MYSQL_RES* myres = mysql_store_result(mysql); + if (myres == nullptr) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); + return EXIT_FAILURE; + } + + MYSQL_ROW row = mysql_fetch_row(myres); + if (row == nullptr || row[0] == nullptr) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "mysql_fetch_row() failed"); + return EXIT_FAILURE; + } + + out_val = std::stol(row[0]); + + mysql_free_result(myres); + + return EXIT_SUCCESS; +} + +#define log_err(err_msg) fprintf(stderr, "File %s, line %d, Error: \"%s\"\n", __FILE__, __LINE__, err_msg); + +int get_conn_auto_inc_delay_token(MYSQL* proxy_mysql, int& out_auto_inc_delay) { + MYSQL_QUERY(proxy_mysql, "PROXYSQL INTERNAL SESSION"); + MYSQL_RES* my_res = mysql_store_result(proxy_mysql); + vector int_sess_res = extract_mysql_rows(my_res); + mysql_free_result(my_res); + + int cur_auto_inc_delay_mult = 0; + + if (int_sess_res.empty()) { + log_err("Empty result received from 'PROXYSQL INTERNAL SESSION'"); + return EXIT_FAILURE; + } + + try { + nlohmann::json j_int_sess = nlohmann::json::parse(int_sess_res[0][0]); + nlohmann::json backend_conns = j_int_sess.at("backends"); + nlohmann::json m_off_conn {}; + + for (const auto& j_conn : backend_conns) { + if (j_conn.find("conn") != j_conn.end()) { + m_off_conn = j_conn.at("conn"); + } + } + + if (m_off_conn.empty()) { + cur_auto_inc_delay_mult = -1; + } else { + cur_auto_inc_delay_mult = m_off_conn.at("auto_increment_delay_token").get(); + } + } catch (const std::exception& ex) { + const string err_msg { + string { "Invalid JSON received from 'PROXYSQL INTERNAL SESSION'. Ex: '" } + ex.what() + "'" + }; + log_err(err_msg.c_str()); + return EXIT_FAILURE; + } + + out_auto_inc_delay = cur_auto_inc_delay_mult; + + return EXIT_SUCCESS; +} + +uint32_t VAL_RANGE = 10; +uint32_t STEP = 5; + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + plan( + 1 + // Check variables are present + ((VAL_RANGE / STEP) + 1) * 2 + // Tests for different 'auto_increment_delay_multiplex' values + (VAL_RANGE / STEP) * 3 + // Tests for different 'auto_increment_delay_multiplex_timeout_ms' values + 3 // Tests for 'auto_increment_delay_multiplex_timeout_ms' zero value + ); + + MYSQL* proxy_mysql = mysql_init(NULL); + MYSQL* proxy_admin = mysql_init(NULL); + + if (!mysql_real_connect(proxy_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: \"%s\"\n", __FILE__, __LINE__, mysql_error(proxy_mysql)); + return EXIT_FAILURE; + } + if (!mysql_real_connect(proxy_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: \"%s\"\n", __FILE__, __LINE__, mysql_error(proxy_admin)); + return EXIT_FAILURE; + } + + MYSQL_QUERY(proxy_mysql, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY(proxy_mysql, "DROP TABLE IF EXISTS test.auto_inc_multiplex"); + MYSQL_QUERY(proxy_mysql, "CREATE TABLE IF NOT EXISTS test.auto_inc_multiplex (c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY, c2 VARCHAR(100), c3 VARCHAR(100))"); + + // 1. Check that the required variables are present + { + uint64_t auto_increment_delay_multiplex = 0; + MYSQL_QUERY(proxy_admin, "SELECT variable_value FROM global_variables WHERE variable_name='mysql-auto_increment_delay_multiplex'"); + MYSQL_RES* my_res_auto_inc_multiplex = mysql_store_result(proxy_admin); + uint64_t auto_inc_row_num = mysql_num_rows(my_res_auto_inc_multiplex); + mysql_free_result(my_res_auto_inc_multiplex); + + MYSQL_QUERY(proxy_admin, "SELECT variable_value FROM global_variables WHERE variable_name='mysql-auto_increment_delay_multiplex_timeout_ms'"); + MYSQL_RES* my_res_auto_inc_multiplex_to = mysql_store_result(proxy_admin); + uint64_t auto_inc_to_row_num = mysql_num_rows(my_res_auto_inc_multiplex_to); + mysql_free_result(my_res_auto_inc_multiplex_to); + + ok( + auto_inc_row_num == 1 && auto_inc_to_row_num == 1, + "'mysql-auto_increment_delay_multiplex' and 'mysql-auto_increment_delay_multiplex_timeout_ms' variables present" + ); + } + + // 2. Change and check 'auto_increment_delay_multiplex' behavior + { + // Disable the 'timeout' for the this check since it can be fixated now + MYSQL_QUERY(proxy_admin, "SET mysql-auto_increment_delay_multiplex_timeout_ms=0"); + + int cur_auto_inc_delay_mult = 0; + int exp_auto_inc_delay_mult = 0; + + for (uint32_t val = 0; val <= VAL_RANGE; val += STEP) { + MYSQL_QUERY(proxy_admin, string {"SET mysql-auto_increment_delay_multiplex=" + std::to_string(val)}.c_str()); + MYSQL_QUERY(proxy_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + MYSQL_QUERY(proxy_mysql, "INSERT INTO test.auto_inc_multiplex (c2, c3) VALUES ('foo','bar')"); + + for (uint32_t i = 1; i < val; i++) { + // We target the same hostgroup as before + MYSQL_QUERY(proxy_mysql, "DO 1"); + int g_res = get_conn_auto_inc_delay_token(proxy_mysql, cur_auto_inc_delay_mult); + if (g_res != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + exp_auto_inc_delay_mult = val - i; + + diag( + "'auto_increment_delay_token' should be reduced by one with each query to the same hostgroup: { Exp: %d, Act: %d }", + exp_auto_inc_delay_mult, cur_auto_inc_delay_mult + ); + if (cur_auto_inc_delay_mult != exp_auto_inc_delay_mult) { + break; + } + } + + ok( + exp_auto_inc_delay_mult == cur_auto_inc_delay_mult, + "'auto_increment_delay_token' should be reduced by one with each query to the same hostgroup: { Exp: %d, Act: %d }", + exp_auto_inc_delay_mult, cur_auto_inc_delay_mult + ); + + if (cur_auto_inc_delay_mult != exp_auto_inc_delay_mult) { + break; + } + + // Check that the connection is no longer attached when `auto_increment_delay_token` reaches `0`. + MYSQL_QUERY(proxy_mysql, "DO 1"); + int g_res = get_conn_auto_inc_delay_token(proxy_mysql, cur_auto_inc_delay_mult); + if (g_res != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + ok( + cur_auto_inc_delay_mult == -1, + "Connection should no longer be attached when 'auto_increment_delay_token' reaches '0'" + ); + } + } + + const auto check_auto_increment_to = [] (MYSQL* proxy_admin, MYSQL* proxy_mysql, uint32_t f_auto_incr_val, uint64_t poll_to, uint32_t auto_inc_delay_to) -> int { + int cur_auto_inc_delay_mult = 0; + const string set_auto_inc_to_query { + "SET mysql-auto_increment_delay_multiplex_timeout_ms=" + std::to_string(auto_inc_delay_to) + }; + MYSQL_QUERY(proxy_admin, set_auto_inc_to_query.c_str()); + MYSQL_QUERY(proxy_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + MYSQL_QUERY(proxy_mysql, "INSERT INTO test.auto_inc_multiplex (c2, c3) VALUES ('foo','bar')"); + + // Wait at least '500' milliseconds over the poll period + usleep((poll_to + 500) * 1000); + uint32_t waited = poll_to + 500; + + int g_res = get_conn_auto_inc_delay_token(proxy_mysql, cur_auto_inc_delay_mult); + if (g_res != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + ok( + f_auto_incr_val == cur_auto_inc_delay_mult, + "'auto_increment_delay_token' val unchanged before timeout:" + " { Exp: %d, Act: %d, Timeout: %d, Waited: %d }", + f_auto_incr_val, cur_auto_inc_delay_mult, auto_inc_delay_to, waited + ); + + uint32_t DEF_TIMEOUT = 5; + uint32_t timeout = auto_inc_delay_to == 0 ? DEF_TIMEOUT : auto_inc_delay_to; + + // Wait timeout and check that the connection is detached + usleep((timeout + poll_to + 500) * 1000); + waited = (timeout + poll_to + 500); + + // Check 'auto_increment_delay_token' is '0' after timeout + g_res = get_conn_auto_inc_delay_token(proxy_mysql, cur_auto_inc_delay_mult); + if (g_res != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + if (auto_inc_delay_to == 0) { + ok( + f_auto_incr_val == cur_auto_inc_delay_mult, + "'auto_increment_delay_token' val should remain unchanged '%d' after default timeout:" + " { Exp: %d, Act: %d, mysql-auto_increment_delay_multiplex_timeout_ms: %d, Timeout: %d, Waited: %d }", + f_auto_incr_val, f_auto_incr_val, cur_auto_inc_delay_mult, auto_inc_delay_to, timeout, waited + ); + } else { + ok( + 0 == cur_auto_inc_delay_mult, + "'auto_increment_delay_token' val should be '0' after timeout:" + " { Exp: %d, Act: %d, Timeout: %d, Waited: %d }", + 0, cur_auto_inc_delay_mult, auto_inc_delay_to, waited + ); + } + + MYSQL_QUERY(proxy_mysql, "DO 1"); + uint32_t old_auto_inc_delay_mult = cur_auto_inc_delay_mult; + g_res = get_conn_auto_inc_delay_token(proxy_mysql, cur_auto_inc_delay_mult); + if (g_res != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + if (auto_inc_delay_to == 0) { + ok( + old_auto_inc_delay_mult == cur_auto_inc_delay_mult + 1, + "'auto_increment_delay_token' should be reduced by one because timeout is meaningless: { Old: %d, New: %d }", + old_auto_inc_delay_mult, cur_auto_inc_delay_mult + ); + } else { + ok( + cur_auto_inc_delay_mult == -1, + "Connection should no longer be attached when 'auto_increment_delay_token' reaches '0'" + ); + } + + return EXIT_SUCCESS; + }; + + // 3. Change and check 'auto_increment_delay_multiplex_timeout_ms' behavior + { + // Set the default 'mysql-auto_increment_delay_multiplex' since it's no longer relevant + const int f_auto_incr_val = 5; + const string set_auto_inc_query { + "SET mysql-auto_increment_delay_multiplex=" + std::to_string(f_auto_incr_val) + }; + MYSQL_QUERY(proxy_admin, set_auto_inc_query.c_str()); + + uint64_t poll_timeout = 0; + const string q_poll_timeout { "SELECT variable_value FROM global_variables WHERE variable_name='mysql-poll_timeout'" }; + int g_res = get_query_result(proxy_admin, q_poll_timeout.c_str(), poll_timeout); + if (g_res != EXIT_SUCCESS) { return EXIT_FAILURE; } + + // Check that different values for 'auto_increment_delay_multiplex_timeout_ms' behave properly + for (uint32_t auto_inc_delay_to = 5; auto_inc_delay_to <= VAL_RANGE; auto_inc_delay_to += STEP) { + uint32_t _auto_inc_delay_to = auto_inc_delay_to * 1000; + + if (_auto_inc_delay_to < (poll_timeout + 500)) { + diag( + "Error: Supplied 'auto_increment_delay_multiplex_timeout_ms' too small: { Act: %d, Min: %ld }", + _auto_inc_delay_to, poll_timeout + 500 + ); + return EXIT_FAILURE; + } + + int c_res = check_auto_increment_to(proxy_admin, proxy_mysql, f_auto_incr_val, poll_timeout, _auto_inc_delay_to); + if (c_res != EXIT_SUCCESS) { return EXIT_FAILURE; } + } + + // Check that value '0' for 'auto_increment_delay_multiplex_timeout_ms' disables the feature + int c_res = check_auto_increment_to(proxy_admin, proxy_mysql, f_auto_incr_val, poll_timeout, 0); + if (c_res != EXIT_SUCCESS) { return EXIT_FAILURE; } + } + +cleanup: + + mysql_close(proxy_admin); + mysql_close(proxy_mysql); + + return exit_status(); +}