Problem/Motivation
During performance auditing and testing in jsonapi module's issue #3014232: [regression] ResourceTypeRepository is significantly slower in JSON:API 2, becomes noticeable when handling hundreds of interlinked resources it turned out that the cache service cache.static is much slower than using native PHP private variables. mainly caused by the internal serialize on cache set and unserialize on EVERY cache get. In that particular issue the speed-up was around 60% of total request time. and the actual slow method that this was happening from 6 seconds to 30 mili-seconds. (around 200 times).
It turned out that the internal implementation used for static caches namly the cache.static service is \Drupal\Core\Cache\MemoryBackend and it's MUCH slower than the other available one \Drupal\Core\Cache\MemoryCache\MemoryCache
Proposed resolution
Change the factory and use the faster memory cache implementation internally by default or introduce a new service that could utilize that as easy as the cache.static, but to use the faster implementation.
Remaining tasks
Discussion, patch etc.
User interface changes
None expected - tweaks in cache internals.
API changes
TBD.
Data model changes
None expected (cache tweaks).
Release notes snippet
TBD.
| Comment | File | Size | Author |
|---|---|---|---|
| #19 | 3016690-19.patch | 1.25 KB | geek-merlin |
Comments
Comment #2
ndobromirov commentedComment #3
gabesulliceI think introducing a new service, maybe
cache.memory, is the way to go. The reasoncache.staticis so slow, is because it usesserialize()/unserialize()to store its items. That's supposedly a feature, not a bug, because it means that the cache items can't be altered by reference (hence the name "static").#1596472: Replace hard coded static cache of entities with cache backends was the issue that added the
MemoryCacheclass, but failed to add a proper cache bin service for it. It only added theentity.memory_cacheservice, which is now probably being used in a number of places that it wasn't intended to be used.#2973286: Clean up MemoryCache and MemoryBackend naming issues proposes to rename the classes to make their usage constraints clearer, but I think the more onerous problem is actually that there isn't a general service for
MemoryCacheand thatcore.services.ymldoesn't have a good comment forcache.static. It just says "A special cache bin that does not persist beyond the length of the request." but it really should be more like its class docblock: "Should be used for unit tests and specialist use-cases only, does not store cached items between requests."Comment #6
andypostComment #7
andypostIn context of serializers the most promissing looks #3014514: Make igbinary the default serializer if available, it saves 50% time on unserialize and memory footprint
But serializers/cache mostly blocked on #839444: Make serializer customizable for Cache\DatabaseBackend
Comment #8
ndobromirov commented@andypost I do not see how a serializer is related here as we aim to do no object serialization :).
Comment #9
andypost@ndobromirov for me [de]serialize speed also makes sense when we tons of caches used
Comment #15
borisson_This is a really interesting issue, I'm not sure what the next steps are, but it seems like there should be a patch here first before we can mark it as needs work.
Comment #17
geek-merlinIntereting!
Doing the most bold approach to see what happens. NR only for the bot.
EDIT: Can ignore. Throws as of a wrong clone stmt.
Comment #18
geek-merlinLet's try swap out the static backend for the (badly named) entity.memory.
EDIT: Lots of breaks.
Comment #19
geek-merlinTry replace serialize w/ clone (+remove tag sort), to see what breaks.
Only few test breaks, and NO noticeable change in performance.
Comment #21
geek-merlinSo it looks like:
- The originating issue found that cache.static is 3x slower than static class property caching (which is not tags aware) for some entity structures with many related objects. Fix was to use entity.memory_cache which does NOT serialize.
- Core since 8.6 uses entity.memory_cache, likely for same reasons
- The difference is, that cache.static does serialize and you get out a new built data object that is an exact copy of what you put in. The entity.memory_cache otoh gives you the same object, and if it was changed in the meantime, you get that changed version.
IF more optimizations are needed here, i can only imagine one route:
- Use https://github.com/myclabs/DeepCopy. And maybe we need / profit from a deep-clone-filter that takes __sleep / __serialize (and thus DependencySerializationTrait) into account.
I think i'm done here.
Comment #23
catchThese should never have been added in the way they were IMO, but also probably predate the availability of MemoryCache as distinct from MemoryBackend.
Is there a reason not to use MemoryBackend instead of cache.static in the backend chain? I would think that would work.