From c40e88bfeac1cb1839101093a8f692130f946e7b Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 27 Aug 2024 09:51:39 +0200 Subject: [PATCH] FEATURE: Overhaul ContentCacheFlusher #5175 --- .../AssetChangeHandlerForCacheFlushing.php | 93 ++++++++++++++ .../Fusion/Cache/CacheFlushingStrategy.php | 11 ++ .../Fusion/Cache/ContentCacheFlusher.php | 117 ++++++------------ ...phProjectorCatchUpHookForCacheFlushing.php | 9 +- Neos.Neos/Classes/Package.php | 6 +- 5 files changed, 148 insertions(+), 88 deletions(-) create mode 100644 Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php create mode 100644 Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php diff --git a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php new file mode 100644 index 00000000000..bdcd125fe2d --- /dev/null +++ b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php @@ -0,0 +1,93 @@ +getOriginalAsset(); + } + + $filter = AssetUsageFilter::create() + ->withAsset($this->persistenceManager->getIdentifierByObject($asset)) + ->includeVariantsOfAsset(); + + $workspaceNamesByContentStreamId = []; + foreach ($this->globalAssetUsageService->findByFilter($filter) as $contentRepositoryId => $usages) { + $contentRepository = $this->contentRepositoryRegistry->get(ContentRepositoryId::fromString($contentRepositoryId)); + foreach ($usages as $usage) { + // TODO: Remove when WorkspaceName is part of the AssetUsageProjection + $workspaceName = $workspaceNamesByContentStreamId[$contentRepositoryId][$usage->contentStreamId->value] ?? null; + if ($workspaceName === null) { + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); + if ($workspace === null) { + continue; + } + $workspaceName = $workspace->workspaceName; + $workspaceNamesByContentStreamId[$contentRepositoryId][$usage->contentStreamId->value] = $workspaceName; + } + // + + $nodeAggregate = $contentRepository->getContentGraph($workspaceName)->findNodeAggregateById($usage->nodeAggregateId); + if ($nodeAggregate === null) { + continue; + } + $flushNodeAggregateRequest = FlushNodeAggregateRequest::create( + $contentRepository->id, + $workspaceName, + $nodeAggregate->nodeAggregateId, + $nodeAggregate->nodeTypeName, + $this->determineParentNodeAggregateIds($contentRepository, $workspaceName, $nodeAggregate->nodeAggregateId), + ); + $this->contentCacheFlusher->flushNodeAggregate($flushNodeAggregateRequest, CacheFlushingStrategy::ON_SHUTDOWN); + } + } + } + + private function determineParentNodeAggregateIds(ContentRepository $contentRepository, WorkspaceName $workspaceName, NodeAggregateId $childNodeAggregateId): NodeAggregateIds + { + $parentNodeAggregates = $contentRepository->getContentGraph($workspaceName)->findParentNodeAggregates($childNodeAggregateId); + $parentNodeAggregateIds = NodeAggregateIds::fromArray( + array_map(static fn (NodeAggregate $parentNodeAggregate) => $parentNodeAggregate->nodeAggregateId, iterator_to_array($parentNodeAggregates)) + ); + + foreach ($parentNodeAggregateIds as $parentNodeAggregateId) { + // Prevent infinite loops + if (!$parentNodeAggregateIds->contain($parentNodeAggregateId)) { + $parentNodeAggregateIds->merge($this->determineParentNodeAggregateIds($contentRepository, $workspaceName, $parentNodeAggregateId)); + } + } + + return $parentNodeAggregateIds; + } +} diff --git a/Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php b/Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php new file mode 100644 index 00000000000..29c43aebc5e --- /dev/null +++ b/Neos.Neos/Classes/Fusion/Cache/CacheFlushingStrategy.php @@ -0,0 +1,11 @@ +value ); - $this->flushTags($tagsToFlush); + $this->flushTags($tagsToFlush, $cacheFlushingStrategy); } /** - * Main entry point to *directly* flush the caches of a given NodeAggregate + * Main entry point to flush the caches of a given NodeAggregate with a given strategy. */ public function flushNodeAggregate( - FlushNodeAggregateRequest $flushNodeAggregateRequest + FlushNodeAggregateRequest $flushNodeAggregateRequest, + CacheFlushingStrategy $cacheFlushingStrategy ): void { $tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".'; @@ -92,7 +88,7 @@ public function flushNodeAggregate( $tagsToFlush ); - $this->flushTags($tagsToFlush); + $this->flushTags($tagsToFlush, $cacheFlushingStrategy); } /** @@ -194,13 +190,26 @@ private function collectTagsForChangeOnNodeType( return $tagsToFlush; } + /** + * Flush caches according to the given tags and strategy. + * + * @param array $tagsToFlush + */ + protected function flushTags(array $tagsToFlush, CacheFlushingStrategy $cacheFlushingStrategy): void + { + match ($cacheFlushingStrategy) { + CacheFlushingStrategy::IMMEDIATELY => $this->flushTagsImmediately($tagsToFlush), + CacheFlushingStrategy::ON_SHUTDOWN => $this->collectTagsForFlushOnShutdown($tagsToFlush) + }; + } + /** - * Flush caches according to the given tags. + * Flush caches according to the given tags immediately. * * @param array $tagsToFlush */ - protected function flushTags(array $tagsToFlush): void + protected function flushTagsImmediately(array $tagsToFlush): void { if ($this->debugMode) { foreach ($tagsToFlush as $tag => $logMessage) { @@ -219,6 +228,16 @@ protected function flushTags(array $tagsToFlush): void } } + /** + * Collect tags to get flushed on shutdown. + * + * @param array $tagsToFlush + */ + protected function collectTagsForFlushOnShutdown(array $tagsToFlush): void + { + $this->tagsToFlushAfterPersistance = array_merge($tagsToFlush, $this->tagsToFlushAfterPersistance); + } + /** * @param NodeType $nodeType * @return array @@ -237,72 +256,12 @@ function (array $types, NodeType $superType) use ($self) { return array_unique($types); } - - /** - * Fetches possible usages of the asset and registers nodes that use the asset as changed. - * - * @throws NodeTypeNotFound - */ - // TODO: Move out of the ContentCacheFlusher and - public function registerAssetChange(AssetInterface $asset): void - { - // In Nodes only assets are referenced, never asset variants directly. When an asset - // variant is updated, it is passed as $asset, but since it is never "used" by any node - // no flushing of corresponding entries happens. Thus we instead use the original asset - // of the variant. - if ($asset instanceof AssetVariantInterface) { - $asset = $asset->getOriginalAsset(); - } - - $tagsToFlush = []; - $filter = AssetUsageFilter::create() - ->withAsset($this->persistenceManager->getIdentifierByObject($asset)) - ->includeVariantsOfAsset(); - - - $workspaceNamesByContentStreamId = []; - foreach ($this->globalAssetUsageService->findByFilter($filter) as $contentRepositoryId => $usages) { - foreach ($usages as $usage) { - // TODO: Remove when WorkspaceName is part of the AssetUsageProjection - $workspaceName = $workspaceNamesByContentStreamId[$contentRepositoryId][$usage->contentStreamId->value] ?? null; - if ($workspaceName === null) { - $contentRepository = $this->contentRepositoryRegistry->get(ContentRepositoryId::fromString($contentRepositoryId)); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); - if ($workspace === null) { - continue; - } - $workspaceName = $workspace->workspaceName; - $workspaceNamesByContentStreamId[$contentRepositoryId][$usage->contentStreamId->value] = $workspaceName; - } - // - - $flushNodeAggregateRequest = FlushNodeAggregateRequest::create( - ContentRepositoryId::fromString($contentRepositoryId), - $workspaceName, - $usage->nodeAggregateId, - NodeTypeName::fromString("Neos.Neos:Content"), - NodeAggregateIds::create(), - ); - - $tagsToFlush = array_merge( - $this->collectTagsForChangeOnNodeAggregate( - $flushNodeAggregateRequest, - true - ), - $tagsToFlush - ); - } - } - - $this->tagsToFlushAfterPersistance = array_merge($tagsToFlush, $this->tagsToFlushAfterPersistance); - } - /** * Flush caches according to the previously registered changes. */ public function flushCollectedTags(): void { - $this->flushTags($this->tagsToFlushAfterPersistance); + $this->flushTagsImmediately($this->tagsToFlushAfterPersistance); $this->tagsToFlushAfterPersistance = []; } diff --git a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php index 88d29fd864e..a4068eda1ca 100644 --- a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php @@ -21,7 +21,6 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventEnvelope; -use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasDiscarded; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyDiscarded; @@ -263,13 +262,13 @@ private function determineParentNodeAggregateIds(WorkspaceName $workspaceName, N public function onBeforeBatchCompleted(): void { foreach ($this->flushNodeAggregateRequestsOnBeforeBatchCompleted as $index => $request) { - $this->contentCacheFlusher->flushNodeAggregate($request); + $this->contentCacheFlusher->flushNodeAggregate($request, CacheFlushingStrategy::IMMEDIATELY); $this->flushNodeAggregateRequestsOnAfterCatchUp[$index] = $request; } $this->flushNodeAggregateRequestsOnBeforeBatchCompleted = []; foreach ($this->flushWorkspaceRequestsOnBeforeBatchCompleted as $index => $request) { - $this->contentCacheFlusher->flushWorkspace($request); + $this->contentCacheFlusher->flushWorkspace($request, CacheFlushingStrategy::IMMEDIATELY); $this->flushWorkspaceRequestsOnAfterCatchUp[$index] = $request; } $this->flushWorkspaceRequestsOnBeforeBatchCompleted = []; @@ -278,12 +277,12 @@ public function onBeforeBatchCompleted(): void public function onAfterCatchUp(): void { foreach ($this->flushNodeAggregateRequestsOnAfterCatchUp as $request) { - $this->contentCacheFlusher->flushNodeAggregate($request); + $this->contentCacheFlusher->flushNodeAggregate($request, CacheFlushingStrategy::IMMEDIATELY); } $this->flushNodeAggregateRequestsOnAfterCatchUp = []; foreach ($this->flushWorkspaceRequestsOnAfterCatchUp as $request) { - $this->contentCacheFlusher->flushWorkspace($request); + $this->contentCacheFlusher->flushWorkspace($request, CacheFlushingStrategy::IMMEDIATELY); } $this->flushWorkspaceRequestsOnAfterCatchUp = []; } diff --git a/Neos.Neos/Classes/Package.php b/Neos.Neos/Classes/Package.php index f117dbf1104..1022dd0db53 100644 --- a/Neos.Neos/Classes/Package.php +++ b/Neos.Neos/Classes/Package.php @@ -14,7 +14,6 @@ namespace Neos\Neos; -use Neos\EventStore\Model\EventEnvelope; use Neos\Flow\Cache\CacheManager; use Neos\Flow\Core\Bootstrap; use Neos\Flow\Monitor\FileMonitor; @@ -26,14 +25,13 @@ use Neos\Neos\Controller\Backend\ContentController; use Neos\Neos\Domain\Model\Site; use Neos\Neos\Domain\Service\SiteService; -use Neos\Neos\FrontendRouting\Projection\DocumentUriPathProjection; use Neos\Neos\Routing\Cache\RouteCacheFlusher; -use Neos\Neos\Fusion\Cache\ContentCacheFlusher; use Neos\Fusion\Core\Cache\ContentCache; use Neos\Neos\Service\EditorContentStreamZookeeper; use Neos\Media\Domain\Model\AssetInterface; use Neos\Neos\AssetUsage\GlobalAssetUsageService; use Neos\Flow\Persistence\PersistenceManagerInterface; +use Neos\Neos\Fusion\Cache\AssetChangeHandlerForCacheFlushing; /** * The Neos Package @@ -102,7 +100,7 @@ function ( $dispatcher->connect( AssetService::class, 'assetUpdated', - ContentCacheFlusher::class, + AssetChangeHandlerForCacheFlushing::class, 'registerAssetChange', false );