Problem/Motivation
#2554235: Make the content entity storage and entity query use the last installed definitions instead of the ones living in code has resulted in a performance regression in 8.7.x and above. And it gets worse with the path alias move to entities.
For example if you have the history module enabled the post to history/NODE_ID/read is now doing 4 additional queries to the key_value table because we're retrieving the entity definitions for user and node. On 8.6.x we didn't retrieve these definitions at all. These same 4 queries are seen on the post to quickedit/metadata when viewing a node whilst logged in. And there are an additional 2 queries caused by the entity definition change during a user login.
Another thing that's seems problematic about #2554235: Make the content entity storage and entity query use the last installed definitions instead of the ones living in code is that we pass the EntityType object from code into \Drupal\Core\Entity\Sql\SqlContentEntityStorage::__construct() and then do...
parent::__construct($entity_type, $entity_field_manager, $cache, $memory_cache, $entity_type_bundle_info);
which uses the passed in entity type to set some defaults. We then later do:
$this->entityType = $this->entityTypeManager->getActiveDefinition($entity_type->id());
$this->fieldStorageDefinitions = $this->entityFieldManager->getActiveFieldStorageDefinitions($entity_type->id());
It's these two calls that a responsible for an additional 2 queries to the key_value table (and this happens per entity type). For me there are two problems here... the additional queries but also the use of the potentially different info in the call to the parent constructor.
Proposed resolution
TBD. I created https://github.com/alexpott/prefetch_key_value to see if we could prefetch things. It makes some improvement but there we still be 1 additional query on all the above mentioned requests. And potentially if you have non-optimised entity definitions then it could be more.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Comment | File | Size | Author |
---|---|---|---|
#10 | 2554235-10.patch | 14.99 KB | alexpott |
#10 | 8-10-interdiff.txt | 1.73 KB | alexpott |
Comments
Comment #2
alexpottComment #4
BerdirRelated improvement that helps to reduce the impact of this: #3162827: Do not instantiate entity storages in constructors of services that do not always need them
Comment #5
BerdirWhat about something like this, which is consistent with how entity type/field manager cache those things. A single cache entry for all entity types and a cache entry for the field storage definitions per entity type, as they can be quite big:
Also using the discovery bin, just like those services.
For some reason that I don't know, we don't inject the service into entity field manager but query the key value storage directly, so I have to fix that.
Also, in case someone does writes directly to key value, it will skip cache invalidation, but there's not much to be done about that.
Lets see how well this works. I did manual testing on a standard install and it saved 10 key value queries for me. And cache lookups should be minimal due to fast chained.
Comment #7
BerdirFixing the test fails. Workspace test needs an updated container, fixed one bug to unset the statically cached entity type definitions when one is deleted and updated the unit test.
Comment #8
BerdirCoding standard fix.
Comment #9
alexpottTo test this patch I installed Standard and disabled render cache.
I've looked at the patch in #8 and it is indeed saving 8 queries to state on viewing a node with an image. See https://blackfire.io/profiles/compare/3b99f947-872b-4cdc-ac9e-afd53146fa... - there is a cost though - memory usage jumps from 12.5 MB to 13 MB.
Similarly on node edit of the article node - we save 14 queries to state - see https://blackfire.io/profiles/compare/f11eee09-c284-4cb4-ae0f-c39bed0acb...() and we use around 500 kB of memory more.
So this is about whether the tradeoff is worth it.
There is a format for these things... we need a CR and then to link to it.
Got to love tests that do an uninstall via the UI but then don't rebuild the container in the test runner.
Comment #10
alexpottWe can claw a tiny amount back by not caching the active definitions on both the entity type manager and entity last installed schema repository. 61 kB - see https://blackfire.io/profiles/compare/f7487f44-f79c-4de8-b4f3-3ec7826cb1...
Comment #11
andypost#10 profile shows +8 calls to
getLastInstalledDefinition
OIwait +713%
0.8 µs → 6.5 µs
Comment #12
BerdirThanks for testing. IMHO queries/s is more likely to be the bottleneck of a site.
For context, this patch + #2575105: Use cache collector for state meant that one of the large NP8 sites went from 8k key_value queries per minute (that's 130/s) to 400 per minute. I don't have data on memory usage, apparently new relic doesn't collect that.
@andypost: anything that says "µs" is well within random variations, you can also see that database queries are 500µs slower but it's the same queries, that's perfectly fine and normal. The first profile is also 1.5% slower, but again, that's mostly just random variations.
Note: the node edit profile link doesn't seem to work.
Comment #14
daffie CreditAttribution: daffie commentedAll code changes look good to me.
The patch adds the use of a backend cache to the
entity.last_installed_schema.repository
service.There is already a lot of testing for this service and to me that would fail if the added backend cache was not implemented correctly.
@alexpott adds performance profiling results that show that the patch is saving in the number queries. It also shows that the memory usage is a little bit higher, from 12.5MB to 13MB. To me almost any performance increase outweighs a memory usage increase.
In comment #12 @Berdir confirms that the patch from this issue in combination with the patch from #2575105: Use cache collector for state makes the number of queries to the key_value table decrease from 8000 per minute to 400 per minute.
I have added a CR for the new construction parameters to the classes EntityFieldManager and EntityLastInstalledSchemaRepository.
This is for me enough to give this issue a RTBC.
Comment #16
catchThis looks great and the numbers look good.
Committed 685419f and pushed to 9.2.x. Thanks!
I'm leaving this fixed on 9.2.x rather than backporting due to the various constructor changes.
Comment #17
Wim LeersPublished https://www.drupal.org/node/3190792.