As pointed by @Berdir :
There are cases where we might avoid fetching FieldStorage objects just to access the "field type" (which is a pretty critical piece of information...). Specifically, if we could optimize the simple case of "entity load with cache hit", that would be a win. Maybe even the "entity view with render cache hit" case ?
FieldInstanceConfig already saves a denormalized field_type entry in its CMI records, because that information is needed for proper config schema building. We just currently ditch it at runtime, and fetch it from the FieldStorage when needed.
Instead, we could :
- keep field_type as a real property in the FieldinstanceConfig object
- fetch it from the FieldStorage in FieldinstanceConfig::postCreate() in case it's not present in the incoming $values (typically, for programmatic entity_create() calls), instead of re-adding it at save-time in toArray().
I'm not too intimate with the "entity loaded from entity cache" code path in current HEAD, and even less with the "entiy rendered from render cache" path.
Once we do the above, we might find that there are still places in those critical paths where we need to load the FieldStorage to grab other informations than the field type - e.g cardinality, or in order to get FieldItem::propertyDefinitions($field_storage).
In that case, not much we can do I'm afraid...
| Comment | File | Size | Author |
|---|---|---|---|
| #26 | 2313875-FIC-denormalize_field_type-26.patch | 7.67 KB | yched |
| #22 | 2313875.22.patch | 6.05 KB | alexpott |
| #22 | 18-22-interdiff.txt | 907 bytes | alexpott |
| #18 | 2313875.18.patch | 5.95 KB | alexpott |
| #18 | 2313875.18-test-only.patch | 1.01 KB | alexpott |
Comments
Comment #1
yched commentedFWIW, not sure if we should keep the current title or name this "Avoid loading FieldStorage in the cache hit critical paths" ?
Comment #2
yched commentedRan a quick check by placing a breakpoint in FIC::getFieldStorageDefinition(), and checking the occurrences where FIC::getType() is *not* the caller.
On node/[nid] (with entity and render cache hits) :
- EntityViewController::view() (page controller for "full entity" pages) does an explicit $entity->title->view('full'), to extract the entity title as page title, this creates the FieldItem/FieldItemList objects and required the FieldStorage for various stuff
- The "add new comment" form creates a fresh comment entity and generates a form for it, that requires full loading as well.
Not too worried on those two, no big impact, + not much we can do anyway.
On /node (list of node teasers, with entity and render cache hits) :
- comment_node_links_alter() does a $node->get($comment_field)->status, full load
So we might be good ?
Comment #3
yched commentedWell, of course, the above might vary if some formatters have #post_render_cache steps that do additional work with field values, but similarly, not sure we can do much about that case.
Comment #4
yched commentedLet's see with this.
Contrary to what I wrote in the summary, fetching the FieldStorage in FieldinstanceConfig::__construct() is painful, because $this->getFieldStorageDefinition() doesn't work at this point (the $values haven't been assigned to the object properties yet), and we'd have to basically duplicate it.
Instead, getType() lazy loads the field type if not set, and toArray() makes sure it's set and will be present in the config record. Not a huge fan, but it's what I have for now. Suggestions welcome.
Comment #5
yched commentedFrom my comment #2 above:
Fixed in #2314359: Optimization: do not iterate on $entity fields in EntityViewBuilder::viewField()
Comment #6
yched commentedFrom my comment #4 above:
Attached patch calls getType() to make sure $this->field_type is initialized in postCreate() rather than in toArray().
postCreate() already performs a somewhat similar check (call $this->getFieldStorageDefinition() to make sure we refer to a valid FieldStorage), and this removes the need to override toArray().
Comment #8
yched commentedHm. Then FieldInstanceConfigEntityUnitTest::testToArray(), which simply does
fails, since it skips postCreate()...
Not sure what's best here. Adding an explicit call to $instance->postCreate() in there feels lame...
Comment #9
yched commentedOK, gave it some more thought.
- Most/all of the current FIC::__construct() (massaging $values) is what preCreate() is there for. The only thing that stops me from moving it there atm is the fact the phpdoc for FIC::__construct() is where we document the various things you can pass to entity_create('FIC', $values), and moving that doc to preCreate() is not as obvious.
- Following the same reasoning, populating the denormalized 'field_type' really is a postCreate() thing.
- Unit tests that skip
entity_create('FIC', $values)and directly donew FieldinstanceConfig($values)need to adjust their $values accordingly - in this case, pass an explicit 'field_type'.Thus, attached patch does the following :
- 'field_type' is fetched from the field storage in FIC::postCreate(), if needed,
- FIC::getType() no longer lazy-loads, it just returns $this->field_type. The regular entity_create() flow ensures it's present, if you skip that flow, it's your job to make sure it's present in the object you build.
- adjusts the
new FIC()values in FieldInstanceConfigEntityUnitTest accordingly- side fix: passing a $values['field_storage'] in
entity_create('FIC', $values)currently results in the $instance having both ->field_storage and ->fieldStorage properties. The internal property is the latter.Comment #10
yched commentedComment #11
amateescu commentedA thousand times yes! I remember adding a "field helper function" in ctools D7 for getting all instances of a field type, so I think denormalizing this data in the instance config makes very much sense.
Shouldn't we add an entry in the config schema for the new property?
Comment #12
berdirIt's already stored anyway as it's needed for config schema, we just very carefully remove it at runtime ;)
Comment #13
amateescu commentedRight, I could've checked first :)
Comment #14
andypostThere's issue for that #2269025: CommentManager::getAllFields() should have a static cache
Comment #15
alexpottI think I might break this in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields so I'd love to have a test.
Comment #16
alexpottComment #17
yched commentedGoing away from my coding env. for 10ish days tomorrow, not sure I'll have time to work on tests before that.
Also, not sure what to test exactly:
- the basic behavior is that an entity_create('f_i_c') automatically populates $instance->field_type. We can add a dedicated test (cannot be a PhpUnit test, we can't test entity_create with those, can we ?), but tens of tests would fail already if that broke.
- the more general behavior is that you can fetch the field type of a field definition without loading the storage definition, or mere globally that you can load and render an entity from the caches without loading the field storage. Would be cool to track regressions on that, but not sure how this is testable ?
In short : help welcome for the tests.
Comment #18
alexpottHere is a simple test that ensure we don't delegate to the FieldStorage.
Comment #19
yched commentedSure, that doesn't test much though - merely that getType() returns the 'field_type' you pass to the FIC construct.
It also passes in HEAD because this unit test's setUp() prepares a mock FieldStorageConfig, which, in HEAD, is where FIC::getType() reads from.
So, not sure this will really help prevent regressions ?
Comment #20
alexpott@yched you sure it passes in HEAD?
This fails because getType will be called on the mock field storage :)
Comment #21
yched commentedOops, sorry, was too quick, I saw some green, but there are no results yet :-/
So, sure, good enough for me if it's good enough for you :-)
Comment #22
alexpottSlight improvement in the test to truly know that we're not getting the field storage definition from the entity manager.
Comment #24
swentel commentedComment #25
yched commentedActually, I'd rather wait for #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields and re-do the patch with BaseFieldOverrides in mind.
Comment #26
yched commentedUpdated for FieldConfigBase and BaseFieldOverride.
No interdiff because of the reroll - basically, the "populate $this->field_type on postCreate()" behavior is moved to FieldConfigBase.
Comment #27
swentel commentedComment #28
webchickCommitted and pushed to 8.x. Thanks!