/** * @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 #include #include #include #include #include #include #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(currentTime - lastTime).count(); lastTime = currentTime; return deltaTime; } private: std::chrono::time_point 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 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 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 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 pg_admin_conn(createNewConnection(ADMIN, with_ssl), &PQfinish); if (!pg_admin_conn) return; std::vector 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 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 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(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 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(); }