Problem/Motivation
As part of #2124957: Replace 'content' cache tag with 'rendered' and use it sparingly, we added extensive test coverage for the most important content entity types' usage of cache tags in #2188565: Test coverage for Comment, Custom Block, Node, Taxonomy Term and User entity cache tags.
Shortcut is one of the last three content entity types (the other two are at #2241229: Aggregator Feed & Item content entity types, and AggregatorFeedBlock should use cache tags) that don't yet use cache tags yet when rendering content.
#2217749: Entity base class should provide standardized cache tags and built-in invalidation added standardized cache tags to all entities, so we can use those.
This also prevents #2254865: toolbar_pre_render() runs on every page and is responsible for ~15ms/17000 function calls from yielding a bigger performance win.
Proposed resolution
- Don't assign
Shortcut-specific cache tags, use the associatedShortcutSet's cache tags instead. Shortcuts are never rendered on their own, and there are few shortcuts within a shortcut set, so that'd be fine. - Provide full test coverage for the above, by subclassing
EntityCacheTagsTestBase. - This is the first entity that subclasses
EntityCacheTagsTestBasewhich does not use the typical<entity type>:<entity ID>cache tags, so this requiresEntityCacheTagsTestBaseto be updated to useEntityInterface::getCacheTag(). - This is the first entity that subclasses
EntityCacheTagsTestBasewhich does not have a view builder, which also requires adjustments. - Let
shortcut_renderable_links()assign those shortcuts.
Remaining tasks
None.
User interface changes
None.
API changes
None.
Original report by @Wim Leers
As part of #2124957: Replace 'content' cache tag with 'rendered' and use it sparingly, we added extensive test coverage for the most important content entity types' usage of cache tags in #2188565: Test coverage for Comment, Custom Block, Node, Taxonomy Term and User entity cache tags.
Shortcut is one of the last three content entity types (the other two are at #2241229: Aggregator Feed & Item content entity types, and AggregatorFeedBlock should use cache tags) that don't yet use cache tags yet when rendering content.
#2217749: Entity base class should provide standardized cache tags and built-in invalidation added standardized cache tags to all entities, so we can use those.
Let's use those cache tags for rendered shortcuts and add basic test coverage.
This will help in making the toolbar more cacheable :)
Unfortunately, AFAICT there is no test coverage yet for rendered shortcuts (shortcut_renderable_links()), and definitely not for shortcuts as part of the toolbar. So either that will need to be done in this issue, or this will need to be committed without test coverage (which would still be a step forward).
| Comment | File | Size | Author |
|---|---|---|---|
| #18 | shortcut_cache_tags-2241235-18.patch | 16.93 KB | wim leers |
Comments
Comment #1
wim leersComment #2
wim leersShortcuts are always rendered in a Shortcut Set. Just like Menu Links are always rendered in a Menu.
Therefore, it may make sense to discard Shortcuts' own cache tags entirely and just always make them refer to the Shortcut Set's cache tags.
i.e. something like this in
MenuLink::postSave()could also be applied inShortcut::postSave():Comment #3
wim leersAlso see #2241291-1: Regression: menu link-specific cache tags, for the other half of what needs to happen for #2.
Comment #4
wim leersIn #2254865: toolbar_pre_render() runs on every page and is responsible for ~15ms/17000 function calls, we could render cache shortcuts, but only if this lands, otherwise old shortcuts would be served to the end user in the toolbar. So let's get this done.
I had to update
\Drupal\system\Tests\Entity\EntityCacheTagsTestBase, becauseShortcutis a bit of a special snowflake: it has no view builder, andEntityCacheTagsTestBasewas assuming that every entity it'd test would have that.EntityCacheTagsTestBasewas also assuming that an entity's cache tag would be based on that entity's entity type, butShortcutandMenuLinkare the special snowflakes in that regard — fortunately we now haveEntityInterface::getCacheTag(), so that's easily solved.Comment #6
wim leersSilly mistake.
Comment #8
wim leers#6 failed because of a hilarious bug in current D8 HEAD: if you go to
admin/config/user-interface/shortcut/add-set, reload that page a few times (say 10 times), then you've now created 10 duplicates of each shortcut in the "default" shortcut set. But those shortcuts are not associated with anything. Because when you go to that page, it'll automatically duplicate those shortcuts, even if you don't actually create a new shortcut set!Because of this silly behavior, no cache tags could be generated for those duplicated shortcuts. Simply duplicating those shortcuts after the shortcut set is *actually* created fixes the problem :)
Comment #9
wim leersThis prevents better cacheability of shortcuts, and prevents #2254865: toolbar_pre_render() runs on every page and is responsible for ~15ms/17000 function calls from making a bigger improvement. Increasing priority accordingly.
Comment #10
wim leersOops, #6 and #8 didn't include
ShortcutCacheTagsTest… restoring that file. Plus, #2099131: Use #pre_render pattern for entity render caching broke this. Rerolled.Comment #11
wim leersAn updated issue summary should help with reviewing.
Comment #12
amateescu commentedIsn't a
+=enough here?Why is this change needed?
Entity::invalidateTagsOnSave()also invalidates$this->getListCacheTags(), so if we're returning the correct tags in ourgetListCacheTags()implementation, why is this neccessary?As said above, are we sure we need this?
I think you mean:
$this->shortcut_set->entity->getListCacheTags().Same for
getCacheTag()above if we decide to keep it.I don't think we need ugly inline comments like this if we're not even using any special method from the Shortcut class. And btw, it should hint the interface :)
Comment #13
wim leersThanks for the review! :)
array('entity type' => 'entity ID'). So$a = array('shortcut' => array(1))andarray('shortcut' => array(2));$a + $b === array('shortcut' => array(1))which is wrong,NestedArray::mergeDeep($a, $b) === array('shortcut' => array(1, 2)), which is right.->isNew()is unnecessary there. The other part is necessary though:$this->pathis only set when an entity is created, not when an entity is loaded. Therefore, it's necessary to prevent this from fataling:$shortcut = entity_load('shortcut', 343); $shortcut->save();, which happens as part of\Drupal\system\Tests\Entity\EntityCacheTagsTestBase::testReferencedEntity().The proper solution would be to not have
$this->pathat all, but to always use routes in theShortcutentity. I first considered that out of scope, but since you brought it up, I might as well fix it. Fixed.array('shortcut_sets' => TRUE), but the entity's unique tag (array('shortcut_set' => '<id>'))! The entity's unique tag is invalidated only upon update byEntity::invalidateTagsOnSave(), but we need it to be invalidated upon creation as well: when a shortcut is created, it's always associated with a shortcut set, and since a shortcut uses the associated shortcut set's cache tags, we must invalidate that tag.Shortcuttells Drupal to use the associatedShortcutSet's cache tags instead. I think you're confused? :)Shortcut::getCacheTag()returnsShortcutSet::getCacheTag(), andShortcut::getListCacheTags()returnsShortcutSet::getListCacheTags(). Simple forwarding. I think you thought::getListCacheTags()is a superset of::getCacheTag(), but it's not.Comment #15
berdirAlso stumbled over the postCreate() problem in #2183231-260: Make ContentEntityDatabaseStorage generate static database schemas for content entities, a bit a different fix there without the weird isOriginalId(), see also the comment about @alexpott suggesting that the whole thing should live in the form submit or so.
Comment #16
wim leersClearly, I didn't test #13.2 sufficiently. In trying to fix it, I tried to understand the complex relationship that
Shortcuthas with a "path" field. Its "path" field is a computed field that is actually used for input to compute the actual values to be stored, which are then used to compute the path when editing again… I found a solution for that, but then I ran into problems withMapItem, which I suspect is buggy and may also be used incorrectly.I spent more than three hours trying to get this to work, digging through the many layers of Entity/Field API (with many layers of "setting" and "getting" values) … and just when I was about to give up, I found #2235457: Use link field for shortcut entity, which seems to solve all of this :) A nice 3 hours wasted. I learned my lesson: I'll look for existing issues sooner next time. (In case it turns out to be useful after all: I've attached an "out of scope" interdiff.)
So this reroll:
All this slowdown is all due to out-of-scope issues. Let's focus on the issue at hand: make shortcuts use the cache tags of shortcut sets, and make sure they're invalidated correctly.
Comment #17
wim leersComment #18
wim leersTalked to amateescu in IRC, turns out I completely misunderstood what he meant in #12.4 & #12.5. See the interdiff for what he meant :)
Much cleaner now! :)
Comment #19
amateescu commentedCool, this looks ready to go now.
Comment #20
catchCommitted/pushed to 8.x, thanks!
Comment #22
wim leersGreat! Now #2254865: toolbar_pre_render() runs on every page and is responsible for ~15ms/17000 function calls is fully unblocked :)
Comment #23
moshe weitzman commentedAny chance that this fixed #2216271: Regression: Shortcut links access is not checked?
Comment #24
wim leers#23: no, that's completely independent of what this issue tackled.