Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-5502: Added additional tag cleaning to limit down number of orphaned tag entries #372

Open
wants to merge 6 commits into
base: 1.3
Choose a base branch
from

Conversation

mateuszbieniek
Copy link
Contributor

@mateuszbieniek mateuszbieniek commented May 9, 2023

Question Answer
JIRA issue IBX-5502
Type bug
Target Ibexa version v3.3
BC breaks no

Due to the how cache is implemented in Symfony-based projects using the Redis adapter (and probably - not only Redis), entries (Members) inside a Tag (Set) are removed only when the tag is purged. Due to that, we can encounter on multiple occasions a situation, when a Tag is purged, resulting in cache items (Keys) being purged as well, but only the Tag that triggered a purge is cleaned up. All other tags that contain entries pointing to cache items that no longer exist will remain there until the affected tag is purged - which often is "never".

This results in orphaned entries in tags (or even in whole orphaned tags) filling up the memory. Memory allocated to tags in Redis is nonevictable, therefore it is freed only when purged on purpose (maxmemory-policy setting won't affect orphaned members/sets). On very big and/or constantly changing content structures, this unevictable memory allocated to orphaned tags and entries can significantly lower the amount of memory available for standard cache items to be stored.

Additional Tag purging added in this PR reduces this effect, but - unfortunately - it is not possible to resolve it fully with current Symfony's cache adapters implementation, due to the fact that there is no available interface to remove an entry from a Tag, without purging it whole - and that would result in purging cache items that were nor related to the original action performed on content.

Additional tag purging introduced by PR:

  • ContentHandler::updateContent:
    Multiple content-related entries (ibx-c-<contentId>-<versionNo>-0, ibx-c-<contentId>-<versionNo>-<languageCode>, ibx-cvi-<contentId>, ibx-cl-<contentId>-pfd are orphaned in tags: l-<locationId>, lp-<locationId>, c-<contentId>-v-<versionNo>, c-<contentId>-v-<prevVersionNo>. Added purging of those tags to avoid it.

  • TrashHandler::trashSubtree:
    Multiple content-related entries are orphaned (ibx-c-<contentId>-v<versionNumber>, ibx-cl-<contentId>, ibx-cvi-<contentId>, ibx-l-<locationId>-<languageCode>, ibx-c-<contentId>-<versionNo>-0, ibx-c-<contentId>-<versionNo>-<languageCode>, ibx-cvi-<contentId> ... (many more) are orphaned in tags: l-<locationId>, c-<contentId>-v-<versionNo>.

Handling those two occurrences helps to limit greatly the number of orphaned members.

My tests show, that one of the biggest culprits here is lp-<locationIdOfParent> tag, which can have multiple orphaned entries - therefore it might be a good idea to purge it as well, to avoid blocked memory in cost of potentially small performance loss - up for discussion.

This PR won't resolve the issue fully but limits the scope of it using available tools. To resolve this fully, we will need (in my opinion):

  • an interface to remove entries from sets
  • a garbage collector, as entries in tags might become orphaned as well when a key is evicted by Redis when maxmemory limit is reached.
    But those two are out of this PR scope.

Checklist:

  • Provided PR description.
  • Tested the solution manually.
  • Checked that target branch is set correctly (master for features, the oldest supported for bugs).
  • Ran PHP CS Fixer for new PHP code (use $ composer fix-cs).
  • Asked for a review (ping @ezsystems/engineering-team).

@mateuszbieniek mateuszbieniek added the Bug Something isn't working label May 9, 2023
@mateuszbieniek mateuszbieniek requested a review from a team May 9, 2023 14:25
@sonarcloud
Copy link

sonarcloud bot commented May 9, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
2.2% 2.2% Duplication

Copy link
Member

@konradoboza konradoboza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failing tests need to be investigated. Besides, you mentioned clearing tags with -languageCode suffix and I am kinda missing where it's done exactly.

eZ/Publish/Core/Persistence/Cache/ContentHandler.php Outdated Show resolved Hide resolved
$this->cacheIdentifierGenerator->generateTag(
$locations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentId);

$locationTags = array_map(function (Content\Location $location) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$locationTags = array_map(function (Content\Location $location) {
$locationTags = array_map(function (Content\Location $location): string {

eZ/Publish/Core/Persistence/Cache/ContentHandler.php Outdated Show resolved Hide resolved
eZ/Publish/Core/Persistence/Cache/TrashHandler.php Outdated Show resolved Hide resolved
@konradoboza konradoboza requested a review from a team May 10, 2023 09:51
@alongosz alongosz changed the title IBX-5502 - Added additional tag cleaning to limit down number of orphaned tag entries IBX-5502: Added additional tag cleaning to limit down number of orphaned tag entries Jul 28, 2023
@mateuszbieniek
Copy link
Contributor Author

I apologize - I've missed the CR.

Failing tests need to be investigated.

Fixed.

Besides, you mentioned clearing tags with -languageCode suffix and I am kinda missing where it's done exactly.

It is not a tag, but a key. For example, this key is a member of a "Location" tag - those additional tag purgings resolves this key being orphaned when Content is sent to the Trash.

@sonarcloud
Copy link

sonarcloud bot commented Aug 10, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
2.2% 2.2% Duplication

@alongosz alongosz self-requested a review October 10, 2023 07:49
Copy link

@kisztof kisztof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mateuszbieniek
Copy link
Contributor Author

@kisztof done (test failing not related to pr) and thank you for reminding me. Happy new year!

Copy link

sonarcloud bot commented Jan 4, 2024

Quality Gate Passed Quality Gate passed

Kudos, no new issues were introduced!

0 New issues
0 Security Hotspots
No data about Coverage
2.2% Duplication on New Code

See analysis details on SonarCloud

Copy link
Member

@konradoboza konradoboza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My tests show, that one of the biggest culprits here is lp- tag, which can have multiple orphaned entries - therefore it might be a good idea to purge it as well, to avoid blocked memory in cost of potentially small performance loss - up for discussion.

Are you able to tell what performance losses are we observing here?

@@ -47,7 +49,7 @@ public function providerForUnCachedMethods(): array
['setStatus', [2, 0, 1], [['content_version', [2, 1], false]], null, ['c-2-v-1']],
['setStatus', [2, 1, 1], [['content', [2], false]], null, ['c-2']],
['updateMetadata', [2, new MetadataUpdateStruct()], [['content', [2], false]], null, ['c-2']],
['updateContent', [2, 1, new UpdateStruct()], [['content_version', [2, 1], false]], null, ['c-2-v-1']],
//['updateContent', [2, 1, new UpdateStruct()], [['content_version', [2, 1], false]], null, ['c-2-v-1']],
Copy link
Member

@konradoboza konradoboza Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this line commented?

Copy link
Contributor Author

@mateuszbieniek mateuszbieniek Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can see this is a copypaste of the line above - I was probably checking something and overlooked it.
Ofc I'll fix that - thank you for spotting this!

/**
* @covers \eZ\Publish\Core\Persistence\Cache\ContentHandler::updateContent
*/
public function testUpdateContent()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are plenty of missing return types in here, but increasing code quality even with such small steps is worth the effort to me:

Suggested change
public function testUpdateContent()
public function testUpdateContent(): void

$this->cacheIdentifierGeneratorMock
->expects($this->exactly(5))
->method('generateTag')
->withConsecutive(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withConsecutive and willReturnOnConsecutiveCalls are deprecated in the newer versions of PHPUnit. Please consider changing those to returnValueMap here as it would be needed on merge-up anyway. For example, you can refer to https://github.com/ibexa/product-catalog/pull/1034/files#diff-df82e5aba4361380977da9b3843954e8614ebbb83c317a6bd45d4b53c0fe55f2R95.

@@ -51,13 +55,23 @@ public function trashSubtree($locationId)
}, $reverseRelations);
}

$versionTags = array_map(function (VersionInfo $versionInfo) use ($contentId): string {
return $this->cacheIdentifierGenerator->generateTag(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return $this->cacheIdentifierGenerator->generateTag(
return $this->cacheIdentifierGenerator->generateTag(

Comment on lines +44 to +45
$reverseRelations = $this->persistenceHandler->contentHandler()->loadRelations($contentId);
$versions = $this->persistenceHandler->contentHandler()->listVersions($contentId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$reverseRelations = $this->persistenceHandler->contentHandler()->loadRelations($contentId);
$versions = $this->persistenceHandler->contentHandler()->listVersions($contentId);
$contentHandler = $this->persistenceHandler->contentHandler();
$reverseRelations = $contentHandler->loadRelations($contentId);
$versions = $contentHandler->listVersions($contentId);

@mateuszbieniek
Copy link
Contributor Author

My tests show, that one of the biggest culprits here is lp- tag, which can have multiple orphaned entries - therefore it might be a good idea to purge it as well, to avoid blocked memory in cost of potentially small performance loss - up for discussion.

Are you able to tell what performance losses are we observing here?

To be frank no, but looking at it this is a simple tag. Due to the way how caching works - worst-case scenario it will get recreated the next time it is required, and as it is lp - Location Parent (?) - I would not expect anything spectacular, especially - judging from the public issue description - you have to empty trash for this to get orphaned - I won't expect that this will be ever again requested after that.

In addition, freeing this memory to be reused should - especially in the long run - benefit to overall application performance (more memory, less evictions).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working
Development

Successfully merging this pull request may close these issues.

8 participants