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

FEATURE: Optimised Redis cache backend #2721

Merged
merged 15 commits into from
Mar 25, 2022

Conversation

Sebobo
Copy link
Member

@Sebobo Sebobo commented Mar 2, 2022

What I did

With this change the flushByTags is optimised to run flush operations 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 #2718

Additionally all optimisations from https://github.com/sandstorm/OptimizedRedisCacheBackend were added.

How I did it

Instead of uploading the flush Lua script and only flushing the entries for one tag.
The same is now done for batches of tags reducing the calls to Redis and the
evaluation time Redis needs to parse the script.

How to verify it

#2718 includes updated tests for the Redis backend that will also check this method.

Checklist

@Sebobo Sebobo added the P: Cache label Mar 2, 2022
@Sebobo Sebobo self-assigned this Mar 2, 2022
@kitsunet
Copy link
Member

kitsunet commented Mar 3, 2022

Look neat! Thank you, does this superseed the sandstorm opt or does that still have any extras then?

@Sebobo
Copy link
Member Author

Sebobo commented Mar 3, 2022

@kitsunet thanks.

I only added the main new method in this PR, it doesn't cover all the other improvements they did. But the script is already based on the Sandstorm version of the single tag flush. I had a call yesterday with @skurfuerst and we also discussed to now bring over all improvements from the Sandstorm version.

I have a few more calls planned with people who modified the Redis backend to get their input too.

I would then put all those into a separate PR where everyone with improvements then can chime in.

Sebobo added 3 commits March 18, 2022 23:27
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 neos#2718
WIth this change optimisations from the
community package sandstorm/optimized-redis-backend
are introduced into the core Redis backend.
@Sebobo Sebobo force-pushed the feature/flush-by-tags-redis branch from 4c03485 to f9dd34f Compare March 18, 2022 22:27
@Sebobo
Copy link
Member Author

Sebobo commented Mar 18, 2022

@kitsunet I now added in another commit the remaining optimisations from the sandstorm package.
As my flushByTags is already based on their script and not on the flushByTag of the core version, I think it doesn't make sense to only optimise this one method and have it behave a bit different.

If the change should be "clean" I would need to make the flushByTags based on the old script.

Wdyt? Should I make a "clean" PR and then another for the other optimisations?
I mean the sandstorm version is running in a lot of projects afaik.

If we go this way, I would of course adjust the PR title and description.

@Sebobo Sebobo changed the title FEATURE: flushByTags for the Redis cache backend FEATURE: Optimised Redis cache backend Mar 21, 2022
@Sebobo Sebobo marked this pull request as ready for review March 21, 2022 08:22
@kdambekalns
Copy link
Member

Should I make a "clean" PR and then another for the other optimisations?

I remember faintly there were issues with that implementation when not used in a "strictly Neos" context, as it had cut some corners to optimize. Not sure if those are still existing, I'll try to research a bit more later today.

Copy link
Member

@kdambekalns kdambekalns left a comment

Choose a reason for hiding this comment

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

Ok, I think the only potential issue with the OptimizedRedisCacheBackend left is the documented behaviour around leaving stale tags in place and the effect that could have on findIdentifiersBytag(). So if that is "copied"

Another thing I noticed: In the README of OptimizedRedisCacheBackend it says

For implementing flush(), we just iterate over the relevant part of the keyspace and remove all elements. Before, this was done through a similar logic as explained in FlushByTag, because the Redis Backend was freezable (but we do not need this for Neos Content Cache).

So, if this is case, implementing FreezableBackendInterface should be removed from the backend, no?

@jonnitto
Copy link
Member

Trigger checks

@jonnitto jonnitto closed this Mar 24, 2022
@jonnitto jonnitto reopened this Mar 24, 2022
@Sebobo
Copy link
Member Author

Sebobo commented Mar 24, 2022

Ok, I think the only potential issue with the OptimizedRedisCacheBackend left is the documented behaviour around leaving stale tags in place and the effect that could have on findIdentifiersBytag(). So if that is "copied"

I will test the difference related to performance when keeping this part.

Another thing I noticed: In the README of OptimizedRedisCacheBackend it says

For implementing flush(), we just iterate over the relevant part of the keyspace and remove all elements. Before, this was done through a similar logic as explained in FlushByTag, because the Redis Backend was freezable (but we do not need this for Neos Content Cache).

So, if this is case, implementing FreezableBackendInterface should be removed from the backend, no?

But the backend is not only for Neos? So I would leave it in there. Shouldn't hurt to keep it. Doesn't look like I should have a big impact. But will test.

What do you think about increasing the minimum Redis version to the 6.0.0 which is the oldest with EOL?
Then we could also finally add some more advanced features in the nearer future than will also help with performance.

@kdambekalns
Copy link
Member

I fixed the Psalm complaint, but the test failures look legit…

@kdambekalns
Copy link
Member

kdambekalns commented Mar 24, 2022

So, if this is case, implementing FreezableBackendInterface should be removed from the backend, no?

But the backend is not only for Neos? So I would leave it in there. Shouldn't hurt to keep it.

That's my point - if it's kept, it should behave like before, but I understand the README says it differs… So either remove freezability or keep behaviour.

What do you think about increasing the minimum Redis version to the 6.0.0 which is the oldest with EOL? Then we could also finally add some more advanced features in the nearer future than will also help with performance.

Fine with me…

@Sebobo
Copy link
Member Author

Sebobo commented Mar 24, 2022

Will check

Sebobo added 5 commits March 24, 2022 14:47
All Redis versions < 6.0.0 are already EOL.
This would allow us to improve the backends
behaviour by using more recent features of Redis.
This prevents getting non existent identifiers when
calling `findIdentifiersByTag`.
Due to the removal of the `entries` cache entry to track
all entries, the iteration must now be done by retrieving
all matching keys from the cache.
@Sebobo
Copy link
Member Author

Sebobo commented Mar 24, 2022

Ok a unit test fails now, will check again...

@Sebobo Sebobo closed this Mar 24, 2022
@Sebobo Sebobo reopened this Mar 24, 2022
@Sebobo
Copy link
Member Author

Sebobo commented Mar 24, 2022

Everything green, hope I didn't miss anything. But at least I now know 1000% more about Redis than before 😎

Copy link
Member

@kdambekalns kdambekalns left a comment

Choose a reason for hiding this comment

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

Two more tiny suggestions… and the class docblock still says:

 * Since Redis < 2.8.0 does not provide a mechanism for iterating over keys,
 * a separate list with all entries is populated

That is not true anymore?! At least the version mentioned is no longer supported, so is there a better way?

Copy link
Member

@kdambekalns kdambekalns left a comment

Choose a reason for hiding this comment

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

Notwithstanding my tiny remarks, this seems to work just fine… I only did limited testing (compared to what @Sebobo did), but… ✅

@Sebobo
Copy link
Member Author

Sebobo commented Mar 24, 2022

Two more tiny suggestions… and the class docblock still says:

 * Since Redis < 2.8.0 does not provide a mechanism for iterating over keys,
 * a separate list with all entries is populated

That is not true anymore?! At least the version mentioned is no longer supported, so is there a better way?

You are right, that was my last change to clean that up after I had read it, but forgot to remove the text.
After working on this stuff for hours I'm half braindead 🧟‍♂️

@Sebobo Sebobo force-pushed the feature/flush-by-tags-redis branch from efa53e7 to 6ad44bc Compare March 24, 2022 20:14
@Sebobo Sebobo closed this Mar 24, 2022
@Sebobo Sebobo reopened this Mar 24, 2022
@mficzel
Copy link
Member

mficzel commented Mar 24, 2022

After the updated docblocks i get "Typed property Neos\Cache\Backend\RedisBackend::$frozen must not be accessed before initialization"

Does this rely on features from #2701

Co-authored-by: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com>
* @var integer Cursor used for iterating over cache entries
*/
protected $entryCursor = 0;
protected ?bool $frozen = null;
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
protected ?bool $frozen = null;
protected bool $frozen = false;

since the getter declares its return type as bool that would conflict anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

I assume it was done like that because when it's null the getter retrieves the value from Redis and "caches" it this way.

Copy link
Member

@albe albe left a comment

Choose a reason for hiding this comment

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

Don't know much about redis and I can't process the lua scripts in a way to decide if they are sane or not. So I just left three suggestions to use union false pseudo type instead of the more generic bool: https://www.php.net/manual/de/language.types.declarations.php#language.types.declarations.composite.union.false

}

/**
* {@inheritdoc}
*/
public function key()
public function key(): string|bool
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
public function key(): string|bool
public function key(): string|false

* @api
*/
public function get(string $entryIdentifier)
public function get(string $entryIdentifier): string|bool
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
public function get(string $entryIdentifier): string|bool
public function get(string $entryIdentifier): string|false

@@ -310,28 +355,30 @@ public function findIdentifiersByTag(string $tag): array
/**
* {@inheritdoc}
*/
public function current()
public function current(): string|bool
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
public function current(): string|bool
public function current(): string|false

Copy link
Member

@mficzel mficzel left a comment

Choose a reason for hiding this comment

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

Looks good to me and works on my machine. Am no redis expert

@mficzel mficzel merged commit 05e57cc into neos:master Mar 25, 2022
@Sebobo Sebobo deleted the feature/flush-by-tags-redis branch March 25, 2022 12:47
@skurfuerst
Copy link
Member

awesome work everybody ❤️ ❤️ ❤️

I'll add docs to OptimizedRedisCacheBackend, marking it as obsolete starting with Neos 8.0 💯

redis.call('DEL', key)
end
";
$this->redis->eval($script, [$this->getPrefixedIdentifier('frozen'), $this->getPrefixedIdentifier('')], 1);
Copy link
Member

Choose a reason for hiding this comment

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

$this->getPrefixedIdentifier('frozen') is not needed anymore, is it? At least KEYS[1] is never accessed.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is still needed, but properly the script is incorrect now. Will check and adjust the test. Thx for noticing!

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, you are right, the key is already included in the args with wildcard.

@Sebobo
Copy link
Member Author

Sebobo commented Mar 28, 2022

awesome work everybody ❤️ ❤️ ❤️

I'll add docs to OptimizedRedisCacheBackend, marking it as obsolete starting with Neos 8.0 💯

Hi @skurfuerst, thx.

There is still a difference to your package as I enabled the removal of related tags again, which you didn't do in the "optimised" version.
It might not be a problem as long as flushByTags is used, as its so much faster, but there was a reason for the removal I guess. But you should only mark it obsolete if it behaves same or better in production related to the tag removal behaviour.

There might still be a need for a Neos Content Cache specific Redis backend implementation that does some things a bit differently than the general one.

@bwaidelich
Copy link
Member

This change made it into neos/flow 7.3.8 but it seems to be missing from 7.3.9

@kdambekalns
Copy link
Member

kdambekalns commented Mar 21, 2023

Hm.

The 7.3.8 version was tagged on the 8.2 branch AFAICT.

@kdambekalns
Copy link
Member

And 7.3.7 was tagged in HEAD off of 7.3 and later merged back into 7.3 (which is confusing, but correct.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

9 participants