mirror of https://github.com/sysown/proxysql
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
7.6 KiB
218 lines
7.6 KiB
/**
|
|
* @file pgsql-query_cache_soft_ttl_pct-t.cpp
|
|
* @brief This test verifies that query cache entries are refreshed upon reaching the soft TTL.
|
|
* @details The test sets up a query rule with caching and configures the global variable
|
|
* `pgsql-query_cache_soft_ttl_pct`. It then executes a query: "SELECT PG_SLEEP(1)",
|
|
* and creates four threads to execute this same query once the soft TTL has been reached.
|
|
* Finally, it checks that only one of the threads has hit the hostgroup by examining
|
|
* the execution time of each thread and querying the "stats_pgsql_query_digest" table.
|
|
*/
|
|
#include <unistd.h>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include "libpq-fe.h"
|
|
#include "command_line.h"
|
|
#include "tap.h"
|
|
#include "utils.h"
|
|
|
|
#define NUM_QUERIES 3
|
|
#define NUM_THREADS 8
|
|
|
|
double timer_results[NUM_THREADS];
|
|
const char* DUMMY_QUERY = "SELECT PG_SLEEP(1)";
|
|
|
|
CommandLine cl;
|
|
|
|
class Timer {
|
|
public:
|
|
Timer() : lastTime(std::chrono::high_resolution_clock::now()) {}
|
|
|
|
double elapsed() {
|
|
auto currentTime = std::chrono::high_resolution_clock::now();
|
|
double deltaTime = std::chrono::duration<double>(currentTime - lastTime).count();
|
|
lastTime = currentTime;
|
|
return deltaTime;
|
|
}
|
|
|
|
private:
|
|
std::chrono::time_point<std::chrono::high_resolution_clock> lastTime;
|
|
};
|
|
|
|
|
|
enum ConnType {
|
|
ADMIN,
|
|
BACKEND
|
|
};
|
|
|
|
PGconn* createNewConnection(ConnType conn_type, bool with_ssl) {
|
|
std::stringstream ss;
|
|
const char* host = (conn_type == BACKEND) ? cl.pgsql_host : cl.pgsql_admin_host;
|
|
int port = (conn_type == BACKEND) ? cl.pgsql_port : cl.pgsql_admin_port;
|
|
const char* username = (conn_type == BACKEND) ? cl.pgsql_username : cl.admin_username;
|
|
const char* password = (conn_type == BACKEND) ? cl.pgsql_password : cl.admin_password;
|
|
|
|
ss << "host=" << host << " port=" << port;
|
|
ss << " user=" << username << " password=" << password;
|
|
ss << (with_ssl ? " sslmode=require" : " sslmode=disable");
|
|
|
|
PGconn* conn = PQconnectdb(ss.str().c_str());
|
|
if (PQstatus(conn) != CONNECTION_OK) {
|
|
diag("Connection failed to '%s': %s", (conn_type == BACKEND ? "Backend" : "Admin"), PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
return nullptr;
|
|
}
|
|
return conn;
|
|
}
|
|
|
|
void runDummyQuery(double* timer_result) {
|
|
std::unique_ptr<PGconn, decltype(&PQfinish)> pg_conn(createNewConnection(BACKEND, false), &PQfinish);
|
|
if (!pg_conn) {
|
|
*timer_result = -1.0;
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < NUM_QUERIES; i++) {
|
|
// execute the query at 1.4 , 2.8 and 4.2 second
|
|
// Running 3 queries we verify:
|
|
// 1. the cache before the threshold
|
|
// 2. the cache after the threshold
|
|
// 3. that the cache has been refreshed
|
|
usleep(1400000); // Sleep for 1.4 seconds
|
|
|
|
Timer stopwatch;
|
|
PGresult* res = PQexec(pg_conn.get(), DUMMY_QUERY);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
|
|
diag("Failed to execute query `%s`: %s", DUMMY_QUERY, PQerrorMessage(pg_conn.get()));
|
|
*timer_result = -1.0;
|
|
PQclear(res);
|
|
return;
|
|
}
|
|
*timer_result += stopwatch.elapsed();
|
|
PQclear(res);
|
|
}
|
|
}
|
|
|
|
const std::string STATS_QUERY_DIGEST =
|
|
"SELECT hostgroup, SUM(count_star) FROM stats_pgsql_query_digest "
|
|
"WHERE digest_text = 'SELECT PG_SLEEP(?)' GROUP BY hostgroup";
|
|
|
|
std::map<std::string, int> getDigestStatsDummyQuery(PGconn* proxy_admin) {
|
|
diag("Running: %s", STATS_QUERY_DIGEST.c_str());
|
|
PGresult* res = PQexec(proxy_admin, STATS_QUERY_DIGEST.c_str());
|
|
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
|
|
diag("Failed to execute query `%s`: %s", STATS_QUERY_DIGEST.c_str(), PQerrorMessage(proxy_admin));
|
|
PQclear(res);
|
|
return {};
|
|
}
|
|
|
|
std::map<std::string, int> stats{ {"cache", 0}, {"hostgroups", 0} }; // {hostgroup, count_star}
|
|
int nRows = PQntuples(res);
|
|
|
|
for (int i = 0; i < nRows; i++) {
|
|
stats[atoi(PQgetvalue(res, i, 0)) == -1 ? "cache" : "hostgroups"] = atoi(PQgetvalue(res, i, 1));
|
|
}
|
|
|
|
diag("Queries hitting the cache: %d", stats["cache"]);
|
|
diag("Queries NOT hitting the cache: %d", stats["hostgroups"]);
|
|
PQclear(res);
|
|
return stats;
|
|
}
|
|
|
|
void executeTests(bool with_ssl) {
|
|
std::unique_ptr<PGconn, decltype(&PQfinish)> pg_admin_conn(createNewConnection(ADMIN, with_ssl), &PQfinish);
|
|
if (!pg_admin_conn) return;
|
|
|
|
std::vector<std::string> admin_queries = {
|
|
"DELETE FROM pgsql_query_rules",
|
|
"INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
|
|
"LOAD PGSQL QUERY RULES TO RUNTIME",
|
|
"UPDATE global_variables SET variable_value=50 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
|
|
"LOAD PGSQL VARIABLES TO RUNTIME",
|
|
};
|
|
|
|
for (const auto& query : admin_queries) {
|
|
diag("Running: %s", query.c_str());
|
|
PGresult* res = PQexec(pg_admin_conn.get(), query.c_str());
|
|
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
|
|
diag("Failed to execute query `%s`: %s", query.c_str(), PQerrorMessage(pg_admin_conn.get()));
|
|
PQclear(res);
|
|
return;
|
|
}
|
|
PQclear(res);
|
|
}
|
|
|
|
auto stats_before = getDigestStatsDummyQuery(pg_admin_conn.get());
|
|
|
|
std::unique_ptr<PGconn, decltype(&PQfinish)> pg_conn(createNewConnection(BACKEND, with_ssl), &PQfinish);
|
|
if (!pg_conn) return;
|
|
|
|
diag("Running: %s", DUMMY_QUERY);
|
|
PGresult* res = PQexec(pg_conn.get(), DUMMY_QUERY);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
|
|
diag("Failed to execute query `%s`: %s", DUMMY_QUERY, PQerrorMessage(pg_conn.get()));
|
|
PQclear(res);
|
|
return;
|
|
}
|
|
PQclear(res);
|
|
|
|
std::vector<std::thread> mythreads(NUM_THREADS);
|
|
for (unsigned int i = 0; i < NUM_THREADS; i++) {
|
|
mythreads[i] = std::thread(runDummyQuery, &timer_results[i]);
|
|
}
|
|
|
|
for (auto& thread : mythreads) {
|
|
thread.join();
|
|
}
|
|
|
|
for (unsigned int i = 0; i < NUM_THREADS; i++) {
|
|
if (timer_results[i] == -1.0) {
|
|
fprintf(stderr, "Error: one or more threads finished with errors in file %s, line %d\n", __FILE__, __LINE__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Calculate the number of clients that took more than 1 second to execute the query
|
|
int num_slow_clients = 0;
|
|
for (unsigned int i = 0; i < NUM_THREADS; i++) {
|
|
num_slow_clients += static_cast<int>(timer_results[i]);
|
|
}
|
|
|
|
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. "
|
|
"Expected: '%d', Actual: '%d'", expected_num_slow_clients, num_slow_clients);
|
|
|
|
auto stats_after = getDigestStatsDummyQuery(pg_admin_conn.get());
|
|
|
|
std::map<std::string, int> expected_stats{ {"cache", NUM_THREADS * NUM_QUERIES - 1}, {"hostgroups", 2} };
|
|
ok(
|
|
expected_stats["cache"] == stats_after["cache"] - stats_before["cache"],
|
|
"Query cache should have been hit %d times. Number of hits - Expected: '%d', Actual: '%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 - Expected: '%d', Actual: '%d'",
|
|
expected_stats["hostgroups"], expected_stats["hostgroups"],
|
|
stats_after["hostgroups"] - stats_before["hostgroups"]
|
|
);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
plan(3); // Total number of tests planned
|
|
|
|
if (cl.getEnv())
|
|
return exit_status();
|
|
|
|
executeTests(false); // without SSL
|
|
|
|
return exit_status();
|
|
}
|
|
|