From 5ac92db5d557e26d8fbd5c2c3269568b49d3873c Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Fri, 15 Nov 2024 19:14:11 +0100 Subject: [PATCH] Vacuum the sqlite rpmdb if necessary Check if there is 20MB in free pages and then execute a VACUUM command. The threshold can be controlled by the _sqlite_vacuum macro. Don't add this to the macros file on purpose as we don't want people to get involved with such details. Here it is mainly used for testing. Using a 20 MB threshold should prevent the vacuuming to happend too often while still triggering after large transactions. As we install new headers first and then remove the old ones transactions leave behind large amounts of free pages. We do not use PRAGMA auto_vacuum here as it does not defrag the database and only frees empty pages. So it still requires running VACUUM from time to time. Freeing the empty pages would get rid of the condition we use here for running VACUUM. Using pure C for the macro expansion on purpopse in case we want to back port this. Resolves: #3309 --- lib/backend/sqlite.cc | 29 +++++++++++++++++++++++++++++ tests/rpmdb.at | 17 +++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/lib/backend/sqlite.cc b/lib/backend/sqlite.cc index 7048d79615..76bac94c23 100644 --- a/lib/backend/sqlite.cc +++ b/lib/backend/sqlite.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -185,6 +186,21 @@ static int sqlite_init(rpmdb rdb, const char * dbhome) return rc; } +static int64_t sqlite_free_space_kb(sqlite3 * db) +{ + int64_t result = 0; + sqlite3_stmt *s = NULL; + const char *cmd = "SELECT (freelist_count*page_size) as FreeSizeEstimate" + " FROM pragma_freelist_count, pragma_page_size"; + if (sqlite3_prepare_v2(db, cmd, -1, &s, NULL) == SQLITE_OK) { + while (sqlite3_step(s) == SQLITE_ROW) { + result = sqlite3_column_int64(s, 0); + } + sqlite3_finalize(s); + } + return result / 1024; +} + static int sqlite_fini(rpmdb rdb) { int rc = 0; @@ -194,8 +210,21 @@ static int sqlite_fini(rpmdb rdb) rdb->db_opens--; } else { if (sqlite3_db_readonly(sdb, NULL) == 0) { + sqlexec(sdb, "PRAGMA optimize"); sqlexec(sdb, "PRAGMA wal_checkpoint = TRUNCATE"); + + int max_size = rpmExpandNumeric("%{?_sqlite_vacuum_kb}"); + if (max_size <= 0) + max_size = 20*1024; + int64_t free_space = sqlite_free_space_kb(sdb); + + if (free_space > max_size) { + sqlexec(sdb, "VACUUM"); + rpmlog(RPMLOG_DEBUG, "rpmdb sqlite backend VACUUM maxfree:" + " %ikB, free: %" PRIu64 "kB -> %" PRIu64 "kB\n", + max_size, free_space, sqlite_free_space_kb(sdb)); + } } rdb->db_dbenv = NULL; int xx = sqlite3_close(sdb); diff --git a/tests/rpmdb.at b/tests/rpmdb.at index 7c57f2bffe..a65c4f1ea1 100644 --- a/tests/rpmdb.at +++ b/tests/rpmdb.at @@ -683,3 +683,20 @@ runroot rpm -q 'versiontest-1.0~2-1' []) RPMTEST_CLEANUP + +# ------------------------------ +AT_SETUP([rpmdb vacuum]) +AT_KEYWORDS([install rpmdb sqlite]) +RPMDB_INIT +RPMTEST_CHECK([ +runroot rpm -U --noscripts --nodeps --ignorearch --noverify \ + /data/RPMS/hello-1.0-1.i386.rpm +runroot rpm -D "_sqlite_vacuum_kb 1" -vv -U --noscripts --nodeps --ignorearch \ + /data/RPMS/hello-2.0-1.i686.rpm 2>&1 | grep VACUUM +], +[0], +[D: VACUUM: 0 +D: rpmdb sqlite backend VACUUM maxfree: 1kB, free: 8kB -> 0kB +], +[]) +RPMTEST_CLEANUP