core/lib/Drupal/Core/Cache/DatabaseBackend.php | 24 ++++++++++++ core/modules/system/system.install | 11 ++++++ .../KernelTests/Core/Cache/DatabaseBackendTest.php | 43 ++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index d53c51c..fac8245 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -17,6 +17,16 @@ class DatabaseBackend implements CacheBackendInterface { /** + * The number of cache items that this cache bin is allowed to store. + * + * @todo Bikeshed the size limit. + * @todo Make configurable per-bin. + * + * @var int + */ + public static $boundedSizeCount = 1000; + + /** * @var string */ protected $bin; @@ -326,6 +336,14 @@ public function invalidateAll() { */ public function garbageCollection() { try { + // Bounded size cache bin, using FIFO. + $query = $this->connection->select($this->bin); + $query->addExpression('MAX(bounded_id)', 'bounded_id'); + $max_bounded_id = $query->execute()->fetchField(); + $this->connection->delete($this->bin) + ->condition('bounded_id', $max_bounded_id - static::$boundedSizeCount, '<=') + ->execute(); + $this->connection->delete($this->bin) ->condition('expire', Cache::PERMANENT, '<>') ->condition('expire', REQUEST_TIME, '<') @@ -469,11 +487,17 @@ public function schemaDefinition() { 'length' => 255, 'not null' => TRUE, ], + 'bounded_id' => [ + 'type' => 'serial', + ], ], 'indexes' => [ 'expire' => ['expire'], ], 'primary key' => ['cid'], + 'unique keys' => [ + 'bounded_id' => ['bounded_id'], + ] ]; return $schema; } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index a4ffffa..684fb77 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1984,3 +1984,14 @@ function system_update_8401() { ->clear('response') ->save(); } + +/** + * Delete all cache_* tables. They are recreated on demand with the new schema. + */ +function system_update_8402() { + foreach (\Drupal\Core\Cache\Cache::getBins() as $bin => $cache_backend) { + if ($cache_backend instanceof \Drupal\Core\Cache\DatabaseBackend) { + db_drop_table("cache_$bin"); + } + } +} diff --git a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php index de8bbda..61c3b8d 100644 --- a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php +++ b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php @@ -48,4 +48,47 @@ public function testSetGet() { $this->assertIdentical($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id."); } + /** + * Tests the row count limiting of cache bin database tables. + */ + public function testGarbageCollection() { + $backend = $this->getCacheBackend(); + $max_rows = DatabaseBackend::$boundedSizeCount; + + $this->assertSame(0, (int) $this->getNumRows()); + + // Fill to just the limit. + for ($i = 0; $i < $max_rows; $i++) { + $backend->set("test$i", $i); + } + $this->assertSame($max_rows, $this->getNumRows()); + + // Garbage collection has no effect. + $backend->garbageCollection(); + $this->assertSame($max_rows, $this->getNumRows()); + + // Go one row beyond the limit. + $backend->set('test' . ($max_rows + 1), $max_rows + 1); + $this->assertSame($max_rows + 1, $this->getNumRows()); + + // Garbage collection removes one row: the oldest. + $backend->garbageCollection(); + $this->assertSame($max_rows, $this->getNumRows()); + $this->assertSame(FALSE, $backend->get('test0')); + } + + /** + * Gets the number of rows in the test cache bin database table. + * + * @return int + * The number of rows in the test cache bin database table. + */ + protected function getNumRows() { + $table = 'cache_' . $this->testBin; + $connection = $this->container->get('database'); + $query = $connection->select($table); + $query->addExpression('COUNT(cid)', 'cid'); + return (int) $query->execute()->fetchField(); + } + }