- Issue created by @catch
- Merge request !12835Add property holding array keys for hook_entity_bundle_info() → (Open) created by catch
- 🇬🇧United Kingdom catch
So this reduces the number of entity queries from 1 per bundle to one per entity type. With webform it's not hard to get to hundreds of bundles (that don't actually have any configurable fields on them), so it can reduce the number of database queries by several hundred on a cold cache. On more regular sites with say 10 entity types and 5 bundles each it would still reduce 50 queries to 10.
Also because the first time it loads the field config entities it's for the entity type, not each bundle, it results in less database queries/cache sets / cache gets from the config entity storage too.
Performance tests are showing the improvement (by failing - haven't updated them yet), however seeing a _lot_ of kernel test failures which is likely to be the memory cache entry not being invalidated properly yet.
- 🇬🇧United Kingdom catch
A related issue is EntityFieldManager::getFieldDefinitions() - again in the webform case you can have a large number of bundles with no bundle fields at all, this means hundreds of cache sets of empty arrays on cold caches to chained fast.
We could potentially change this to a cache by entity type? Will open a separate issue to look at that.
- 🇬🇧United Kingdom catch
Adding before/after xhprof screenshots from the 10.4 site I found this on, and also the (hacky, don't use it) backport patch I used to get the numbers.
- 🇬🇧United Kingdom catch
Would it make sense to enable memory cache for the field config entity type?
I kind of thought we already do... Would need to look properly at what's going on in config entity loading.
Found another similar issue in EntityFieldManager: 📌 Optimize EntityFieldManager::buildBundleFielDefinitions() Active .
I kind of thought we already do... Would need to look properly at what's going on in config entity loading.
Right now,
Drupal\Core\Entity\Attribute\ConfigEntityType
has$static_cache
default toFALSE
,
and the only config entity types that havestatic_cache: TRUE
arerole
andlanguage_content_settings
.I'm not sure about the history about that or any complexities associated with enabling the static cache on more config entity types. Maybe @berdir would know?
- 🇬🇧United Kingdom catch
I can see it's coming \Drupal\Core\Entity\EntityFieldManager::getFieldLabels(). But that is only called if there is a field storage for a given entity type. so I assume you actually do have a configurable field there?
That code loops over all bundles just to figure out which bundles have a given field. We already have this information, in the field map. Maybe we can prevent this higher up in the chain?
This isn't how I'm reproducing it but it might be a similar pattern causing the problem.
Uploading an xhprof screenshot that shows the callers (to Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions) , there are 965 bundles on the site (not all webforms, also paragraphs etc. but webforms are about 2/3rds), so each caller with 965 calls to it is looping over every bundle (unless it's an amazing coincidence).
The main culprits are:
Drupal\jsonapi\ResourceType\ResourceTypeRepository::getAllFieldNames() Drupal\jsonapi\ResourceType\ResourceTypeRepository::getFields()
Drupal\jsonapi\ResourceType\ResourceTypeRepository::calculateRelatableResourceTypes()
Drupal\search_api\Plugin\search_api\processor\ReverseEntityReferences::getEntityReferences()It might be that we need to follow a similar approach to your suggestion above in all four places and add some docs discouraging people from looping over all bundles.
Confirmed that none of the webform bundles have a configurable field fwiw.
- 🇨🇭Switzerland berdir Switzerland
Ah, jsonapi, yeah, I didn't test with that. The call chain for me was views data.
Maybe 📌 Change JSON:API routes from every bundle/field to per entity with arguments Active would help then? I'm assuming those calls are coming from route building. If we have those generic routes, then the need to fetch all fields and bundles would just go away... not a quickfix like this obviously.
- 🇬🇧United Kingdom catch
On the front page of Umami after a cache clear, I still get 15 calls to FieldHooks::entityBundleFieldInfo() - didn't check how many total bundles there are, I think this is from views data as you say.
On Umami this is about 330ms for me, which is not tens of seconds bad, but it's still 330ms that will block rendering of a lot of pages on a site (e.g. anything with a view on it).
For me webform + jsonapi is an extreme case and we should definitely do 📌 Change JSON:API routes from every bundle/field to per entity with arguments Active for that case, but we have a lot of code paths calling EntityFieldInfo::getFieldDefinitions() bundle by bundle and should either try to optimize that or convert cases to using the field map.
- 🇬🇧United Kingdom catch
I took a look at converting EntityFieldManager::getFieldLabels() to use the field map and it doesn't help unfortunately.
The field map doesn't contain the field data itself, only which bundles have a field. Because this includes base fields/properties, every bundle is included in the field map, so you end up calling ::getFieldDefinitions() on all bundles anyway when building views data because it wants the field labels for base fields too.
If we knew which fields had overrides or not and which ones were base vs. configured in advance, we could pre-filter, but that information is from ::getFieldDefinitions(), so short of storing that in key/value too, it's just circular.
Opened an issue anyway since it might help in cases where you really only want the field labels for one field 📌 Use the field map in ::getFieldLabels() Active .