diff --git a/README.md b/README.md index 6ea43ec..3507b3c 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ cd .. ./build/bin/cards /path/to/config.json ``` -Для подробного вывода этапов запуска и итоговой конфигурации используйте флаг `--debug`. Пароль базы данных в таком выводе не раскрывается. +Для подробного вывода этапов запуска и итоговой конфигурации используйте флаг `--debug`. Он также включает диагностику работы с БД: создание схемы, запуск и остановку воркеров, запросы, выбранную стратегию выборки, результаты и переподключения после ошибок. Пароль базы данных в таком выводе не раскрывается. ```bash ./build/bin/cards --debug diff --git a/src/database.cpp b/src/database.cpp index a6bf778..d38486a 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -1,13 +1,17 @@ #include "database.hpp" #include +#include +#include -Database::Database(const DatabaseConfig& cfg, int workers) +Database::Database(const DatabaseConfig& cfg, int workers, bool debug) : connStr( "host=" + cfg.host + " port=" + std::to_string(cfg.port) + " dbname=" + cfg.dbname + " user=" + cfg.user + - " password=" + cfg.password) { + " password=" + cfg.password), + debug(debug) { + logDebug("Creating schema for random_data"); { pqxx::connection probe(connStr); pqxx::work txn(probe); @@ -22,6 +26,7 @@ Database::Database(const DatabaseConfig& cfg, int workers) ); txn.commit(); } + logDebug("Schema is ready; starting " + std::to_string(workers) + " database worker(s)"); for (int i = 0; i < workers; ++i) { threadPool.emplace_back([this, i]() { worker(i); }); } @@ -36,16 +41,20 @@ void Database::stop() { std::lock_guard lock(mtx); stopFlag = true; } + logDebug("Stopping database workers"); cv.notify_all(); for (auto& t : threadPool) { if (t.joinable()) { t.join(); } } + logDebug("All database workers stopped"); } void Database::worker(int id) { + logDebug("Worker " + std::to_string(id) + " connecting to database"); auto conn = std::make_unique(connStr); + logDebug("Worker " + std::to_string(id) + " connected to database"); std::mt19937 rng(std::random_device{}()); for (;;) { @@ -53,7 +62,10 @@ void Database::worker(int id) { { std::unique_lock lock(mtx); cv.wait(lock, [&] { return stopFlag || !queue.empty(); }); - if (stopFlag && queue.empty()) return; + if (stopFlag && queue.empty()) { + logDebug("Worker " + std::to_string(id) + " stopped"); + return; + } task = std::move(queue.front()); queue.pop(); } @@ -61,23 +73,36 @@ void Database::worker(int id) { json res; try { if (task.op == Op::RandomRecord) { + logDebug("Worker " + std::to_string(id) + " fetching one random record"); res = getRandomRecord(*conn, rng); } else { + logDebug("Worker " + std::to_string(id) + " fetching " + std::to_string(task.count) + + " random record(s)"); res = getRandomRecords(*conn, rng, task.count, task.med, task.max); } - } catch (const std::exception&) { + } catch (const std::exception& e) { + logDebug("Worker " + std::to_string(id) + " database error: " + e.what() + + "; reconnecting"); conn = std::make_unique(connStr); + logDebug("Worker " + std::to_string(id) + " reconnected to database"); res = json{{"error", "Database error"}}; } + logDebug("Worker " + std::to_string(id) + " completed request" + + (res.contains("error") ? ": " + res["error"].get() : "")); task.cb(std::move(res)); } } void Database::enqueue(Task task) { + std::size_t queueSize; + const char* operation = task.op == Op::RandomRecord ? "one random record" : "random records"; { std::lock_guard lock(mtx); queue.push(std::move(task)); + queueSize = queue.size(); } + logDebug("Queued request for " + std::string(operation) + + "; pending requests: " + std::to_string(queueSize)); cv.notify_one(); } @@ -91,6 +116,7 @@ void Database::getRandomRecords(int count, int med, int max, Callback cb) { json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) { int totalCount = getTotalCount(conn); + logDebug("random_data contains " + std::to_string(totalCount) + " record(s)"); if (totalCount == 0) { return json{ @@ -100,6 +126,7 @@ json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) { std::uniform_int_distribution idDist(1, totalCount); int randomId = idDist(rng); + logDebug("Selected random record ID " + std::to_string(randomId)); pqxx::work txn(conn); @@ -111,6 +138,7 @@ json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) { ); if (result.empty()) { + logDebug("Selected record no longer exists; falling back to the first available record"); result = txn.exec("SELECT id, name, text, description, created_at FROM random_data LIMIT 1"); } @@ -137,6 +165,7 @@ json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) { } json Database::getRandomRecordsFast(pqxx::connection& conn, const int count) { + logDebug("Using recursive CTE strategy for " + std::to_string(count) + " record(s)"); pqxx::work txn(conn); std::string query = @@ -210,6 +239,7 @@ json Database::getRandomRecordsFast(pqxx::connection& conn, const int count) { } txn.commit(); + logDebug("Recursive CTE returned " + std::to_string(response.size()) + " record(s)"); return { {"data", response}, @@ -220,6 +250,8 @@ json Database::getRandomRecordsFast(pqxx::connection& conn, const int count) { } json Database::getRandomRecordsSlow(pqxx::connection& conn, std::mt19937& rng, const int count, const int totalCount) { + logDebug("Using individual-ID strategy for " + std::to_string(count) + " of " + + std::to_string(totalCount) + " record(s)"); std::set ids; { std::uniform_int_distribution idDist(1, totalCount); @@ -259,6 +291,7 @@ json Database::getRandomRecordsSlow(pqxx::connection& conn, std::mt19937& rng, c } txn.commit(); + logDebug("Individual-ID query returned " + std::to_string(response.size()) + " record(s)"); return { {"data", response}, @@ -277,6 +310,8 @@ json Database::getRandomRecords(pqxx::connection& conn, std::mt19937& rng, int c } int totalCount = getTotalCount(conn); + logDebug("Requested " + std::to_string(count) + " random record(s); " + + std::to_string(totalCount) + " available"); if (totalCount == 0) { return json{ @@ -286,10 +321,12 @@ json Database::getRandomRecords(pqxx::connection& conn, std::mt19937& rng, int c } if (count > totalCount) { + logDebug("Reducing requested count to available record count"); count = totalCount; } if (count > max) { + logDebug("Reducing requested count to configured maximum of " + std::to_string(max)); count = max; } @@ -329,3 +366,9 @@ std::string Database::getQueryRandomIds(const int count) { query += ") ORDER BY id"; return query; } + +void Database::logDebug(const std::string& message) const { + if (debug) { + std::osyncstream(std::cout) << "[debug][database] " << message << '\n'; + } +} diff --git a/src/database.hpp b/src/database.hpp index fa254cc..84cbf6a 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -19,7 +19,7 @@ class Database { public: using Callback = std::function; - explicit Database(const DatabaseConfig& config, int workers = 2); + explicit Database(const DatabaseConfig& config, int workers = 2, bool debug = false); ~Database(); Database(const Database&) = delete; @@ -42,6 +42,7 @@ private: void worker(int id); void enqueue(Task task); void stop(); + void logDebug(const std::string& message) const; json getRandomRecord(pqxx::connection& conn, std::mt19937& rng); json getRandomRecords(pqxx::connection& conn, std::mt19937& rng, int count, int med, int max); @@ -56,4 +57,5 @@ private: std::condition_variable cv; std::queue queue; bool stopFlag = false; + bool debug; }; diff --git a/src/main.cpp b/src/main.cpp index 2c0587d..63a94b1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) { } std::cout << "Connecting to database..." << std::endl; - auto db = std::make_shared(config.database); + auto db = std::make_shared(config.database, 2, debug); std::cout << "Database connected successfully!" << std::endl; if (debug) {