Problem/Motivation
From EntityStorageInterface
:
/**
* Resets the internal, static entity cache.
*
* @param $ids
* (optional) If specified, the cache is reset for the entities with the
* given ids only.
*/
public function resetCache(array $ids = NULL);
Implementation in ContentEntityStorageBase
:
/**
* {@inheritdoc}
*/
public function resetCache(array $ids = NULL) {
if ($ids) {
$cids = array();
foreach ($ids as $id) {
unset($this->entities[$id]);
$cids[] = $this->buildCacheId($id);
}
if ($this->entityType->isPersistentlyCacheable()) {
$this->cacheBackend->deleteMultiple($cids);
}
}
else {
$this->entities = array();
if ($this->entityType->isPersistentlyCacheable()) {
Cache::invalidateTags(array($this->entityTypeId . '_values'));
}
}
}
This clears the persistent entity cache every time the static cache is reset. It means there is no way to clear the static cache without clearing the persistent caches for content entities. Other entity storage implementation work correctly, this should follow suit. The current implementation abuses the API.
This also means there are other things that should not happen, like the persistent cache being cleared when you call $storage->loadUnchanged($id)!
Proposed resolution
Remove it! See what breaks. Speaking to catch, resetting the persistent cache should be something that the storage cares about internally upon saving etc...
Remaining tasks
User interface changes
None
API changes
Content entity usage of the EntityStorageInterface will be fixed.
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#62 | 2635440.53-document-static-entity-cache.patch | 1.39 KB | Spokje |
#60 | Screenshot from 2021-01-06 14-58-48.png | 19.68 KB | Abhijith S |
#55 | 2635440.53-document-static-entity-cache.patch | 1.39 KB | deviantintegral |
Comments
Comment #2
damiankloip CreditAttribution: damiankloip commentedFirstly, see what breaks.
Comment #3
damiankloip CreditAttribution: damiankloip commentedComment #5
damiankloip CreditAttribution: damiankloip commentedComment #6
damiankloip CreditAttribution: damiankloip commentedSo funny thing is that the current code relies on the fact that loadUnchanged() will call resetCache() which will clear the persistent cache ready for the new entity values :)
Let's see how this one gets on.
Comment #8
damiankloip CreditAttribution: damiankloip commentedAnd on delete..
Comment #9
damiankloip CreditAttribution: damiankloip commentedComment #11
damiankloip CreditAttribution: damiankloip commentedLooks like most of those fails now relate to comments.
Comment #12
damiankloip CreditAttribution: damiankloip commentedSo likely the cache needs to be cleared after loadUnChanged but before the other hooks are invoked?
Comment #14
damiankloip CreditAttribution: damiankloip commentedMoved code into a clearPersistentCache method instead. Comments work fine normally, this is just a test issue, maybe with CommentTestBase::postComment()
Comment #15
damiankloip CreditAttribution: damiankloip commenteds/an entity/entities
Comment #17
damiankloip CreditAttribution: damiankloip commentedok, that's not a good idea then...
Comment #19
damiankloip CreditAttribution: damiankloip commentedDuh, silly mistake. Based on the patch from #14 again.
Comment #21
BerdirI know the current API is unfortunate, but it's how it always worked, also with entitycache in 7.x. This is IMHO an API change that we can not do in 8.x. All we can do is add a new method that only does the first.
There are valid cases where external code has to invalidate the persistent cache.
Comment #22
dawehnerWhat about adding a second parameter which is just added to the actual implementation instead of the interface? With that practically everything can use it, but it doesn't break APIs.
Comment #23
damiankloip CreditAttribution: damiankloip commentedI don't agree. Content entities are breaking this API totally and not honouring what the method is meant for. Which is clear static cache only. Someone clearly added it in here for convenience and a semi-hack for tests passing (?).
If code wants to clear the persistent cache then shouldn't it use tags like everything else has to? Or we add a new method that does persistent cache clearing. Currently what we have is a method that is documented as only clearing the static cache and doing just that on config entities, but content entities it clears out everything. Leaving no way to clear the static cache during a request. That's quite a big oversight IMO.
Comment #24
damiankloip CreditAttribution: damiankloip commented@dawehner x-posted with you, weird after such a long time! Adding an additional parameter would work for me, ANYTHING that unbreaks this IMO.
Comment #25
BerdirWell, as I said, this is older than cache tags and a direct port from entity_cache in 7.x. It always worked this there. We don't use entity cache tags for the entity storage, we had an issue once but it didn't make sense.
Changing this IMHO is an API break for existing code that calls this function. There *are* cases that expect that it clears the persistent cache, comment related stuff for example, it's not just for tests. Tests actually don't need that, they only need the static cache clear part.
Comment #26
damiankloip CreditAttribution: damiankloip commentedok, technically a break, yes :) This is just horrible though. The docs even say:
So they should say:
So basically anything could happen when you call it :)
When I am iterating lots of entities, I have no choice but to clear the persistent cache when dealing with Content entities, which is terrible. I would ideally have a way to do that. As we can't rely on the API docs, we maybe need an alternative way to fix this?
Comment #27
catchI think that's the kind of expectation it's OK to break in a minor release, given the documentation completely contradicts it and there are documented ways to do it properly.
Comment #30
BerdirWhat documented ways? This is the only way to invalidate the entity storage cache. We don't use cache tags here. On purpose, since it is a 1:1 relationship between tags and cache ids, not dependencies.
Comment #31
catchSaving the entity resets the cache and should be the only time it needs to be reset.
Can also use the cache API directly since the cid is predictable if you're doing something unusual.
Comment #32
damiankloip CreditAttribution: damiankloip commentedI have hit this problem again processing stuff in queues with drush queue-run. Just to use the entities I would have to clear their caches (static and persistent)...
Let's see how something like this works instead.
Comment #33
damiankloip CreditAttribution: damiankloip commentedYes, E.g. the cids would be "values:user:1" etc..
Comment #35
BerdirWe have hook_entity_storage_load() and we tried to use it as much as possible (e.g. comment module). That allows you to attach stuff to entities when they're loaded and have it cached. Obviously, if that data changes, you have to invalidate the cache.
So the storage cache is *not* an internal, transparent thing. It's part of our API. And we need IMHO a way to invalidate it through the API.
Comment #36
damiankloip CreditAttribution: damiankloip commentedok, I understand that. but still, the documentation for resetCache() is clearly for static caching only... and it is very much needed.
So I am loading entities and processing stuff in a queue, which I am running with drush. There is no way for me to reset the static cache without wiping out the persistent cache. So I have to clear the persistent cache for entities that don't need it. Just so I can reduce the memory footprint. The persistent cache is something that should be additional to content entities, like it is now. Yes, my patch is internal etc.. but otherwise it is a total api break. Currently it is not really (IMO).
I stick by what I say, in it's current form, this is pretty much unusable for some cases, where you need to load a lot of entities. I have already hit problems trying to E.g. delete a lot of entities. You cannot load too many, and you can't use my iterator patch (related issue) because it will clear the persistent cache when ever it is used.
Do you have any suggestions then? :)
Comment #37
mikeryanMigration is also a major use case for clearing the static cache: #2558857: Migrations invalidate entity caches when trying to reclaim memory, should flush.
Comment #38
andrewbelcher CreditAttribution: andrewbelcher at FreelyGive commented+1 for a solution to this. My use case is a long running CLI process where the static cache essentially causes a memory leak, but invaliding tags and clearing the persistent cache is both resource intensive and undesirable.
My work around is to use my own storage class extending
SqlContentEntityStorage
adding aresetStaticCache()
method which only deals with the static cache. Perhaps this is something we can consider adding toContentEntityStorageBase
as a means to make this possible? Happy to post a patch if people feel that would be a reasonable solution?Comment #39
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commented#38: I like resetStaticCache() a lot.
Comment #40
andrewbelcher CreditAttribution: andrewbelcher at FreelyGive commentedHow would backward compatibility affect adding this to an interface?
Ideally it would go into
EntityStorageInterface
. I Otherwise I presume we'd need to add a new interface that implementing code can check for before using..?I'm thinking it would be best added to
EntityStorageInterface
and can simply callself::resetCache
, or even makeresetCache
call the new method so the interface documentation can be updated to be more accurate.Comment #41
damiankloip CreditAttribution: damiankloip commentedresetStaticCache will work fine, it just makes things confusing as resetCache is already just meant to do that :( IMO if we are adding a new method, it should be for clearing the persistent cache, not just the static cache but this way round is better for BC.
I don't think this is something that belongs on EntityStorageInterface as that is already documented (I think, correctly) with resetCache. This is only a problem with Content entities.
Comment #42
andrewbelcher CreditAttribution: andrewbelcher at FreelyGive commentedtl;dr: I'm very happy to work on this, but I think the decision of which base class and interface to add this to is beyond my level of understanding of the BC policy.
I'll get a patch up today which simply adds
resetStaticCache()
toContentEntityStorageBase
with no addition to/of interfaces - that can the be expanded/adjusted once the wider decisions are made...It seems to me that there are a few choices we could make:
Methods
1) Deprecating
resetCache()
(for removal in 9) and having 2 new methods, one for static and one for everything/persistent (don't think you ever want to clear persistent without static, but maybe there is a use of that..?). Then we can updateresetCache
's documentation to match what it actually does. Everything in code is then switch to the appropriate new method and contrib can do the same.2) Add a method for static cache and simply update the documentation for
resetCache()
and take the hit that people who were only expecting static caches to be reset will remain unaware unless they read the updated documentation.3) Add a method for persistent cache and fix
resetCache()
with a change record and immediately update core to use the new method. This breaks BC, but only because it's fixing a mis-match between documentation and what it actually does.What level to add it at
Once that's decided, we can decide if we do it for all entities or just content entities. I would lean towards all as non content entities may also have persistent even though core config doesn't.
How to deal with interfaces
1) We could add a new interface for just the new methods which the core base classes can implement and any calls to the methods need to check for this in case contrib has directly implemented the interface.
2) We could add the method to the interface breaking BC if we think that
EntityStorageInterface
is actually an internal and contrib should be extending the base classes (in which we will implement it) rather than the interfaces directly. Similar has been discussed elsewhere, but I don't know if it applies here.3) Add an
EntityStorageInterfaceFixed
(with a better name) which extendsEntityStorageInterface
and becomes the new defacto interface withEntityStorageInterface
kept as a backward compatibility layer to be deprecated/removed in 9.4) Not add it to any interface and expect code to do a method exists/know that the entity they are dealing with implements it.
Comment #43
catchJust a note I'm working on #1596472: Replace hard coded static cache of entities with cache backends which provides a workaround for this (you can clear the cache.static backend directly), but also doesn't conflict with this afaik.
Comment #45
plachComment #46
fagoAfais, resetCache() is used as the way to clear the cache of an entity API, i.e., whatever cache is there. So I'd have said it's the docs that need to be fixed. Alternatively, I could see us adding an $excludePermanentCache = FALSE parameter to it?
Comment #47
catch@fago increasingly I think we should do #1596472: Replace hard coded static cache of entities with cache backends and mark this as duplicate. For now I'm going to postpone it.
Comment #48
andrewbelcher CreditAttribution: andrewbelcher at FreelyGive commented@catch I think that #1596472: Replace hard coded static cache of entities with cache backends is probably the best solution to the original issue of not being able to clear the static, but at the moment there is an outstanding bug, whether it be documentation or API. I'm not sure this should be postponed, rather the plan of cache backends should influence how far we go in solving the current issue.
Comment #50
xjmThe change requested here we will handle as a duplicate of #1596472: Replace hard coded static cache of entities with cache backends. We should rescope this issue to simply correct documentation to explain what is cleared.
Comment #52
JvE CreditAttribution: JvE at One Shoe commentedThis not to be committed to core.
Just a workaround until #1596472: Replace hard coded static cache of entities with cache backends provides a solution.
Comment #55
deviantintegral CreditAttribution: deviantintegral at Lullabot commentedHere's a first take at a docs update that clarifies that
resetCache
is any cache, not just static caches, and forks the docs for content entities to explain how to reset the static cache.Comment #59
daffie CreditAttribution: daffie commentedComment #60
Abhijith S CreditAttribution: Abhijith S as a volunteer and at Zyxware Technologies commentedApplied patch #55 on 9.2.x and it works fine.
Comment #61
joachim CreditAttribution: joachim commentedThe new docs look good. Thanks!
(BTW @Abhijith S thanks for taking the time to check this, but the automated review process already shows that a patch is applying ok. And you don't need to upload a screenshot just to prove it; that wastes space on d.org's servers.)
Comment #62
SpokjeRe-uploading
2635440.53-document-static-entity-cache.patch
so it gets retested every 2 days against9.2.x-dev
.Currently it's tested every 2 days against
8.8.x-dev
.Comment #65
catchCommitted/pushed to 9.2.x and cherry-picked to 9.1.x, thanks!