From f7240301e4ff687a15e815ec6ba39d1e944faeea Mon Sep 17 00:00:00 2001 From: "J. King" Date: Fri, 26 Jul 2019 09:37:51 -0400 Subject: [PATCH] Basic database maintenance Closes #169 --- CHANGELOG | 3 +++ lib/Database.php | 10 ++++++++-- lib/Db/Driver.php | 6 ++++++ lib/Db/MySQL/Driver.php | 13 +++++++++++++ lib/Db/PostgreSQL/Driver.php | 6 ++++++ lib/Db/SQLite3/Driver.php | 6 ++++++ lib/Service.php | 9 +++++++-- tests/cases/Database/SeriesMiscellany.php | 4 ++++ tests/cases/Db/BaseDriver.php | 5 +++++ tests/cases/Db/BaseUpdate.php | 5 +++++ 10 files changed, 63 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8e4988d..4cdc00b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,9 @@ Bug fixes: - Sort Tiny Tiny RSS special feeds according to special ordering - Invalidate sessions when passwords are changed +Changes: +- Perform regular database maintenance to improve long-term performance + Version 0.7.1 (2019-03-25) ========================== diff --git a/lib/Database.php b/lib/Database.php index 10e66eb..366d84d 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -110,6 +110,11 @@ class Database { return $this->db->charsetAcceptable(); } + /** Performs maintenance on the database to ensure good performance */ + public function driverMaintenance(): bool { + return $this->db->maintenance(); + } + /** Computes the column and value text of an SQL "SET" clause, validating arbitrary input against a whitelist * * Returns an indexed array containing the clause text, an array of types, and another array of values @@ -1788,10 +1793,11 @@ class Database { $limitUnread = Date::sub(Arsse::$conf->purgeArticlesUnread); } $feeds = $this->db->query("SELECT id, size from arsse_feeds")->getAll(); + $deleted = 0; foreach ($feeds as $feed) { - $query->run($feed['id'], $feed['size'], $feed['id'], $limitUnread, $limitRead); + $deleted += $query->run($feed['id'], $feed['size'], $feed['id'], $limitUnread, $limitRead)->changes(); } - return true; + return (bool) $deleted; } /** Ensures the specified article exists and raises an exception otherwise diff --git a/lib/Db/Driver.php b/lib/Db/Driver.php index 7f04dc6..a456fba 100644 --- a/lib/Db/Driver.php +++ b/lib/Db/Driver.php @@ -82,4 +82,10 @@ interface Driver { * This functionality should be avoided in favour of using statement parameters whenever possible */ public function literalString(string $str): string; + + /** Performs implementation-specific database maintenance to ensure good performance + * + * This should be restricted to quick maintenance; in SQLite terms it might include ANALYZE, but not VACUUM + */ + public function maintenance(): bool; } diff --git a/lib/Db/MySQL/Driver.php b/lib/Db/MySQL/Driver.php index cec575b..bb9cac8 100644 --- a/lib/Db/MySQL/Driver.php +++ b/lib/Db/MySQL/Driver.php @@ -216,4 +216,17 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { public function literalString(string $str): string { return "'".$this->db->real_escape_string($str)."'"; } + + public function maintenance(): bool { + // with MySQL each table must be analyzed separately, so we first have to get a list of tables + foreach ($this->query("SHOW TABLES like 'arsse\\_%'") as $table) { + $table = array_pop($table); + if (!preg_match("/^arsse_[a-z_]+$/", $table)) { + // table is not one of ours + continue; // @codeCoverageIgnore + } + $this->query("ANALYZE TABLE $table"); + } + return true; + } } diff --git a/lib/Db/PostgreSQL/Driver.php b/lib/Db/PostgreSQL/Driver.php index 12ad8fc..7550393 100644 --- a/lib/Db/PostgreSQL/Driver.php +++ b/lib/Db/PostgreSQL/Driver.php @@ -225,4 +225,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { public function literalString(string $str): string { return pg_escape_literal($this->db, $str); } + + public function maintenance(): bool { + // analyze the database + $this->exec("ANALYZE"); + return true; + } } diff --git a/lib/Db/SQLite3/Driver.php b/lib/Db/SQLite3/Driver.php index 96e345f..6cf290f 100644 --- a/lib/Db/SQLite3/Driver.php +++ b/lib/Db/SQLite3/Driver.php @@ -188,4 +188,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { public function literalString(string $str): string { return "'".\SQLite3::escapeString($str)."'"; } + + public function maintenance(): bool { + // analyze the database then checkpoint and truncate the write-ahead log + $this->exec("ANALYZE; PRAGMA wal_checkpoint(truncate)"); + return true; + } } diff --git a/lib/Service.php b/lib/Service.php index bc752ae..93d4e9b 100644 --- a/lib/Service.php +++ b/lib/Service.php @@ -92,7 +92,12 @@ class Service { } public static function cleanupPost(): bool { - // delete old articles, according to configured threasholds - return Arsse::$db->articleCleanup(); + // delete old articles, according to configured thresholds + $deleted = Arsse::$db->articleCleanup(); + // if any articles were deleted, perform database maintenance + if ($deleted) { + Arsse::$db->driverMaintenance(); + } + return true; } } diff --git a/tests/cases/Database/SeriesMiscellany.php b/tests/cases/Database/SeriesMiscellany.php index 0080356..a7591bb 100644 --- a/tests/cases/Database/SeriesMiscellany.php +++ b/tests/cases/Database/SeriesMiscellany.php @@ -44,4 +44,8 @@ trait SeriesMiscellany { public function testCheckCharacterSetAcceptability() { $this->assertInternalType("bool", Arsse::$db->driverCharsetAcceptable()); } + + public function testPerformMaintenance() { + $this->assertTrue(Arsse::$db->driverMaintenance()); + } } diff --git a/tests/cases/Db/BaseDriver.php b/tests/cases/Db/BaseDriver.php index 74ef7c9..677339b 100644 --- a/tests/cases/Db/BaseDriver.php +++ b/tests/cases/Db/BaseDriver.php @@ -382,4 +382,9 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest { public function testProduceAStringLiteral() { $this->assertSame("'It''s a string!'", $this->drv->literalString("It's a string!")); } + + public function testPerformMaintenance() { + // this performs maintenance in the absence of tables; see BaseUpdate.php for another test with tables + $this->assertTrue($this->drv->maintenance()); + } } diff --git a/tests/cases/Db/BaseUpdate.php b/tests/cases/Db/BaseUpdate.php index 2780684..e9bc10d 100644 --- a/tests/cases/Db/BaseUpdate.php +++ b/tests/cases/Db/BaseUpdate.php @@ -130,4 +130,9 @@ class BaseUpdate extends \JKingWeb\Arsse\Test\AbstractTest { $this->assertException("updateTooNew", "Db"); $this->drv->schemaUpdate(-1, $this->base); } + + public function testPerformMaintenance() { + $this->drv->schemaUpdate(Database::SCHEMA_VERSION); + $this->assertTrue($this->drv->maintenance()); + } }