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
EZP-29659: As a v2 Cluster Admin & Developer I want support for InMemory SPI cache #2553
Conversation
This comment has been minimized.
This comment has been minimized.
6910254
to
0f22413
Compare
This comment has been minimized.
This comment has been minimized.
0f22413
to
bf38150
Compare
On request by @lserwatka added all of you to review ;) |
The mandatory OT comment from orbit: it seems to me that this is based on what our german developer friends used to call an "identity-map" cache, i.e. we are not serializing objects for storing them to memory, but instead just putting a ref to them into an array, which is as fast as anyone can get, which I strongly agree with - at least for value-objects, which hold no refs to other objects/resources. Without having looked at the code, my main question is: what about expiration? Apart from the TTL thing, which seems more or less to be a measure introduced for cli scripts which are supposed to live longer than 1-2 seconds, is the in-memory cache subject to transparent expiration when the script itself does an update of the relevant entity? Ie. if a web page updates, say, a ContentType, will the php code running within the context of that same page transparently see the new version coming from the in-memory cache after the (single?) expiration call done by the kernel? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another silly question: I imagine that you have explored usage of ymfony\Component\Cache\Adapter\ArrayAdapter and Cache\Adapter\PHPArray for the base in-memory caching logic?
Does not have limit last time I checked. Also serializes the object (but configurable like global ttl) to avoid issues if being written to which is not relevant here. It also is global unless we go for different adapters (proxy, or w/o) per handler, and if allowed to be global for cache pool means it will be used for cases where it really shouldn't, like having same ttl and limits enforced for meta data as well as for Content for instance. See closed PR that was first attempt at POC for this: #2453
.... ;)
Same process yes. So within single script execution or single request. If your process update it, you are guaranteed to get fresh objects of it straight after (assuming you re-load it after change, as was the case before). For other processes (in progress) the update will propagate to Symfony cache and Database immediately like before, and the script will get it after the ttl expires, hence why the TTL should never be set very high. It's only a Request /burst cache, where it's down to probability of what can be somewhat safely cached for short bursts. All script / request processes started after the change will get fresh version of it immediately. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Results looks impressive!
eZ/Bundle/EzPublishDebugBundle/Collector/PersistenceCacheCollector.php
Outdated
Show resolved
Hide resolved
6ee7c65
to
2880180
Compare
3be4081
to
987c529
Compare
This comment has been minimized.
This comment has been minimized.
9013258
to
924be63
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
eZ/Bundle/EzPublishDebugBundle/Collector/PersistenceCacheCollector.php
Outdated
Show resolved
Hide resolved
public function setMulti(array $objects, callable $objectIndexes, string $listIndex = null): void | ||
{ | ||
// If objects accounts for more then 20% of our limit, assume it's bulk content load and skip saving in-memory | ||
if ($this->enabled === false || \count($objects) >= $this->limit / 5) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is worth it but $this->limit / 5
could be calculated only once and stored in property e.g. bulkLoadLimit
. As a bonus it will improve code readibility ;-)
|
||
$time = microtime(true); | ||
// if set add objects to cache on list index (typically a "all" key) | ||
if ($listIndex) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about "0"
as $listIndex
value, is it possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not make much sense, as key/indexes needs to be same as used in symfony cache given this is only meant to be used inside abstract in-memory handler.
private function vacuum(): void | ||
{ | ||
// Vacuuming cache in bulk, clearing the 33% oldest cache values | ||
$this->cache = \array_slice($this->cache, (int) ($this->limit / 3)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$this->limit / 3
also could be calculated only once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this and the / 5 comment:
- I haven't seen this being called very often.
- I don't expect the match here to take much ops compared to array_slice.
// simplest/fastests hash possible to identify if we have already collected this before to save on memory use | ||
$callHash = \hash('adler32', $method . \serialize($arguments)); | ||
if (empty($this->calls[$callHash])) { | ||
$this->calls[$callHash] = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an improvement would be great have VO instead of arrays with fixed keys (also in terms of memory usage)
final class PersistenceLoggerCall
{
/** @var string */
private $method;
/** @var array */
private $arguments;
/** @var \eZ\Publish\Core\Persistence\Cache\PersistenceLoggerStats */
private $stats;
/** @var \eZ\Publish\Core\Persistence\Cache\PersistenceLoggerCallTrace[] */
private $traces;
// ...
}
final class PersistenceLoggerStats
{
/** @var int */
private $uncached;
/** @var int */
private $miss;
/** @var int */
private $hit;
/** @var int */
private $memory;
// ...
}
final class PersistenceLoggerCallTrace
{
/** @var string[] */
private $trace;
/** @var int */
private $count;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a followup if you want to work on it before final ;)
eZ/Publish/Core/Persistence/Legacy/Content/Language/CachingHandler.php
Outdated
Show resolved
Hide resolved
Co-Authored-By: andrerom <andre.romcke@gmail.com>
802ee31
to
b299750
Compare
b299750
to
494c257
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coming late to the party, +1. Thanks for addressing my callable type-hints remarks :)
Found one typo:
Co-Authored-By: andrerom <andre.romcke@gmail.com>
QA passes will as agreed with Lukasz be done on rc2, thanks for all the reviews and let’s make 2.5 the fastest release yet 😉 |
2.5
masterIntroduces time limited (3sec) and object limited (100 items) In-Memory caching to meta data elements; for now Language, ContentType (inc groups) & UserHandler (user meta data, roles, role assignments).
This can be built upon further later (see inline example), but this should already help solve the performance issues we have with Cluster setups.
For comparison here is
admin/dashboard
on 2.4ezplatform-ee-demo
:* Numbers estimates a latency of 2-5ms per call to Redis (in cluster setup Redis is often on another server as per nature of a cluster). If RedisCluster is used then it will need Symfony 3.4.23, otherwise base setup will have 17k lookups to Redis nodes and about 34-85 sec response time.
Done:
Improvements to SPI Persistence/Cache logging, here on admin/dashboard:
TODO:
$ composer fix-cs
).Review notes:
Followup:
Also requested by Community to expose a command to clear this also (with option to specify which once)