Skip to content

Commit 306c4d3

Browse files
authored
IBX-5270: Implemented cache indices validator and fixed broken loadSubtreeIds pattern
For more details see https://issues.ibexa.co/browse/IBX-5270 and #367 * Implemented cache indices validator * Fixed broken `loadSubtreeIds` pattern
1 parent 6cd2cb8 commit 306c4d3

File tree

10 files changed

+123
-31
lines changed

10 files changed

+123
-31
lines changed

eZ/Bundle/EzPublishCoreBundle/DependencyInjection/EzPublishCoreExtension.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class EzPublishCoreExtension extends Extension implements PrependExtensionInterf
3838
'mappings' => [],
3939
];
4040

41+
private const DEBUG_PARAM = 'kernel.debug';
42+
4143
/** @var \eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\Suggestion\Collector\SuggestionCollector */
4244
private $suggestionCollector;
4345

@@ -399,6 +401,13 @@ private function handleCache(array $config, ContainerBuilder $container, FileLoa
399401

400402
$container->setParameter('ezpublish.http_cache.purge_type', $purgeType);
401403
}
404+
405+
if (
406+
$container->hasParameter(self::DEBUG_PARAM)
407+
&& $container->getParameter(self::DEBUG_PARAM) === true
408+
) {
409+
$loader->load('debug/cache_validator.yaml');
410+
}
402411
}
403412

404413
/**
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
Ibexa\Core\Persistence\Cache\CacheIndicesValidatorInterface:
3+
alias: Ibexa\Core\Persistence\Cache\CacheIndicesValidator
4+
5+
Ibexa\Core\Persistence\Cache\CacheIndicesValidator:
6+
calls:
7+
- [ setLogger, [ '@?logger' ] ]
8+
tags:
9+
- { name: 'monolog.logger', channel: 'ibexa.core' }

eZ/Publish/Core/Persistence/Cache/AbstractInMemoryHandler.php

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
use eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface;
1010
use eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache;
11+
use Ibexa\Core\Persistence\Cache\CacheIndicesValidatorInterface;
1112

1213
/**
1314
* Abstract handler for use in other SPI Handlers.
@@ -35,25 +36,23 @@ abstract class AbstractInMemoryHandler
3536
*/
3637
private $inMemory;
3738

38-
/**
39-
* Setups current handler with everything needed.
40-
*
41-
* @param \eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface $cache
42-
* @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
43-
* @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
44-
*/
39+
/** @var \Ibexa\Core\Persistence\Cache\CacheIndicesValidatorInterface */
40+
private $cacheIndicesValidator;
41+
4542
public function __construct(
4643
TransactionAwareAdapterInterface $cache,
4744
PersistenceLogger $logger,
48-
InMemoryCache $inMemory
45+
InMemoryCache $inMemory,
46+
?CacheIndicesValidatorInterface $cacheIndicesValidator = null
4947
) {
5048
$this->cache = $cache;
5149
$this->logger = $logger;
5250
$this->inMemory = $inMemory;
51+
$this->cacheIndicesValidator = $cacheIndicesValidator;
5352
}
5453

5554
/**
56-
* Load one cache item from cache and loggs the hits / misses.
55+
* Loads one cache item from cache and logs the hits / misses.
5756
*
5857
* Load items from in-memory cache, symfony cache pool or backend in that order.
5958
* If not cached the returned objects will be placed in cache.
@@ -99,6 +98,11 @@ final protected function getCacheValue(
9998
$this->logger->logCacheMiss($arguments ?: [$id], 3);
10099

101100
$object = $backendLoader($id);
101+
102+
if ($this->cacheIndicesValidator !== null) {
103+
$this->cacheIndicesValidator->validate($keyPrefix, $object, $cacheIndexes);
104+
}
105+
102106
$this->inMemory->setMulti([$object], $cacheIndexes);
103107
$this->cache->save(
104108
$cacheItem

eZ/Publish/Core/Persistence/Cache/AbstractInMemoryPersistenceHandler.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface;
1010
use eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache;
1111
use eZ\Publish\SPI\Persistence\Handler as PersistenceHandler;
12+
use Ibexa\Core\Persistence\Cache\CacheIndicesValidatorInterface;
1213
use Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface;
1314
use Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierSanitizer;
1415
use Ibexa\Core\Persistence\Cache\LocationPathConverter;
@@ -32,27 +33,17 @@ abstract class AbstractInMemoryPersistenceHandler extends AbstractInMemoryHandle
3233
/** @var \Ibexa\Core\Persistence\Cache\LocationPathConverter */
3334
protected $locationPathConverter;
3435

35-
/**
36-
* Setups current handler with everything needed.
37-
*
38-
* @param \eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface $cache
39-
* @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
40-
* @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
41-
* @param \eZ\Publish\SPI\Persistence\Handler $persistenceHandler
42-
* @param \Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface $cacheIdentifierGenerator
43-
* @param \Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierSanitizer $cacheIdentifierSanitizer
44-
* @param \Ibexa\Core\Persistence\Cache\LocationPathConverter $locationPathConverter
45-
*/
4636
public function __construct(
4737
TransactionAwareAdapterInterface $cache,
4838
PersistenceLogger $logger,
4939
InMemoryCache $inMemory,
5040
PersistenceHandler $persistenceHandler,
5141
CacheIdentifierGeneratorInterface $cacheIdentifierGenerator,
5242
CacheIdentifierSanitizer $cacheIdentifierSanitizer,
53-
LocationPathConverter $locationPathConverter
43+
LocationPathConverter $locationPathConverter,
44+
?CacheIndicesValidatorInterface $cacheIndicesValidator = null
5445
) {
55-
parent::__construct($cache, $logger, $inMemory);
46+
parent::__construct($cache, $logger, $inMemory, $cacheIndicesValidator);
5647

5748
$this->persistenceHandler = $persistenceHandler;
5849
$this->cacheIdentifierGenerator = $cacheIdentifierGenerator;

eZ/Publish/Core/Persistence/Cache/InMemory/InMemoryCache.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public function get(string $key)
116116
*/
117117
public function setMulti(array $objects, callable $objectIndexes, string $listIndex = null): void
118118
{
119-
// If objects accounts for more then 20% of our limit, assume it's bulk load and skip saving in-memory
119+
// If objects accounts for more than 20% of our limit, assume it's bulk load and skip saving in-memory
120120
if ($this->enabled === false || \count($objects) >= $this->limit / 5) {
121121
return;
122122
}
@@ -127,7 +127,7 @@ public function setMulti(array $objects, callable $objectIndexes, string $listIn
127127
}
128128

129129
$expiryTime = microtime(true) + $this->ttl;
130-
// if set add objects to cache on list index (typically a "all" key)
130+
// if set add objects to cache on list index (typically an "all" key)
131131
if ($listIndex) {
132132
$this->cache[$listIndex] = $objects;
133133
$this->cacheExpiryTime[$listIndex] = $expiryTime;

eZ/Publish/Core/Persistence/Cache/PersistenceLogger.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class PersistenceLogger
3434

3535
/**
3636
* @param bool $logCalls Flag to enable logging of calls or not, provides extra debug info about calls made to SPI
37-
* level, including where they come form. However this uses quite a bit of memory.
37+
* level, including where they come form. However, this uses quite a bit of memory.
3838
*/
3939
public function __construct(bool $logCalls = true)
4040
{
@@ -71,10 +71,10 @@ public function logCall(string $method, array $arguments = []): void
7171
/**
7272
* Log Cache miss, gets info it needs by backtrace if needed.
7373
*
74-
* @since 7.5
75-
*
7674
* @param array $arguments
7775
* @param int $traceOffset
76+
*
77+
* @since 7.5
7878
*/
7979
public function logCacheMiss(array $arguments = [], int $traceOffset = 2): void
8080
{
@@ -95,12 +95,12 @@ public function logCacheMiss(array $arguments = [], int $traceOffset = 2): void
9595
/**
9696
* Log a Cache hit, gets info it needs by backtrace if needed.
9797
*
98-
* @since 7.5
99-
*
10098
* @param array $arguments
10199
* @param int $traceOffset
102100
* @param bool $inMemory Denotes is cache hit was from memory (php variable), as opposed to from cache pool which
103101
* is usually disk or remote cache service.
102+
*
103+
* @since 7.5
104104
*/
105105
public function logCacheHit(array $arguments = [], int $traceOffset = 2, bool $inMemory = false): void
106106
{

eZ/Publish/Core/Persistence/Cache/Tests/AbstractBaseHandlerTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use eZ\Publish\Core\Persistence\Cache\UserHandler as CacheUserHandler;
3030
use eZ\Publish\Core\Persistence\Cache\UserPreferenceHandler as CacheUserPreferenceHandler;
3131
use eZ\Publish\SPI\Persistence\Handler;
32+
use Ibexa\Core\Persistence\Cache\CacheIndicesValidatorInterface;
3233
use Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface;
3334
use Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierSanitizer;
3435
use Ibexa\Core\Persistence\Cache\LocationPathConverter;
@@ -67,6 +68,9 @@ abstract class AbstractBaseHandlerTest extends TestCase
6768
/** @var \Ibexa\Core\Persistence\Cache\LocationPathConverter */
6869
protected $locationPathConverter;
6970

71+
/** @var \Ibexa\Core\Persistence\Cache\CacheIndicesValidatorInterface */
72+
protected $cacheIndicesValidator;
73+
7074
/**
7175
* Setup the HandlerTest.
7276
*/
@@ -81,6 +85,7 @@ protected function setUp(): void
8185
$this->cacheIdentifierGeneratorMock = $this->createMock(CacheIdentifierGeneratorInterface::class);
8286
$this->cacheIdentifierSanitizer = new CacheIdentifierSanitizer();
8387
$this->locationPathConverter = new LocationPathConverter();
88+
$this->cacheIndicesValidator = $this->createMock(CacheIndicesValidatorInterface::class);
8489

8590
$cacheAbstractHandlerArguments = $this->provideAbstractCacheHandlerArguments();
8691
$cacheInMemoryHandlerArguments = $this->provideInMemoryCacheHandlerArguments();
@@ -178,6 +183,7 @@ private function provideInMemoryCacheHandlerArguments(): array
178183
$this->cacheIdentifierGeneratorMock,
179184
$this->cacheIdentifierSanitizer,
180185
$this->locationPathConverter,
186+
$this->cacheIndicesValidator,
181187
];
182188
}
183189
}

eZ/Publish/Core/settings/storage_engines/cache.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ parameters:
4141
location: 'l-%s'
4242
location_path: 'lp-%s'
4343
location_remote_id: 'lri'
44-
location_subtree: 'ls'
44+
location_subtree: 'ls-%s'
4545
content_locations_with_parent_for_draft_suffix: 'cl-%s-pfd'
4646
notification: 'n-%s'
4747
notification_count: 'nc-%s'
@@ -135,7 +135,7 @@ parameters:
135135
location: 'l-%s'
136136
location_path: 'lp-%s'
137137
location_remote_id: 'lri'
138-
location_subtree: 'ls'
138+
location_subtree: 'ls-%s'
139139
content_locations_with_parent_for_draft_suffix: 'cl-%s-pfd'
140140
notification: 'n-%s'
141141
notification_count: 'nc-%s'
@@ -275,6 +275,7 @@ services:
275275
- '@Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface'
276276
- '@Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierSanitizer'
277277
- '@Ibexa\Core\Persistence\Cache\LocationPathConverter'
278+
- '@?Ibexa\Core\Persistence\Cache\CacheIndicesValidatorInterface'
278279

279280
ezpublish.spi.persistence.cache.sectionHandler:
280281
class: eZ\Publish\Core\Persistence\Cache\SectionHandler
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Core\Persistence\Cache;
10+
11+
use Psr\Log\LoggerAwareInterface;
12+
use Psr\Log\LoggerAwareTrait;
13+
use Psr\Log\LoggerInterface;
14+
15+
/**
16+
* @internal
17+
*/
18+
final class CacheIndicesValidator implements CacheIndicesValidatorInterface, LoggerAwareInterface
19+
{
20+
use LoggerAwareTrait;
21+
22+
public function __construct(?LoggerInterface $logger = null)
23+
{
24+
$this->logger = $logger;
25+
}
26+
27+
/**
28+
* @param mixed $object
29+
*/
30+
public function validate(string $keyPrefix, $object, callable $cacheIndices): void
31+
{
32+
if ($this->logger === null) {
33+
return;
34+
}
35+
36+
$cacheIndicesUnpacked = $cacheIndices($object);
37+
38+
foreach ($cacheIndicesUnpacked as $cacheIndex) {
39+
if (strpos($cacheIndex, $keyPrefix) === 0) {
40+
return;
41+
}
42+
}
43+
44+
$this->logger->error(
45+
sprintf(
46+
'There is no corresponding cache index for key prefix %s. Cache indices are as follows: %s.',
47+
$keyPrefix,
48+
implode(', ', $cacheIndicesUnpacked)
49+
)
50+
);
51+
}
52+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Core\Persistence\Cache;
10+
11+
/**
12+
* @internal
13+
*/
14+
interface CacheIndicesValidatorInterface
15+
{
16+
/**
17+
* @param mixed $object
18+
*/
19+
public function validate(string $keyPrefix, $object, callable $cacheIndices): void;
20+
}

0 commit comments

Comments
 (0)