[BUGFIX] Faster garbage collection of caching framework 54/33254/10
authorStephan Großberndt <stephan@grossberndt.de>
Fri, 10 Oct 2014 22:03:31 +0000 (00:03 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Mon, 19 Jan 2015 15:18:02 +0000 (16:18 +0100)
Do the garbage collection of the caching framework with a single DELETE
statement using JOIN instead of fetching all entries by identifier and
deleting them in a separate statement afterwards. This improves
performance especially for big installations.

For EXT:DBAL the old behaviour is kept since Oracle does neither
support subqueries nor DELETE statements with JOINs.

Removed test collectGarbageSelectsExpiredCacheEntries since
collectGarbage() doesn't perform a SELECT query anymore.

Resolves: #61814
Releases: master, 6.2
Change-Id: Ifca9065e274b85219ca81afa87724ce8884cc662
Reviewed-on: http://review.typo3.org/33254
Reviewed-by: Stephan Großberndt <stephan@grossberndt.de>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Classes/Cache/Backend/Typo3DatabaseBackend.php
typo3/sysext/core/Tests/Unit/Cache/Backend/Typo3DatabaseBackendTest.php

index 0b09a4b..3fa3fe4 100644 (file)
@@ -268,8 +268,37 @@ class Typo3DatabaseBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend
         */
        public function flushByTag($tag) {
                $this->throwExceptionIfFrontendDoesNotExist();
+
+               if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')) {
+                       $this->flushByTagDbal($tag);
+               } else {
+                       $GLOBALS['TYPO3_DB']->sql_query('
+                               DELETE ' . $this->cacheTable . ', ' . $this->tagsTable . '
+                               FROM ' . $this->cacheTable . ' JOIN ' . $this->tagsTable . ' ON ' . $this->cacheTable . '.identifier=' . $this->tagsTable . '.identifier
+                               WHERE ' . $this->tagsTable . '.tag = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tag, $this->tagsTable)
+                       );
+               }
+       }
+
+       /**
+        * Removes all cache entries of this cache for DBAL databases which are tagged by the specified tag.
+        *
+        * @param string $tag The tag the entries must have
+        * @return void
+        */
+       protected function flushByTagDbal($tag) {
                $tagsTableWhereClause = $this->tagsTable . '.tag = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tag, $this->tagsTable);
-               $this->deleteCacheTableRowsByTagsTableWhereClause($tagsTableWhereClause);
+               $cacheEntryIdentifierRowsResource = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT identifier', $this->tagsTable, $tagsTableWhereClause);
+               $cacheEntryIdentifiers = array();
+               while ($cacheEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($cacheEntryIdentifierRowsResource)) {
+                       $cacheEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr($cacheEntryIdentifierRow['identifier'], $this->cacheTable);
+               }
+               $GLOBALS['TYPO3_DB']->sql_free_result($cacheEntryIdentifierRowsResource);
+               if (!empty($cacheEntryIdentifiers)) {
+                       $deleteWhereClause = 'identifier IN (' . implode(', ', $cacheEntryIdentifiers) . ')';
+                       $GLOBALS['TYPO3_DB']->exec_DELETEquery($this->cacheTable, $deleteWhereClause);
+                       $GLOBALS['TYPO3_DB']->exec_DELETEquery($this->tagsTable, $deleteWhereClause);
+               }
        }
 
        /**
@@ -279,16 +308,34 @@ class Typo3DatabaseBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend
         */
        public function collectGarbage() {
                $this->throwExceptionIfFrontendDoesNotExist();
+
+               if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')) {
+                       $this->collectGarbageDbal();
+               } else {
+                       $GLOBALS['TYPO3_DB']->sql_query('
+                               DELETE ' . $this->cacheTable . ', ' . $this->tagsTable . '
+                               FROM ' . $this->cacheTable . ' JOIN ' . $this->tagsTable . ' ON ' . $this->cacheTable . '.identifier=' . $this->tagsTable . '.identifier
+                               WHERE ' . $this->expiredStatement
+                       );
+               }
+       }
+
+       /**
+        * Does garbage collection for DBAL databases
+        *
+        * @return void
+        */
+       protected function collectGarbageDbal() {
                // Get identifiers of expired cache entries
-               $tagsEntryIdentifierRowsResource = $GLOBALS['TYPO3_DB']->exec_SELECTquery('identifier', $this->cacheTable, $this->expiredStatement);
-               $tagsEntryIdentifiers = array();
-               while ($tagsEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($tagsEntryIdentifierRowsResource)) {
-                       $tagsEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr($tagsEntryIdentifierRow['identifier'], $this->tagsTable);
+               $cacheEntryIdentifierRowsResource = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT identifier', $this->cacheTable, $this->expiredStatement);
+               $cacheEntryIdentifiers = array();
+               while ($cacheEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($cacheEntryIdentifierRowsResource)) {
+                       $cacheEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr($cacheEntryIdentifierRow['identifier'], $this->tagsTable);
                }
-               $GLOBALS['TYPO3_DB']->sql_free_result($tagsEntryIdentifierRowsResource);
+               $GLOBALS['TYPO3_DB']->sql_free_result($cacheEntryIdentifierRowsResource);
                // Delete tag rows connected to expired cache entries
-               if (count($tagsEntryIdentifiers)) {
-                       $GLOBALS['TYPO3_DB']->exec_DELETEquery($this->tagsTable, 'identifier IN (' . implode(', ', $tagsEntryIdentifiers) . ')');
+               if (!empty($cacheEntryIdentifiers)) {
+                       $GLOBALS['TYPO3_DB']->exec_DELETEquery($this->tagsTable, 'identifier IN (' . implode(', ', $cacheEntryIdentifiers) . ')');
                }
                // Delete expired cache rows
                $GLOBALS['TYPO3_DB']->exec_DELETEquery($this->cacheTable, $this->expiredStatement);
@@ -369,23 +416,4 @@ class Typo3DatabaseBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend
                return $requiredTableStructures;
        }
 
-       /**
-        * Deletes rows in cache table found by where clause on tags table
-        *
-        * @param string $tagsTableWhereClause The where clause for the tags table
-        * @return void
-        */
-       protected function deleteCacheTableRowsByTagsTableWhereClause($tagsTableWhereClause) {
-               $cacheEntryIdentifierRowsResource = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT identifier', $this->tagsTable, $tagsTableWhereClause);
-               $cacheEntryIdentifiers = array();
-               while ($cacheEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($cacheEntryIdentifierRowsResource)) {
-                       $cacheEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr($cacheEntryIdentifierRow['identifier'], $this->cacheTable);
-               }
-               $GLOBALS['TYPO3_DB']->sql_free_result($cacheEntryIdentifierRowsResource);
-               if (count($cacheEntryIdentifiers)) {
-                       $GLOBALS['TYPO3_DB']->exec_DELETEquery($this->cacheTable, 'identifier IN (' . implode(', ', $cacheEntryIdentifiers) . ')');
-                       $GLOBALS['TYPO3_DB']->exec_DELETEquery($this->tagsTable, 'identifier IN (' . implode(', ', $cacheEntryIdentifiers) . ')');
-               }
-       }
-
 }
index 4ac6ae5..15818cc 100644 (file)
@@ -355,22 +355,6 @@ class Typo3DatabaseBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @test
         */
-       public function collectGarbageSelectsExpiredCacheEntries() {
-               /** @var \TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend|\PHPUnit_Framework_MockObject_MockObject $backend */
-               $backend = $this->getMock(\TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend::class, array('dummy'), array('Testing'));
-               $this->setUpMockFrontendOfBackend($backend);
-
-               $GLOBALS['TYPO3_DB'] = $this->getMock(\TYPO3\CMS\Core\Database\DatabaseConnection::class, array(), array(), '', FALSE);
-               $GLOBALS['TYPO3_DB']
-                       ->expects($this->once())
-                       ->method('exec_SELECTquery')
-                       ->with('identifier', 'cf_Testing');
-               $backend->collectGarbage();
-       }
-
-       /**
-        * @test
-        */
        public function collectGarbageDeletesTagsFromExpiredEntries() {
                /** @var \TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend|\PHPUnit_Framework_MockObject_MockObject $backend */
                $backend = $this->getMock(\TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend::class, array('dummy'), array('Testing'));