From 4f73331defd1f6ca0d8e7faafe92d23a96c70cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 28 Jun 2023 16:35:49 +0200 Subject: [PATCH] Add functions for breaking down 'commit' checksum generation --- include/MySQL_HostGroups_Manager.h | 3 + include/proxysql_glovars.hpp | 14 +-- include/proxysql_utils.h | 35 ++++++ lib/MySQL_HostGroups_Manager.cpp | 171 +++++++++++++++++++++++++++++ lib/ProxySQL_Cluster.cpp | 11 -- lib/proxysql_utils.cpp | 21 ++++ 6 files changed, 232 insertions(+), 23 deletions(-) diff --git a/include/MySQL_HostGroups_Manager.h b/include/MySQL_HostGroups_Manager.h index 1d32b6a3e..b7549b3cf 100644 --- a/include/MySQL_HostGroups_Manager.h +++ b/include/MySQL_HostGroups_Manager.h @@ -759,7 +759,10 @@ class MySQL_HostGroups_Manager { void wrlock(); void wrunlock(); int servers_add(SQLite3_result *resultset); + std::string gen_global_mysql_servers_checksum(); bool commit(SQLite3_result* runtime_mysql_servers = nullptr, const std::string& checksum = "", const time_t epoch = 0); + void commit_generate_mysql_servers_table(SQLite3_result* runtime_mysql_servers = nullptr); + void commit_update_checksum_from_mysql_servers(SpookyHash& myhash, bool& init); void commit_update_checksums_from_tables(SpookyHash& myhash, bool& init); void CUCFT1(SpookyHash& myhash, bool& init, const string& TableName, const string& ColumnName, uint64_t& raw_checksum); // used by commit_update_checksums_from_tables() diff --git a/include/proxysql_glovars.hpp b/include/proxysql_glovars.hpp index e2eb14c8a..0653f10d0 100644 --- a/include/proxysql_glovars.hpp +++ b/include/proxysql_glovars.hpp @@ -5,27 +5,17 @@ #define CLUSTER_SYNC_INTERFACES_MYSQL "('mysql-interfaces')" #include +#include #include #include "configfile.hpp" #include "proxy_defines.h" +#include "proxysql_utils.h" namespace ez { class ezOptionParser; }; -/** - * @brief Helper function used to replace spaces and zeros by '0' char in the supplied checksum buffer. - * @param checksum Input buffer containing the checksum. - */ -inline void replace_checksum_zeros(char* checksum) { - for (int i=2; i<18; i++) { - if (checksum[i]==' ' || checksum[i]==0) { - checksum[i]='0'; - } - } -} - #ifndef ProxySQL_Checksum_Value_LENGTH #define ProxySQL_Checksum_Value_LENGTH 20 #endif diff --git a/include/proxysql_utils.h b/include/proxysql_utils.h index 5ccbb4eb7..3660bb0a6 100644 --- a/include/proxysql_utils.h +++ b/include/proxysql_utils.h @@ -2,12 +2,19 @@ #define __PROXYSQL_UTILS_H #include +#include #include #include #include #include #include +#include "sqlite3db.h" + +#ifndef ProxySQL_Checksum_Value_LENGTH +#define ProxySQL_Checksum_Value_LENGTH 20 +#endif + #ifndef ETIME // ETIME is not defined on FreeBSD // ETIME is used internaly to report API timer expired @@ -220,4 +227,32 @@ std::string generate_multi_rows_query(int rows, int params); */ std::string rand_str(std::size_t strSize); +/** + * @brief Helper function used to replace spaces and zeros by '0' char in the supplied checksum buffer. + * @param checksum Input buffer containing the checksum. + */ +inline void replace_checksum_zeros(char* checksum) { + for (int i=2; i<18; i++) { + if (checksum[i]==' ' || checksum[i]==0) { + checksum[i]='0'; + } + } +} + +/** + * @brief Generates a ProxySQL checksum as a string from the supplied integer hash. + * @param hash The integer hash to be formated as a string. + * @return String representation of the supplied hash. + */ +std::string get_checksum_from_hash(uint64_t hash); + +/** + * @brief Remove the rows from the resultset matching the supplied predicate. + * @param resultset The resultset which rows are to be removed. + * @param pred Predicate that should return 'true' for the rows to be removed. + */ +void remove_sqlite3_resultset_rows( + std::unique_ptr& resultset, const std::function& pred +); + #endif diff --git a/lib/MySQL_HostGroups_Manager.cpp b/lib/MySQL_HostGroups_Manager.cpp index 353d7e451..c996b320b 100644 --- a/lib/MySQL_HostGroups_Manager.cpp +++ b/lib/MySQL_HostGroups_Manager.cpp @@ -1621,6 +1621,177 @@ void MySQL_HostGroups_Manager::commit_update_checksums_from_tables(SpookyHash& m CUCFT1(myhash,init,"mysql_hostgroup_attributes","hostgroup_id", table_resultset_checksum[HGM_TABLES::MYSQL_HOSTGROUP_ATTRIBUTES]); } +const char MYSQL_SERVERS_CHECKSUM_QUERY[] { + "SELECT hostgroup_id, hostname, port, gtid_port, CASE WHEN status=0 OR status=1 OR status=4 THEN 0 ELSE status END status," + " weight, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment FROM mysql_servers" + " WHERE status<>3 ORDER BY hostgroup_id, hostname, port" +}; + +/** + * @brief Generates a resultset which is used to compute the current 'mysql_servers' checksum. + * @details The resultset should report all servers status as ONLINE(0), with the exception of 'OFFLINE_HARD'. + * Servers with this status should be excluded from the resultset. + * @param mydb The db in which to perform the query, typically 'MySQL_HostGroups_Manager::mydb'. + * @return An SQLite3 resultset for the query 'MYSQL_SERVERS_CHECKSUM_QUERY'. + */ +unique_ptr get_mysql_servers_checksum_resultset(SQLite3DB* mydb) { + char* error = nullptr; + int cols = 0; + int affected_rows = 0; + SQLite3_result* resultset = nullptr; + + mydb->execute_statement(MYSQL_SERVERS_CHECKSUM_QUERY, &error, &cols, &affected_rows, &resultset); + + if (error) { + proxy_error("Checksum generation query for 'mysql_servers' failed with error '%s'\n", error); + assert(0); + } + + return unique_ptr(resultset); +} + +/** + * @brief Generates a resultset holding the current Admin 'runtime_mysql_servers' as reported by Admin. + * @param mydb The db in which to perform the query, typically 'MySQL_HostGroups_Manager::mydb'. + * @return An SQLite3 resultset for the query 'MYHGM_GEN_ADMIN_RUNTIME_SERVERS'. + */ +unique_ptr get_admin_runtime_mysql_servers(SQLite3DB* mydb) { + char* error = nullptr; + int cols = 0; + int affected_rows = 0; + SQLite3_result* resultset = nullptr; + + mydb->execute_statement(MYHGM_GEN_ADMIN_RUNTIME_SERVERS, &error, &cols, &affected_rows, &resultset); + + return unique_ptr(resultset); +} + +/** + * @brief Removes rows with 'OFFLINE_HARD' servers in the supplied resultset. + * @details It assumes that the supplied resultset is generated via 'MYHGM_GEN_ADMIN_RUNTIME_SERVERS'. + * @param resultset The resultset from which rows are to be removed. + */ +void remove_resultset_offline_hard_servers(unique_ptr& resultset) { + if (resultset->columns < 5) { + return; + } + + const auto is_offline = [] (SQLite3_row* row) { + if (strcasecmp(row->fields[4], "OFFLINE_HARD") == 0) { + return true; + } else { + return false; + } + }; + + remove_sqlite3_resultset_rows(resultset, is_offline); +} + +/** + * @brief Updates the global 'mysql_servers' checksum. + * @details If the new computed checksum matches the supplied 'cluster_checksum', the epoch used for the + * checksum, is the supplied epoch instead of current time. This way we ensure the preservation of the + * checksum and epoch fetched from the ProxySQL cluster peer node. + * + * @param new_checksum The new computed checksum by ProxySQL. + * @param old_checksum A checksum, previously fetched from ProxySQL cluster. Should be left empty if the + * update isn't considering this scenario. + * @param epoch The epoch to be preserved in case the supplied 'cluster_checksum' matches the new computed + * checksum. + */ +void update_glovars_mysql_servers_checksum( + const std::string& new_checksum, const std::string& cluster_checksum = "", const time_t epoch = 0 +) { + GloVars.checksums_values.mysql_servers.set_checksum(const_cast(new_checksum.c_str())); + GloVars.checksums_values.mysql_servers.version++; + + time_t t = time(NULL); + bool computed_checksum_matches { + cluster_checksum != "" && GloVars.checksums_values.mysql_servers.checksum == cluster_checksum + }; + + if (epoch != 0 && computed_checksum_matches) { + GloVars.checksums_values.mysql_servers.epoch = epoch; + } else { + GloVars.checksums_values.mysql_servers.epoch = t; + } + + GloVars.checksums_values.updates_cnt++; + GloVars.generate_global_checksum(); + GloVars.epoch_version = t; +} + +void MySQL_HostGroups_Manager::commit_generate_mysql_servers_table(SQLite3_result* runtime_mysql_servers) { + mydb->execute("DELETE FROM mysql_servers"); + generate_mysql_servers_table(); + + if (runtime_mysql_servers == nullptr) { + unique_ptr resultset { get_admin_runtime_mysql_servers(mydb) }; + + // Remove 'OFFLINE_HARD' servers since they are not relevant to propagate to other Cluster nodes, or + // relevant for checksum computation. If this step isn't performed, this could cause mismatching + // checksums between different primary nodes in a ProxySQL cluster, since OFFLINE_HARD servers + // preservation depends on unknown and unpredictable connections conditions. + remove_resultset_offline_hard_servers(resultset); + save_runtime_mysql_servers(resultset.release()); + } else { + save_runtime_mysql_servers(runtime_mysql_servers); + } +} + +void MySQL_HostGroups_Manager::commit_update_checksum_from_mysql_servers(SpookyHash& myhash, bool& init) { + unique_ptr mysrvs_checksum_resultset { get_mysql_servers_checksum_resultset(mydb) }; + + // Reset table checksum value before recomputing + table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS] = 0; + + if (mysrvs_checksum_resultset) { + if (mysrvs_checksum_resultset->rows_count) { + if (init == false) { + init = true; + myhash.Init(19,3); + } + uint64_t hash1_ = mysrvs_checksum_resultset->raw_checksum(); + table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS] = hash1_; + + myhash.Update(&hash1_, sizeof(hash1_)); + proxy_info("Checksum for table %s is 0x%lX\n", "mysql_servers", hash1_); + } + } else { + proxy_info("Checksum for table %s is 0x%lX\n", "mysql_servers", (long unsigned int)0); + } +} + +std::string MySQL_HostGroups_Manager::gen_global_mysql_servers_checksum() { + SpookyHash global_hash; + bool init = false; + + // Regenerate 'mysql_servers' and generate new checksum, initialize the new 'global_hash' + commit_update_checksum_from_mysql_servers(global_hash, init); + + // Complete the hash with the rest of the unchanged modules + for (size_t i = 0; i < table_resultset_checksum.size(); i++) { + uint64_t hash_val = table_resultset_checksum[i]; + + if (i != HGM_TABLES::MYSQL_SERVERS && hash_val != 0) { + if (init == false) { + init = true; + global_hash.Init(19, 3); + } + + global_hash.Update(&hash_val, sizeof(hash_val)); + } + } + + uint64_t hash_1 = 0, hash_2 = 0; + if (init) { + global_hash.Final(&hash_1,&hash_2); + } + + string mysrvs_checksum { get_checksum_from_hash(hash_1) }; + return mysrvs_checksum; +} + bool MySQL_HostGroups_Manager::commit( SQLite3_result* runtime_mysql_servers, const std::string& checksum, const time_t epoch ) { diff --git a/lib/ProxySQL_Cluster.cpp b/lib/ProxySQL_Cluster.cpp index 03ad96238..1404cc13d 100644 --- a/lib/ProxySQL_Cluster.cpp +++ b/lib/ProxySQL_Cluster.cpp @@ -964,17 +964,6 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { } } -std::string get_checksum_from_hash(uint64_t hash) { - uint32_t d32[2] = { 0 }; - memcpy(&d32, &hash, sizeof(hash)); - - vector s_buf(20, 0); - sprintf(&s_buf[0],"0x%0X%0X", d32[0], d32[1]); - replace_checksum_zeros(&s_buf[0]); - - return string { &s_buf.front() }; -} - /** * @brief Computes the checksum from a MySQL resultset in the same we already do in 'SQLite3_result::raw_checksum'. * @details For each received column computing the field length via 'strlen' is required, this is because we diff --git a/lib/proxysql_utils.cpp b/lib/proxysql_utils.cpp index f30f1e5b8..933b3600d 100644 --- a/lib/proxysql_utils.cpp +++ b/lib/proxysql_utils.cpp @@ -14,7 +14,9 @@ #include #include +using std::function; using std::string; +using std::unique_ptr; using std::vector; __attribute__((__format__ (__printf__, 1, 2))) @@ -436,3 +438,22 @@ string rand_str(std::size_t strSize) { return random_str; } } + +std::string get_checksum_from_hash(uint64_t hash) { + uint32_t d32[2] = { 0 }; + memcpy(&d32, &hash, sizeof(hash)); + + vector s_buf(ProxySQL_Checksum_Value_LENGTH, 0); + sprintf(&s_buf[0],"0x%0X%0X", d32[0], d32[1]); + replace_checksum_zeros(&s_buf[0]); + + return string { &s_buf.front() }; +} + +void remove_sqlite3_resultset_rows( + unique_ptr& resultset, const function& pred +) { + const auto remove_it { std::remove_if(resultset->rows.begin(), resultset->rows.end(), pred) }; + resultset->rows.erase(remove_it, resultset->rows.end()); + resultset->rows_count = resultset->rows.size(); +}