Merge pull request #4086 from sysown/v2.x-1486

Add soft TTL to Query Cache entries
pull/4092/head
René Cannaò 3 years ago committed by GitHub
commit 62182da20b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -573,6 +573,7 @@ class MySQL_Threads_Handler
char * ssl_p2s_crl;
char * ssl_p2s_crlpath;
int query_cache_size_MB;
int query_cache_soft_ttl_pct;
int min_num_servers_lantency_awareness;
int aurora_max_lag_ms_only_read_from_replicas;
bool stats_time_backend_query;

@ -856,6 +856,7 @@ __thread int mysql_thread___client_host_error_counts;
/* variables used for Query Cache */
__thread int mysql_thread___query_cache_size_MB;
__thread int mysql_thread___query_cache_soft_ttl_pct;
/* variables used for SSL , from proxy to server (p2s) */
__thread char * mysql_thread___ssl_p2s_ca;
@ -1021,6 +1022,7 @@ extern __thread int mysql_thread___client_host_error_counts;
/* variables used for Query Cache */
extern __thread int mysql_thread___query_cache_size_MB;
extern __thread int mysql_thread___query_cache_soft_ttl_pct;
/* variables used for SSL , from proxy to server (p2s) */
extern __thread char * mysql_thread___ssl_p2s_ca;

@ -30,6 +30,7 @@ struct __QC_entry_t {
unsigned long long create_ms; // when the entry was created, monotonic, millisecond granularity
unsigned long long expire_ms; // when the entry will expire, monotonic , millisecond granularity
unsigned long long access_ms; // when the entry was read last , monotonic , millisecond granularity
bool refreshing; // true when a client will hit the backend to refresh the entry
uint32_t column_eof_pkt_offset = 0;
uint32_t row_eof_pkt_offset = 0;
uint32_t ok_pkt_offset = 0;
@ -89,7 +90,6 @@ class Query_Cache {
~Query_Cache();
void print_version();
bool set(uint64_t user_hash, const unsigned char *kp, uint32_t kl, unsigned char *vp, uint32_t vl, unsigned long long create_ms, unsigned long long curtime_ms, unsigned long long expire_ms, bool deprecate_eof_active);
bool set(uint64_t , const unsigned char *, uint32_t, unsigned char *, uint32_t, unsigned long long, unsigned long long, unsigned long long);
unsigned char * get(uint64_t , const unsigned char *, const uint32_t, uint32_t *, unsigned long long, unsigned long long, bool deprecate_eof_active);
uint64_t flush();
SQLite3_result * SQL3_getStats();

@ -529,6 +529,7 @@ static char * mysql_thread_variables_names[]= {
(char *)"auto_increment_delay_multiplex_timeout_ms",
(char *)"long_query_time",
(char *)"query_cache_size_MB",
(char *)"query_cache_soft_ttl_pct",
(char *)"ping_interval_server_msec",
(char *)"ping_timeout_server",
(char *)"default_schema",
@ -1139,6 +1140,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() {
variables.auto_increment_delay_multiplex_timeout_ms=10000;
variables.long_query_time=1000;
variables.query_cache_size_MB=256;
variables.query_cache_soft_ttl_pct=0;
variables.init_connect=NULL;
variables.ldap_user_variable=NULL;
variables.add_ldap_user_comment=NULL;
@ -2252,6 +2254,7 @@ char ** MySQL_Threads_Handler::get_variables_list() {
VariablesPointers_int["max_transaction_idle_time"] = make_tuple(&variables.max_transaction_idle_time, 1000, 20*24*3600*1000, false);
VariablesPointers_int["max_transaction_time"] = make_tuple(&variables.max_transaction_time, 1000, 20*24*3600*1000, false);
VariablesPointers_int["query_cache_size_mb"] = make_tuple(&variables.query_cache_size_MB, 0, 1024*10240, false);
VariablesPointers_int["query_cache_soft_ttl_pct"] = make_tuple(&variables.query_cache_soft_ttl_pct, 0, 100, false);
#ifdef IDLE_THREADS
VariablesPointers_int["session_idle_ms"] = make_tuple(&variables.session_idle_ms, 1, 3600*1000, false);
#endif // IDLE_THREADS
@ -3986,6 +3989,7 @@ void MySQL_Thread::refresh_variables() {
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");
mysql_thread___query_cache_soft_ttl_pct=GloMTH->get_variable_int((char *)"query_cache_soft_ttl_pct");
mysql_thread___ping_interval_server_msec=GloMTH->get_variable_int((char *)"ping_interval_server_msec");
mysql_thread___ping_timeout_server=GloMTH->get_variable_int((char *)"ping_timeout_server");
mysql_thread___shun_on_failures=GloMTH->get_variable_int((char *)"shun_on_failures");

@ -653,22 +653,35 @@ unsigned char * Query_Cache::get(uint64_t user_hash, const unsigned char *kp, co
if (entry!=NULL) {
unsigned long long t=curtime_ms;
if (entry->expire_ms > t && entry->create_ms + cache_ttl > t) {
THR_UPDATE_CNT(__thr_cntGetOK,Glo_cntGetOK,1,1);
THR_UPDATE_CNT(__thr_dataOUT,Glo_dataOUT,entry->length,1);
if (deprecate_eof_active && entry->column_eof_pkt_offset) {
result = eof_to_ok_packet(entry);
*lv = entry->length + eof_to_ok_dif;
} else if (!deprecate_eof_active && entry->ok_pkt_offset){
result = ok_to_eof_packet(entry);
*lv = entry->length + ok_to_eof_dif;
if (
mysql_thread___query_cache_soft_ttl_pct && !entry->refreshing &&
entry->create_ms + cache_ttl * mysql_thread___query_cache_soft_ttl_pct / 100 <= t
) {
// If the Query Cache entry reach the soft_ttl but do not reach
// the cache_ttl, the next query hit the backend and refresh
// the entry, including ResultSet and TTLs. While the
// refreshing is in process, other queries keep using the "old"
// Query Cache entry.
// soft_ttl_pct with value 0 and 100 disables the functionality.
entry->refreshing = true;
} else {
result = (unsigned char *)malloc(entry->length);
memcpy(result, entry->value, entry->length);
*lv = entry->length;
}
THR_UPDATE_CNT(__thr_cntGetOK,Glo_cntGetOK,1,1);
THR_UPDATE_CNT(__thr_dataOUT,Glo_dataOUT,entry->length,1);
if (deprecate_eof_active && entry->column_eof_pkt_offset) {
result = eof_to_ok_packet(entry);
*lv = entry->length + eof_to_ok_dif;
} else if (!deprecate_eof_active && entry->ok_pkt_offset){
result = ok_to_eof_packet(entry);
*lv = entry->length + ok_to_eof_dif;
} else {
result = (unsigned char *)malloc(entry->length);
memcpy(result, entry->value, entry->length);
*lv = entry->length;
}
if (t > entry->access_ms) entry->access_ms=t;
if (t > entry->access_ms) entry->access_ms=t;
}
}
__sync_fetch_and_sub(&entry->ref_count,1);
}
@ -683,6 +696,7 @@ bool Query_Cache::set(uint64_t user_hash, const unsigned char *kp, uint32_t kl,
entry->column_eof_pkt_offset=0;
entry->row_eof_pkt_offset=0;
entry->ok_pkt_offset=0;
entry->refreshing=false;
// Find the first EOF location
unsigned char* it = vp;

@ -0,0 +1,208 @@
/**
* @file test_query_cache_soft_ttl_pct-t.cpp
* @brief This test that query cache entries are refreshed when soft ttl is
* reached.
* @details This test configures a query rule with cache and configures the
* global variable mysql-query_cache_soft_ttl_pct. Then, caches a
* "SELECT SLEEP(1) and creates 4 threads to send this same query when the soft
* ttl have been reached. Finally, checks that only one of the threads has hit
* the hostgroup looking at how long it has taken for each thread to execute
* the query, and looking in the table "stats_mysql_query_digest"
*/
#include <unistd.h>
#include <iostream>
#include <mysql.h>
#include <vector>
#include <string>
#include <chrono>
#include <thread>
#include <map>
#include "proxysql_utils.h"
#include "command_line.h"
#include "utils.h"
#include "tap.h"
using std::vector;
using std::string;
double timer_result_one = 0;
double timer_result_two = 0;
double timer_result_three = 0;
double timer_result_four = 0;
const string DUMMY_QUERY = "SELECT SLEEP(1)";
class timer {
public:
std::chrono::time_point<std::chrono::high_resolution_clock> lastTime;
timer() : lastTime(std::chrono::high_resolution_clock::now()) {}
inline double elapsed() {
std::chrono::time_point<std::chrono::high_resolution_clock> thisTime = std::chrono::high_resolution_clock::now();
double deltaTime = std::chrono::duration<double>(thisTime-lastTime).count();
lastTime = thisTime;
return deltaTime;
}
};
void run_dummy_query(
const char* host, const char* username, const char* password, const int port, double* timer_result
) {
MYSQL* proxy_mysql = mysql_init(NULL);
if (!mysql_real_connect(proxy_mysql, host, username, password, NULL, port, NULL, 0)) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_mysql));
*timer_result = -1.0;
return;
}
int soft_ttl_seconds = 2;
for (int i = 0; i < 2; i++) {
sleep(1);
timer stopwatch;
int err = mysql_query(proxy_mysql, DUMMY_QUERY.c_str());
if (err) {
diag("Failed to executed query `%s`", DUMMY_QUERY.c_str());
*timer_result = -1.0;
mysql_close(proxy_mysql);
return;
}
*timer_result += stopwatch.elapsed();
MYSQL_RES* res = NULL;
res = mysql_store_result(proxy_mysql);
mysql_free_result(res);
}
mysql_close(proxy_mysql);
}
const string STATS_QUERY_DIGEST =
"SELECT hostgroup, SUM(count_star) FROM stats_mysql_query_digest "
"WHERE digest_text = 'SELECT SLEEP(?)' GROUP BY hostgroup";
std::map<string, int> get_digest_stats_dummy_query(MYSQL* proxy_admin) {
diag("Running: %s", STATS_QUERY_DIGEST.c_str());
mysql_query(proxy_admin, STATS_QUERY_DIGEST.c_str());
std::map<string, int> stats {{"cache", 0}, {"hostgroups", 0}}; // {hostgroup, count_star}
MYSQL_RES* res = mysql_store_result(proxy_admin);
MYSQL_ROW row;
while (row = mysql_fetch_row(res)) {
if (atoi(row[0]) == -1)
stats["cache"] += atoi(row[1]);
else
stats["hostgroups"] += atoi(row[1]);
}
mysql_free_result(res);
return stats;
}
int main(int argc, char** argv) {
CommandLine cl;
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return EXIT_FAILURE;
}
MYSQL* proxy_admin = mysql_init(NULL);
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;
}
vector<string> admin_queries = {
"UPDATE mysql_query_rules SET cache_ttl = 4000 WHERE rule_id = 2",
"LOAD MYSQL QUERY RULES TO RUNTIME",
"UPDATE global_variables SET variable_value=50 WHERE variable_name='mysql-query_cache_soft_ttl_pct'",
"LOAD MYSQL VARIABLES TO RUNTIME",
};
for (const auto &query : admin_queries) {
diag("Running: %s", query.c_str());
MYSQL_QUERY(proxy_admin, query.c_str());
}
std::map<string, int> stats_before = get_digest_stats_dummy_query(proxy_admin);
MYSQL* proxy_mysql = 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));
mysql_close(proxy_admin);
return EXIT_FAILURE;
}
diag("Running: %s", DUMMY_QUERY.c_str());
MYSQL_QUERY(proxy_mysql, DUMMY_QUERY.c_str()); // We want to cache query "SELECT SLEEP(1)"
MYSQL_RES* res = NULL;
res = mysql_store_result(proxy_mysql);
mysql_free_result(res);
mysql_close(proxy_mysql);
std::thread client_one(
run_dummy_query, cl.host, cl.username, cl.password, cl.port, &timer_result_one
);
std::thread client_two(
run_dummy_query, cl.host, cl.username, cl.password, cl.port, &timer_result_two
);
std::thread client_three(
run_dummy_query, cl.host, cl.username, cl.password, cl.port, &timer_result_three
);
std::thread client_four(
run_dummy_query, cl.host, cl.username, cl.password, cl.port, &timer_result_four
);
client_one.join();
client_two.join();
client_three.join();
client_four.join();
if (
timer_result_one == -1.0 ||
timer_result_two == -1.0 ||
timer_result_three == -1.0 ||
timer_result_four == -1.0
) {
fprintf(
stderr, "File %s, line %d, Error: one or more threads finished with errors", __FILE__, __LINE__
);
mysql_close(proxy_admin);
return EXIT_FAILURE;
}
// Get the number of clients that take more 1 second or more to execute the
// query by casting double to int.
int num_slow_clients =
(int)(timer_result_one + timer_result_two + timer_result_three + timer_result_four);
int expected_num_slow_clients = 1;
ok(
num_slow_clients == expected_num_slow_clients,
"Only one client should take 1 second to execute the query. "
"Number of clients that take more than 1 second - Exp:'%d', Act:'%d'",
expected_num_slow_clients, num_slow_clients
);
std::map<string, int> stats_after = get_digest_stats_dummy_query(proxy_admin);
std::map<string, int> expected_stats {{"cache", 7}, {"hostgroups", 2}};
ok(
expected_stats["cache"] == stats_after["cache"] - stats_before["cache"],
"Query cache should have been hit %d times. Number of hits - Exp:'%d', Act:'%d'",
expected_stats["cache"], expected_stats["cache"], stats_after["cache"] - stats_before["cache"]
);
ok(
expected_stats["hostgroups"] == stats_after["hostgroups"] - stats_before["hostgroups"],
"Hostgroups should have been hit %d times. Number of hits - Exp:'%d', Act:'%d'",
expected_stats["hostgroups"], expected_stats["hostgroups"],
stats_after["hostgroups"] - stats_before["hostgroups"]
);
return exit_status();
}
Loading…
Cancel
Save