- 🇬🇧United Kingdom catch
Ten years later...
The main barrier to pursuing this, apart from ::__get() or anything else is that when you call Entity::load()/EntityStorage::load() you either get an entity or FALSE back, you never get an entity object that might or might not exist in the database. That makes the original approach here very hard to reconcile with entity caching - we never want to run a query just to check if we have an entity or not, when we could just get it from cache anyway.
📌 Try to replace path alias preloading with lazy generation Active has a proof of concept for changing how path alias preloading works, it relies on Fibers which didn't exist until PHP 8.1. By doing so it doesn't rely on any changes to entity classes, magic methods etc. and I think the same thing can work here.
Let's say we have three independent entity loading calls in different placeholders (or any code that can run inside a Fiber, which will be most of it after 🌱 Adopt the Revolt event loop for async task orchestration Active .
// Placeholder 1. Node::load(4); // Placeholder 2. Node::load(3); // Placeholder 3. Node::loadMultiple(1, 2, 3);
With the Fibers approach, when each of these calls happen, we can check the static cache, and return as normal in those cases.
When we get a cache miss, we can add the entity ID to a class property that holds 'a list of entities that need to be loaded' (i.e. haven't been returned from static cache), and suspend the Fiber.
So during the page request, node 4 gets added to the class property first, the fiber is suspended, we go to the next placeholder, same thing happens for node 3, go to the third placeholder, 1 and 2 are added (3 is already in there).
Then we run out of placeholders to loop through and end up back at the first one. We now have nodes 1, 2, 3, and 4 all in the 'to be loaded' property, then we can run the rest of multiple loading - first check the persistent cache, then the database. When we reach the later placeholders, they check the static cache again, find that the entity was already loaded, and go ahead.
What would have been three separate database loading operations now gets collapsed into one.
What this won't affect is the following code in a single placeholder:
Node::load(1); Node::load(2);
e.g. when we Fiber::suspend(), we can only get back to Node::load(1) and complete that before going to Node::load(2).
But if this happens in two placeholders:
// Placeholder 1. Node::load(4); Node::load(3); // Placeholder 2. Node::load(2); Node::load(1);
In this case, the approach would mean that node 4 and 2 are multiple loaded together, and then node 3 and 1 are multiple loaded together, so two multiple loads instead of four single loads.
And the worst case is that we Fiber::suspend(), there are no other entities of the same type to load, and we end up back where we were - then it's effectively a no-op and will have the overhead of only a couple of cheap function calls.
- Merge request !10738Initial proof of concept for delayed multiple loading of entities. → (Open) created by catch
- 🇬🇧United Kingdom catch
Pushed a branch. It's going to need 📌 Try to replace path alias preloading with lazy generation Active to be able to show any profiling numbers and for manual testing.
I think kernel test coverage of this should be pretty straightforward. We need a hook_entity_load() test implementation that logs how many times it's called (might already exist), create three entities, then load the three entities inside a fiber each, and see how many times the hook implementation is called.
- 🇬🇧United Kingdom catch
📌 Create placeholders for more things Active isn't enough for manual testing at least with stock Umami. I think for it to work, we'd need to be rendering entities in placeholders, then it would help with entity references - that currently doesn't happen. Will open another issue. Can still add test coverage here to demonstrate the concept.