database: various optimizations for SQLite and PostgreSQL

This commit is contained in:
Luca Matei Pintilie
2026-07-21 01:16:49 +02:00
parent b51b93d6d4
commit ad14748a30
15 changed files with 143 additions and 11 deletions
+3
View File
@@ -24,6 +24,9 @@ For admins
persist option persist option
- Option archive_expires_after has been added to delete older messages from the - Option archive_expires_after has been added to delete older messages from the
database database
- SQLite enables WAL by default now. This can be changed with the
sqlite_journal_mode option.
- Biboumi will regularely perform maintanance on the database.
For packagers For packagers
------------- -------------
+10
View File
@@ -287,6 +287,16 @@ can be changed by this setting
# keep messages forever, the default # keep messages forever, the default
archive_expires_after=0 archive_expires_after=0
sqlite_journal_mode
~~~~~~~~~~~~~~~~~~~
Set the `journal_mode https://sqlite.org/pragma.html#pragma_journal_mode`_ to
be used when connecting to an SQLite database. This option can be one of
`DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL`, `OFF`, or `none` to not set
the journal_mode at all.
By default this option is set to `WAL` to use `Write-Ahead Logging
https://sqlite.org/wal.html`_.
TLS configuration TLS configuration
----------------- -----------------
+1
View File
@@ -2,6 +2,7 @@
#include <utils/tolower.hpp> #include <utils/tolower.hpp>
#include <utils/split.hpp> #include <utils/split.hpp>
#include <fstream>
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
-2
View File
@@ -16,8 +16,6 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <fstream>
#include <memory>
#include <vector> #include <vector>
#include <string> #include <string>
#include <map> #include <map>
+9
View File
@@ -0,0 +1,9 @@
#pragma once
enum class DatabaseType
{
none,
sqlite3,
postgresql,
};
+2 -1
View File
@@ -7,11 +7,11 @@
* Everything else (all portable stuf) should go outside of this class. * Everything else (all portable stuf) should go outside of this class.
*/ */
#include <database/database_type.hpp>
#include <database/statement.hpp> #include <database/statement.hpp>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector>
#include <tuple> #include <tuple>
#include <set> #include <set>
@@ -36,6 +36,7 @@ class DatabaseEngine
return {}; return {};
} }
virtual std::string id_column_type() = 0; virtual std::string id_column_type() = 0;
virtual DatabaseType get_type() { return DatabaseType::none; };
int64_t last_inserted_rowid{-1}; int64_t last_inserted_rowid{-1};
}; };
+3
View File
@@ -101,4 +101,7 @@ std::string PostgresqlEngine::id_column_type()
return "SERIAL"; return "SERIAL";
} }
DatabaseType PostgresqlEngine::get_type() {
return DatabaseType::postgresql;
};
#endif #endif
+3 -1
View File
@@ -2,9 +2,9 @@
#include <biboumi.h> #include <biboumi.h>
#include <string> #include <string>
#include <stdexcept>
#include <memory> #include <memory>
#include <database/database_type.hpp>
#include <database/statement.hpp> #include <database/statement.hpp>
#include <database/engine.hpp> #include <database/engine.hpp>
@@ -30,6 +30,8 @@ class PostgresqlEngine: public DatabaseEngine
void extract_last_insert_rowid(Statement& statement) override; void extract_last_insert_rowid(Statement& statement) override;
std::string get_returning_id_sql_string(const std::string& col_name) override; std::string get_returning_id_sql_string(const std::string& col_name) override;
std::string id_column_type() override; std::string id_column_type() override;
DatabaseType get_type() override final;
private: private:
PGconn* const conn; PGconn* const conn;
}; };
+1
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <string>
#include <type_traits> #include <type_traits>
template <typename... T> template <typename... T>
+46 -2
View File
@@ -7,9 +7,14 @@
#include <database/query.hpp> #include <database/query.hpp>
#include <config/config.hpp>
#include <utils/tolower.hpp> #include <utils/tolower.hpp>
#include <logger/logger.hpp> #include <logger/logger.hpp>
#include <vector> #include <utils/dirname.hpp>
#include <filesystem>
#include <sys/stat.h>
Sqlite3Engine::Sqlite3Engine(sqlite3* db): Sqlite3Engine::Sqlite3Engine(sqlite3* db):
db(db) db(db)
@@ -46,6 +51,8 @@ std::set<std::string> Sqlite3Engine::get_all_columns_from_table(const std::strin
std::unique_ptr<DatabaseEngine> Sqlite3Engine::open(const std::string& filename) std::unique_ptr<DatabaseEngine> Sqlite3Engine::open(const std::string& filename)
{ {
sqlite3* new_db; sqlite3* new_db;
const std::string dirname = utils::dirname(filename);
std::filesystem::create_directories(dirname);
auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
if (res != SQLITE_OK) if (res != SQLITE_OK)
{ {
@@ -53,7 +60,31 @@ std::unique_ptr<DatabaseEngine> Sqlite3Engine::open(const std::string& filename)
sqlite3_close(new_db); sqlite3_close(new_db);
throw std::runtime_error(""); throw std::runtime_error("");
} }
return std::make_unique<Sqlite3Engine>(new_db); std::unique_ptr<DatabaseEngine> db = std::make_unique<Sqlite3Engine>(new_db);
// Enable Write-Ahead Logging
// https://sqlite.org/wal.html
auto sqlite_journal_mode = Config::get("sqlite_journal_mode", "WAL");
if (sqlite_journal_mode != "none") {
if (Sqlite3Engine::is_valid_sqlite_journal_mode(sqlite_journal_mode)) {
auto [success, err_msg] =
db->raw_exec("PRAGMA journal_mode = " + sqlite_journal_mode);
if (!success)
log_error("Failed enabling SQLite WAL with error: ", err_msg);
} else {
log_error("Invalid config option sqlite_journal_mode: ",
sqlite_journal_mode);
}
}
for (const auto &s : Sqlite3Engine::startup_commands) {
auto [success, err_msg] = db->raw_exec(s);
if (!success) {
log_error("Failed running startup command: ", s,
"; with error: ", err_msg);
}
}
return db;
} }
std::tuple<bool, std::string> Sqlite3Engine::raw_exec(const std::string& query) std::tuple<bool, std::string> Sqlite3Engine::raw_exec(const std::string& query)
@@ -97,4 +128,17 @@ std::string Sqlite3Engine::id_column_type()
return "INTEGER PRIMARY KEY AUTOINCREMENT"; return "INTEGER PRIMARY KEY AUTOINCREMENT";
} }
DatabaseType Sqlite3Engine::get_type() {
return DatabaseType::sqlite3;
};
bool Sqlite3Engine::is_valid_sqlite_journal_mode(std::string &sqlite_journal_mode) {
// https://sqlite.org/pragma.html#pragma_journal_mode
for (const std::string &s : Sqlite3Engine::valid_sqlite_journal_modes) {
if (sqlite_journal_mode == s)
return true;
}
return false;
}
#endif #endif
+21
View File
@@ -29,8 +29,29 @@ class Sqlite3Engine: public DatabaseEngine
std::unique_ptr<Statement> prepare(const std::string& query) override; std::unique_ptr<Statement> prepare(const std::string& query) override;
void extract_last_insert_rowid(Statement& statement) override; void extract_last_insert_rowid(Statement& statement) override;
std::string id_column_type() override; std::string id_column_type() override;
DatabaseType get_type() override final;
private: private:
sqlite3* const db; sqlite3* const db;
constexpr static const char *const startup_commands[3] = {
// Wait for a maximum of 5 seconds if a write is busy
"PRAGMA busy_timeout = 5000",
// Reduce disk syncs
// >When synchronous is NORMAL (1), the SQLite database engine will still
// >sync at the most critical moments
// https://sqlite.org/pragma.html#pragma_synchronous
"PRAGMA synchronous = NORMAL",
// Run PRAGMA command early to analyze all tables, as recommended by
// sqlite
// >Applications that use long-lived database connections should run
// >"PRAGMA optimize=0x10002;" when the connection is first opened
// https://sqlite.org/lang_analyze.html
"PRAGMA optimize=0x10002",
};
constexpr static std::string valid_sqlite_journal_modes[6] = {
"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF",
};
static bool is_valid_sqlite_journal_mode(std::string &sqlite_journal_mode);
}; };
#else #else
-1
View File
@@ -5,7 +5,6 @@
#include <database/delete_query.hpp> #include <database/delete_query.hpp>
#include <database/row.hpp> #include <database/row.hpp>
#include <algorithm>
#include <string> #include <string>
#include <set> #include <set>
+42 -3
View File
@@ -1,10 +1,12 @@
#include "biboumi.h" #include "biboumi.h"
#include <config/config.hpp> #include <config/config.hpp>
#include <logger/logger.hpp>
#include <scheduler/scheduler.hpp> #include <scheduler/scheduler.hpp>
#include <utils/parse_duration.hpp> #include <utils/parse_duration.hpp>
#include <utils/timed_events.hpp> #include <utils/timed_events.hpp>
#ifdef USE_DATABASE #ifdef USE_DATABASE
#include <database/database_type.hpp>
#include <database/database.hpp> #include <database/database.hpp>
#endif #endif
@@ -13,8 +15,10 @@ static const std::unordered_map<
events = { events = {
#ifdef USE_DATABASE #ifdef USE_DATABASE
{"cleanup_old_messages", {"cleanup_old_messages",
{60s * 10 /* 10min */, &Scheduler::cleanup_old_messages}}, {std::chrono::seconds(10min), &Scheduler::cleanup_old_messages}},
#endif {"optimize_db",
{std::chrono::seconds(24h), &Scheduler::optimize_db}},
#endif // USE_DATABASE
}; };
void Scheduler::start_scheduler() { void Scheduler::start_scheduler() {
@@ -65,4 +69,39 @@ void Scheduler::cleanup_old_messages() {
"archive_expires_after is ", "archive_expires_after is ",
archive_expires_after); archive_expires_after);
} }
#endif
void Scheduler::optimize_db() {
log_debug("[optimize_db] Starting. Type: ", (int)Database::db->get_type());
switch (Database::db->get_type()) {
# ifdef SQLITE3_FOUND
case DatabaseType::sqlite3: {
log_debug("[optimize_db] Optimizing Sqlite database");
// Run PRAGMA optimize periodically, as recommended by sqlite
// https://sqlite.org/lang_analyze.html#automatically_running_analyze
auto [success, err_msg] = Database::db->raw_exec("PRAGMA optimize");
if (!success) {
log_error("[optimize_db] Failed optimizing Sqlite database: ", err_msg);
}
break;
}
# endif // SQLITE3_FOUND
# ifdef PQ_FOUND
case DatabaseType::postgresql: {
log_debug("[optimize_db] Optimizing PostgreSQL database");
// Run VACUUM and ANALYZE periodically
// https://www.postgresql.org/docs/current/sql-vacuum.html
// https://www.postgresql.org/docs/current/sql-analyze.html
auto [success, err_msg] = Database::db->raw_exec("VACUUM ANALYZE");
if (!success) {
log_error("[optimize_db] Failed optimizing PostgreSQL database: ",
err_msg);
}
break;
}
# endif // PQ_FOUND
case DatabaseType::none:
default:
break;
}
}
#endif // USE_DATABASE
+1
View File
@@ -7,4 +7,5 @@ public:
// Jobs // Jobs
static void cleanup_old_messages(); static void cleanup_old_messages();
static void optimize_db();
}; };
+1 -1
View File
@@ -22,7 +22,7 @@ class TimedEvent
friend class TimedEventsManager; friend class TimedEventsManager;
public: public:
/** /**
* An event the occurs only once, at the given time_point * An event that occurs only once, at the given time_point
*/ */
explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point,
std::function<void()> callback, std::string name=""); std::function<void()> callback, std::string name="");