Skip to content

Commit 220a2c3

Browse files
committed
FEATURE: flushByTags for the PDO cache backend
With this change the `flushByTags` is optimised to run in batches. The maximum batch size can and should be configured based on the used data source. This change relies on the interface changes in #2717
1 parent 6832106 commit 220a2c3

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

Neos.Cache/Classes/Backend/PdoBackend.php

+73
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ class PdoBackend extends IndependentAbstractBackend implements TaggableBackendIn
7272
*/
7373
protected $tagsTableName = 'tags';
7474

75+
/**
76+
* @var integer
77+
*/
78+
protected $batchSize = 999;
79+
7580
/**
7681
* @var \ArrayIterator
7782
*/
@@ -137,6 +142,16 @@ protected function setTagsTableName(string $tagsTableName): void
137142
$this->tagsTableName = $tagsTableName;
138143
}
139144

145+
/**
146+
* Sets the maximum number of items for batch operations
147+
*
148+
* @api
149+
*/
150+
protected function setBatchSize(int $batchSize): void
151+
{
152+
$this->batchSize = $batchSize;
153+
}
154+
140155
/**
141156
* Saves data in the cache.
142157
*
@@ -348,6 +363,64 @@ public function flushByTag(string $tag): int
348363
return $flushed;
349364
}
350365

366+
/**
367+
* Removes all cache entries of this cache which are tagged by any of the specified tags.
368+
*
369+
* @throws Exception
370+
* @api
371+
*/
372+
public function flushByTags(array $tags): int
373+
{
374+
$this->connect();
375+
$this->databaseHandle->beginTransaction();
376+
$flushed = 0;
377+
378+
// All queries need to be run in batches as every backend has a parameter limit.
379+
try {
380+
// Reduce batch size by 2 as we also pass the context and cache identifier parameters.
381+
$batchSize = $this->batchSize - 2;
382+
383+
// Step 1: Collect all identifiers to be flushed for the given tags
384+
$identifiers = [];
385+
for ($i = 0, $iMax = count($tags); $i < $iMax; $i += $batchSize) {
386+
$tagList = array_slice($tags, $i, $batchSize);
387+
$tagPlaceholders = implode(',', array_fill(0, count($tagList), '?'));
388+
$statementHandle = $this->databaseHandle->prepare('SELECT "identifier" FROM "' . $this->tagsTableName . '" WHERE "context"=? AND "cache"=? AND "tag" IN (' . $tagPlaceholders . ')');
389+
$statementHandle->execute(array_merge([$this->context(), $this->cacheIdentifier], $tagList));
390+
$result = $statementHandle->fetchAll();
391+
$identifiers[]= array_column($result, 'identifier');
392+
}
393+
$identifiers = array_merge([], ...$identifiers);
394+
395+
// Step 2: Flush all collected identifiers
396+
for ($i = 0, $iMax = count($identifiers); $i < $iMax; $i += $batchSize) {
397+
$identifierList = array_slice($identifiers, $i, $batchSize);
398+
$identifierPlaceholders = implode(',', array_fill(0, count($identifierList), '?'));
399+
$statementHandle = $this->databaseHandle->prepare('DELETE FROM "' . $this->cacheTableName . '" WHERE "context"=? AND "cache"=? AND "identifier" IN (' . $identifierPlaceholders . ')');
400+
$statementHandle->execute(array_merge([$this->context(), $this->cacheIdentifier], $identifierList));
401+
402+
// Update the flushed counter
403+
$flushed += $statementHandle->rowCount();
404+
}
405+
406+
// Step 3: Remove all given tags
407+
for ($i = 0, $iMax = count($tags); $i < $iMax; $i += $batchSize) {
408+
$tagList = array_slice($tags, $i, $batchSize);
409+
$tagPlaceholders = implode(',', array_fill(0, count($tagList), '?'));
410+
$statementHandle = $this->databaseHandle->prepare('DELETE FROM "' . $this->tagsTableName . '" WHERE "context"=? AND "cache"=? AND "tag" IN (' . $tagPlaceholders . ')');
411+
$statementHandle->execute(array_merge([$this->context(), $this->cacheIdentifier], $tagList));
412+
}
413+
414+
$this->databaseHandle->commit();
415+
} catch (\Exception $exception) {
416+
$this->databaseHandle->rollBack();
417+
418+
throw $exception;
419+
}
420+
421+
return $flushed;
422+
}
423+
351424
/**
352425
* Finds and returns all cache entry identifiers which are tagged by the
353426
* specified tag.

Neos.Flow/Documentation/TheDefinitiveGuide/PartIII/Caching.rst

+13-1
Original file line numberDiff line numberDiff line change
@@ -458,10 +458,22 @@ Options
458458
| | * sqlite:/path/to/sqlite.db | | | |
459459
| | * sqlite::memory: | | | |
460460
+----------------+----------------------------------------------------+-----------+--------+---------+
461-
| username | Username to use for the database connection | No | | |
461+
| username | Username to use for the database connection. | No | | |
462462
+----------------+----------------------------------------------------+-----------+--------+---------+
463463
| password | Password to use for the database connection. | No | | |
464464
+----------------+----------------------------------------------------+-----------+--------+---------+
465+
| cacheTableName | Table name to store cache entries | No | string | cache |
466+
+----------------+----------------------------------------------------+-----------+--------+---------+
467+
| tagsTableName | Table name to store cache tags | No | string | tags |
468+
+----------------+----------------------------------------------------+-----------+--------+---------+
469+
| batchSize | Maximum number of parameters per query for | No | int | 999 |
470+
| | for batch operations. | | | |
471+
| | | | | |
472+
| | This value should be adjusted based on type of | | | |
473+
| | the data source to increase performance. | | | |
474+
| | E.g. Postgres supports up to 65535 parameters, | | | |
475+
| | but SQLite only 999 for older versions. | | | |
476+
+----------------+----------------------------------------------------+-----------+--------+---------+
465477

466478
Neos\\Cache\\Backend\\RedisBackend
467479
----------------------------------

0 commit comments

Comments
 (0)