Problem/Motivation

We currently multiple load entities when we can, mostly in views listing or reference field formatters etc. However, when you need to individually load entities of the same time from multiple different places, they're loaded individually.

The original version of this issue was planning to 'soft load' entities (e.g. an $entity->isLoaded() method or similar), but never happened.

However, using Fibers and the same trick as in #3496369: Multiple load path aliases without the preload cache, we can multiple-load entities even when completely unrelated code is loading them individually, which was the original concept, but can now be achieved with about ten lines of code and no API changes.

Let's say we have four blocks and each block loads a different node, due to Fibers, we're able to get just to the point where we need to load the node (from persistent cache or the database), add it to a list of entities to load, suspend the Fiber, move onto the next one, and continue like this until we reach the first Fiber again. At that point, we can multiple load all the entities we registered so far, and then they're available in the static cache when we get back the other fibers that were about to load them.

This saves around 25 database queries and around 15 cache sets on the umami front page with a cold cache.

Remaining tasks

User interface changes

None.

API changes

Issue fork drupal-1237636

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Crell’s picture

Subscribing, will try to comment soon. Overall in favor of entities-as-classes.

pwolanin’s picture

I don't think we would have

$entity = new Entity($type, $id); // $id likely optional so that mock entities can be created, potentially new ones.

Instead it would need to be a factory method (function or class method).

$entity = entity_load($type, $id);

or maybe:

$node = DrupalEntity::load($type, $id);

We'd have to think about creative use of __get() (as well as __isset())? Though I'm not sure if there would be a meaningful performance hit by having to copy all the properties from a loaded object into the stub object.

catch’s picture

We'll definitely need a factory. I'm assuming entities a classes but wasn't planning for this to be that issue.

fago’s picture

Instead it would need to be a factory method (function or class method).

Agreed.
I do think we want have per-entity-type classes like Node, Comment, .., which are useful for implementing all the entity-type specifics. See the WIP code at #1184944: Make entities classed objects, introduce CRUD support. This makes lazy-loading generic entity objects impossible, though I don't think this really is an issue.

* When ->load() (either directly called or via magic) asks the EntityLoad controller, it inspects the list of entity IDs that it has, vs. the ones that are already loaded. At this point, it is able to multiple load all of the nodes that weren't loaded already - so they're ready for later when requested. (we could also add a method or property to disable this behaviour, allow the default to be overridden or similar).

I'm not a big fan of this, because it makes it extremely hard to follow/control what happens. Some simple calls accessing a property could magically trigger massive load operations, even if the lazy-load is triggered by a simple read access of a property. That way it becomes harder for developers to know what's going on and to keep control.

I'd like to avoid magic getters or methods for the same reason - all the magic makes it hard for developers to see or track what happens. I've already gone that way with faces and the magic really just made things more complicated :( It doesn't help much if someone can simply call a method or access a property if no-one knows it's there in the first place; or if people read the code and don't actually understand what happens in the back.

- for lists, multiple load works more or less the same as now, except you could just foreach over $nids, instantiate the class and load it - no get $nids, load, then foreach pattern. So it should be a simpler for people who just want to load nodes and do stuff with them. The front-loading would be encapsulated in the class logic rather than a pattern that has to be copy/pasted around.

Actually, I'd prefer the pattern. A load-call really isn't hard, but it makes it obvious to me what happens. So in the end I'd argue the load call is simpler, because I don't need to be aware of any magic that happens in the back.

Still, imho having a single or multiple lazyload could be worthwhile as it could help to avoid some unnecessary entity-loads while leading to a more consistent and simpler API (no more Node vs NodeID function args or returned variables..).

Crell’s picture

I agree we probably do want to have a class-per-entity-type rather than one uber Entity class and that's it. However, that doesn't mean we can't still have a universal factory. The universal factory just needs to be told what type of entity it's dealing with along with the id(s). That's quite easy to do if we allow ourselves to mentally separate loading of entities from "being" an entity entirely; You don't get an entity from new Entity or new Node, ever, at all. You get it from a factory that does not begin with Entity:: or Node::. There's nothing wrong with that.

(As I said in a previous DrupalCon presentation, it is not the job of Pizza to make Pizza. It is the job of Pizza to BE Pizza.)

Also, in a previous project back in Drupal 5, which actually served as much of the inspiration for the Data API Design Sprint and the Fields/Entities system that resulted, I had the concept of a "load set". I was loading data objects from a 3rd party system into my own mini-ORM or mini-Entities system. By default, all objects were loaded with just an ID, say from a search result set. When you tried accessing data from one of them, it would lazy-load data. However, to avoid the Select N+1 problem it would lazy-load from *all* objects in that load set at once.

That is, if you got a list of 20 Artwork ids from a search query, you'd get back just IDs. The code would then create 20 Artwork objects, each one with just its ID, no other data, and a load set ID that linked all 20 of them together. No traffic here to load data from the remote system yet. Then when you first accessed data that would result in a network hit, it did the equivalent of node_load_multiple() for all 20 records and populated all of them at that point. Then all Artwork objects had full data, and the code could safely proceed in ignorance of that fact without triggering an avalanche of network traffic to the legacy system. (We later added caching to it as well.)

Perhaps we could explore a similar setup here. When you create entity objects, you create a set of linked skeleton objects. They then initialize and load en masse with entity_load_multiple() (or something similar to it) on first use. That gives you the double benefit of super-thin objects by default and multi-load to avoid tons of hits to the datastore.

catch’s picture

@Everyone - please ignore the code examples, I am not proposing entity class names or factory names here. If I was, the issue would have a completely different title. We should discuss that on #1184944: Make entities classed objects, introduce CRUD support or open a new one dedicated to that discussion.

This issue is only about allow entity objects that don't contain loaded data for consistency, some kind of lazy load (either magic or explicit via a->load() method) and multiple loading. It will affect the implementations and interfaces of the classes, but not what they're called or much else either.

@Crell that's more or less exactly what I'm proposing in the OP :) with the entity load controller (or somewhere in the implementation around that level) handling the load sets internally (and a 1-1 mapping of entity type to load set).

Having the load controller track the load sets internally gives us the following:
- different entities loaded via different code paths can be multiple loaded at the same time, no need to track sets externally. I had previously been mulling over tracking sets externally somehow but currently don't feel like there's a non-ugly way to do that.
- we can reclaim the D6 pattern of just grabbing a list of ids, foreaching over them and loading them - because internally it will be just as fast as multiple loading them up front.
- we can add an LRU cache to this mechanism very easily (minor caveat about when we want to load more entities than will fit in the cache, but that seems resolvable).
- contrib could replace that LRU cache with a weak references implementation - see https://wiki.php.net/rfc/weakreferences - which are specifically designed for this pattern (amongst others). It looks like this will be a PECL extension rather than in PHP 5.4. The general idea is that you have an index of ids, which allows you to pull an object from memory, but the garbage collector can remove the objects themselves (and the API could retrieve them again if they happened to be needed).

It feels to me like the main points of contention are whether to use magic or an explicit ->load() call.

Advantages of __get():
- we could potentially keep all entity storage actually inside the storage controllers, and just have the entity instances themselves pull values from there - no actual copying of values.
- some entity properties might be fields, and the fields might themselves be class instances, this potentially allows for different kinds of routing to happen.
- Keeping the entity instances extremely shallow probably makes weak references/LRU much easier - the actual content of the class would not differ whether the entity is loaded or not, it always calls back to the storage controller.
- code that has entity classes doesn't need to call ->load() on it (this would need to be done before accessing any property within the scope of a function I think). You can just do what you want with the entity instance and the hard work happens in the background

Disadvantages:
- as fago points out, adding magic to things like this can make code flow hard to follow
- __get() is much slower than directly accessing properties, although it's definitely a good trade-off if we keep multiple load, compared to a ->load() call it would probably be a bit slower but hard to tell.

fago’s picture

I agree we probably do want to have a class-per-entity-type rather than one uber Entity class and that's it. However, that doesn't mean we can't still have a universal factory.

Indeed. I was thinking about lazy-loading the entity-type too, which has totally nothing to do with that discussion. So let's forget that, sry.

Perhaps we could explore a similar setup here. When you create entity objects, you create a set of linked skeleton objects. They then initialize and load en masse with entity_load_multiple() (or something similar to it) on first use. That gives you the double benefit of super-thin objects by default and multi-load to avoid tons of hits to the datastore.

Just to make that clear, I'd be happy having this kind of lazy-loading. I'm just hesitant about having the controller magically loading multiple entities while a single entity has been requested, as well as other __get() magic.

@__get():

We can do a get() method which allows easy access to properties and triggers all kind of lazy-loading the same way. So we have the same advantages, but it's obvious and easy to track for developers what accessing the property could trigger. Also we could allow using $entity->load() and directly accessing plain entity properties as usual afterwards without triggering any magic at all.

Crell’s picture

Personally I think the entity system would be greatly improved if we got rid of arbitrary object properties on entity objects. Have fields, have Entity Properties (by whatever API), and don't allow/support anything else. That's going to be necessary for CMI anyway for deployment, I think. If we need to track Entity Metadata as well (think flag status, nodequeue, possibly OG membership, etc.), that should have first-class support rather than being punted as "well, just throw random crap on the object and hope it doesn't break". That would also help deployment.

That would then give us very clear, explicit places where lazy-loading could be triggered, as well as various other advantages. (I'd like to see title turned into a method, for instance, on all entities.)

Also, $node->lazyLoadMe() and __get() are not mutually exclusive. To wit:

class Entity {
  public function lazyLoadMe() {
    $this->controller->lazyLoad($this); // Controller handles the lazy loading, but needs context.
  }

  public function __get($var) {
    $this->lazyLoadMe();
    return $this->$var;
  }
}

That's probably how we'd want to implement it anyway, so the only question is whether lazyLoadMe() is public or protected. I think in this case it's fine to be public.

catch’s picture

Right we need to seriously think about first class support for things that are neither on the base table nor fields. A big issue with fields is the amount of overhead in terms of metadata to attach stuff (and there are issues around certain things not really making sense to render, yet you can't have a field without a formatter). I think we can do this as a separate task to tackling that though.

I agree we can do both methods, although if we actually copy properties into the Entity class then it could be harder to do LRU and similar was thinking more like:

public function __get($var) {
  // LoadController::get() lazy loads from storage if the entity isn't available via LoadController::load().
  return $this->loadController->get($var, $this);
}

public function load() {
  return $this->loadController->load($this);
}

So the actual Entity class stays shallow the whole time.

Crell’s picture

catch: So you're suggesting that all Field data actually live in the controller object, and the entity object is just a routing shell? I am intrigued but not yet convinced. That seems like it adds a lot of unnecessary indirection in common usage, since then all requests go through __get() rather than just the first. __get() has been going out of vogue in the PHP world from what I've heard, because it makes code harder to follow/debug/auto-complete. (Don't underestimate the importance of the last one.)

catch’s picture

I'm considering it as a possibility, not necessarily suggesting we do that yet.

Another way to do it would be something like this:

class Entity {
  public function __construct($type, $id = NULL) {
    // The load controller is prepared with a reference to the object.
    $this->loadController->setReference($this);
  }
  
  public function load() {
    // When actually loading the object, the load controller clears out its
    // reference. That way when this entity goes out of scope, the memory
    // will be freed.
    $this->loadController->load($this);
  }
}

The main thing is to leave it open so that it's possible to manage memory usage from loading entities in a reasonably intelligent way - either let the PHP garbage collector handle it or allow for an LRU cache to be inserted somewhere. Most page requests shouldn't have to worry about this, but it'd be good for migrations, anything that needs to batch process lots of entities etc.

catch’s picture

Hmm, actually #12 works the other way too. Keep a full reference to the object in the load controller, then if there's memory management needs, set that reference back to a skeleton object instead of the full one. So any way we do this I think we can allow for that particular issue, and it just comes down to which API we want for it all.

So Crell's example would work fine for this, we just need to allow the load controller to maintain an index of entities for the multiple loading (i.e. the __construct() part).

Which is good, 'cos part of why I opened this discussion was to make sure we'd be able to do this down the line, and it looks like that should be doable as long as we keep the fundamental principle of the Entity objects and the controllers being separate classes, which it seems everyone agrees on.

fago’s picture

Personally I think the entity system would be greatly improved if we got rid of arbitrary object properties on entity objects. Have fields, have Entity Properties (by whatever API), and don't allow/support anything else. That's going to be necessary for CMI anyway for deployment, I think. If we need to track Entity Metadata as well (think flag status, nodequeue, possibly OG membership, etc.), that should have first-class support rather than being punted as "well, just throw random crap on the object and hope it doesn't break". That would also help deployment.

Exactly, we need an "entity property API".

>catch: So you're suggesting that all Field data actually live in the controller object, and the entity object is just a routing shell?

As said, I don't like having $node->property involve lots of magic and I very much doubt it's good for performance. Once we lazy-loaded an entity, we should have as simple and fast access to the properties as possible.
Also we really need an object containing all the data, else stuff like entity form workflows are going to become even more complicated than they are now.

ad #12:
Registering entity objects in the controller makes sense to me, though we need to keep out entities that are currently edited, thus changed, but not yet saved.
However, most entity objects are going to be loaded via the controller anyway, except for the newly created (=saved) ones which we should register. Not sure whether we should just leave out un-serialized objects (-> form workflow). Maybe better this could be solved by creating a new revision before doing any changes (assuming that each edit generates a new revision anyway).

That's probably how we'd want to implement it anyway, so the only question is whether lazyLoadMe() is public or protected. I think in this case it's fine to be public.

Yes, at least it should be public. Give developers control. Restricting __get() magic to be triggered only for registered entity properties would certainly help to reduce unwanted side-effects. Still, I'm not convinced implementing __get() is a good idea.

Crell’s picture

Well, lazy loading that you have to explicitly initialize doesn't really buy you much. The developer then needs to know whether an object has been fully loaded or not, which defeats the purpose. So we're going to need some sort of automatic "OK, now I need to be loaded" trigger one way or another, and __get() is as good as any I suppose. I agree that we don't want to hit it for every property, though.

fago’s picture

>Well, lazy loading that you have to explicitly initialize doesn't really buy you much.

Sure. Still, it doesn't have to be magic, but could be triggered by an explicit function call, e.g. $entity->get().

We probably need a getter like this anyway, as we want to assist users getting values of various languages, while using a sensible default.

E.g. I think a getter that consistently works like

$terms = $node->get('field_tags', 'en');
echo $terms[0]->label();

for all properties including fields what be helpful. Whereas in that example both function calls could trigger lazy-loading.

casey’s picture

Using __get() to accomplish lazy field loading doesn't really have to mean a negative performance impact. In fact I think it will mean a positive one: __get() will be called only once, if we store the loaded field data inside the entity object during that __get() call.

There are lots of situations where we only need data of a couple of fields so we have a performance win for every field not loaded. An issue I see however is how to combine this with entity caching.

A required getter function like fago suggests in #16 is definitely going to be a performance hit; A function call is consuming at least double execution time as a property call. Fields are most of the time accessed multiple times during one request.

casey’s picture

A cool benefit of lazy loading would be that you can immediately use entity objects returned by EntityFieldQuery.

catch’s picture

With lazy field loading I'm talking about lazy loading /all/ the fields when one is accessed. The field cache is also per-entity so it's about the same with field and entity-level caching really.

However... I think we need to also consider allowing fields or instances to declare themselves as not loaded until specifically requested - http://drupal.org/project/field_suppress tries to deal with some of that. That's a bit of a different discussion though (and might be better handled with reference fields sometimes in the case of entities that have dozens or hundreds of instances - suggests a wider problem when that's the case).

casey’s picture

Good idea. I was just thinking about the same thing the other way around. Allow fields to be preloaded while all others are lazyloaded, but suppressing fields might be better.

catch’s picture

One thing I forgot to mention, the idea here is to lazy load everything about the entity that we don't already know about - so that includes the data from base tables as well as fields.

So with entitycache it would work the same as now - the only difference is the ->load() only happens when you access a property, not when you instantiate the object in the first place.

I think I agree with casey about __get() - it should be faster than having a method call every time, and this is one place where we could use some indirection - it will allow us to stop people accessing $node->field_name[$langcode][0]['foo'] finally as well as other goodies.

fago’s picture

I think I agree with casey about __get() - it should be faster than having a method call every time, and this is one place where we could use some indirection - it will allow us to stop people accessing $node->field_name[$langcode][0]['foo'] finally as well as other goodies.

So how would this work then, considering translatable properties?

catch’s picture

When accessing a field value, have it internally figure out which language to return the value for. Similar to field_get_values() now. We could separately offer some kind of getter allowing raw field values to be accessed, but I wouldn't want to add the ->get() overhead to every field and property on everything, when 99.9% of cases wouldn't be passing an explicit language.

fago’s picture

Yep, just going with the default language via __get() should do it + offer other languages via get()".

I've thought of calling get() only in case you are not sure whether the entity has been already loaded or not, but I see the beauty of not having to deal with that. As long as __get() is restricted to the list of defined properties it should work out.

yched’s picture

Hum, subscribe.

pwolanin’s picture

Status: Active » Postponed

Consensus at the code sprint was that we should postpone any consideration of this, especially gene the level of new WTFs that might be introduced and the unclear effect on performance.

catch’s picture

Status: Postponed » Active
tim.plunkett’s picture

The current issue summary makes a lot of guesses about how the classed entities will work, but that code is in now. So it needs an overhaul.

berdir’s picture

We also discussed yesterday with fago that this is related to the Entity Property API as well, where we might want to have an EntityProperty class that supports lazy loading it's property iems. Another use case might be taxonomy_get_tree() and similar functions that might return a possibly large number of entities?

alexpott’s picture

Issue tags: -Configuration system

Sorting out tags - this is not a configuration system issue

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

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. Instantiating Entity objects which then have an isLoaded() method or somehow evaluate to FALSE when the entity doesn't exist etc. would be a massive API change. Multiple loading of entities only returns the entities it finds etc.

#3496369: Multiple load path aliases without the preload cache 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. Zero API change or additions - just adding one class property.

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 #3394423: Adopt the Revolt event loop for async task orchestration.

// 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.

catch’s picture

Status: Active » Needs work

Pushed a branch. It's going to need #3437499: Use placeholdering for more blocks 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.

catch’s picture

Issue tags: +Needs tests

#3437499: Use placeholdering for more blocks 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.

andypost’s picture

catch’s picture

Title: Entity lazy multiple front loading » [PP-2] Entity lazy multiple front loading
Priority: Normal » Major
Issue summary: View changes
Status: Needs work » Postponed
Issue tags: -Needs issue summary update

Updated the issue summary.

I combined this locally with the branch on #3496369: Multiple load path aliases without the preload cache and together, they're knocking 40 database queries and 17 cache sets off the front page with a cold cache. It will be more on even more complex pages and there may be more we can do to optimise for this in core.

1) Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryFrontPagePerformanceTest::testFrontPagePerformance
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
 Array &0 [
-    'QueryCount' => 376,
+    'QueryCount' => 336,
     'CacheGetCount' => 471,
-    'CacheSetCount' => 467,
+    'CacheSetCount' => 440,
     'CacheDeleteCount' => 0,
     'CacheTagLookupQueryCount' => 49,
     'CacheTagInvalidationCount' => 0,

catch’s picture

Added a kernel test, which in turn uncovered a bug - pushed a commit for both.

The database query reduction now looks like 263 -> 247 when this issue is combined with the others.

I also found #3518992: Config overrides are loaded for English even when translate_english is false while looking at what queries were left, not really related to here except that it's a lot of config entity loads for no reason. #3519094: content_language_settings often don't exist but are loaded repeatedly is also over a dozen queries we can potentially consolidate.

catch’s picture

Title: [PP-2] Entity lazy multiple front loading » [PP-1] Entity lazy multiple front loading
catch’s picture

Issue tags: -Needs tests

Rebased to get a fresh test failure baseline. Removing needs tests tag because these were added.

godotislate’s picture

A couple comments on the MR that look like bugs, not sure if related to the Umami test failure.

catch’s picture

Applied those suggestions which look good, but there are still bizarre bugs with the MR. It's easy to reproduce with the combined MR, you just have to clear caches and load the umami front page to see broken rendering.

I went down a whole rabbit hole of the fibers logic, but after exhausting that, I'm back to an issue with the entity load logic again, it is potentially something like the wrong entity getting returned sometimes, which causes Umami to render different blocks to the ones requested, or something.

The good news is that #3496369: Multiple load path aliases without the preload cache appears to be working without the same problems, although it needs #3518668: Use Fibers for rendering views rows to be more effective.

godotislate’s picture

It's easy to reproduce with the combined MR, you just have to clear caches and load the umami front page to see broken rendering.

Tested this out locally really quick and I can see that drupalSettings.bigPipePlacheolderIds has 3 unreplaced entries.

{
    "callback=Drupal%5Cblock%5CBlockViewBuilder%3A%3AlazyBuilder&args%5B0%5D=umami_views_block__recipe_collections_block&args%5B1%5D=full&args%5B2%5D&token=LSXAUcXQcqsNxZk01EJ-DceQbM_P3ovAc_wniqiM2X0": true,
    "callback=Drupal%5Cblock%5CBlockViewBuilder%3A%3AlazyBuilder&args%5B0%5D=umami_views_block__promoted_items_block_1&args%5B1%5D=full&args%5B2%5D&token=XKowAdwoi2xYmp7GHvIqQteOfgS9deS_4OJckMkYGHQ": true,
    "callback=Drupal%5Cblock%5CBlockViewBuilder%3A%3AlazyBuilder&args%5B0%5D=umami_banner_home&args%5B1%5D=full&args%5B2%5D&token=j1t-pelbFkxnp6XgWmQ0AG2woBVFHKrU7calg2QLe8M": true
}

catch’s picture

There's something very, very weird going on with the bugs here. I pushed a new branch - at https://git.drupalcode.org/project/drupal/-/merge_requests/12469 which has the absolute minimum changes to cause the bug - no entity preloading, just the fiber suspend in the same place. That I guess shows it's not the preloading causing the problem as such.

catch’s picture

Issue summary: View changes
catch’s picture

Title: [PP-1] Entity lazy multiple front loading » Entity lazy multiple front loading
Status: Postponed » Needs work

Update on #59:

1. The entity cache preloading itself isn't the problem, just suspending a fiber from entity load is enough to trigger some very weird placeholder replacement bugs with Umami.

2. After realising that, I wondered if there was a particular entity type causing the problem, and it turns out there is - it's file entities. If I skip the fiber suspend only when loading file entities, I don't get the bug.

3. This means that fiber suspending during file entity loading is sufficient to trigger the bug. That might be something specific to file entities, e.g. they're being rendered via a reference widget so maybe something in there, or it might be that on the umami front page this happens to be the only entity type that triggers the problematic code path a bit further up the call stack.

Still no idea what the actual bug is, but at least have managed to narrow it down from 'every possible situation that loads an entity' which is where I started and led me into several dead ends.

The blocker is in so this is technically unblocked, although it is definitely still stuck for now.

godotislate’s picture

FWIW, since #3518179: Renderer::executeInRenderContext() needs to handle nested calls and suspended fibers is in, I applied the changes to core/lib/Drupal/Core/Entity/EntityStorageBase.php from MR 12444 to 11.x and still see unreplaced big pipe placeholders on the Umami home page, even with the exclusion of file entities. Of note is that one of the unreplaced placeholders is the recipe collection view block, which does not contain any images.

It could be that I didn't cherry-pick the changes correctly, but I don't think that's the case.

catch’s picture

Think I tracked some of this down.

https://git.drupalcode.org/project/drupal/-/merge_requests/12747

That two-line change looks like it's enough to cause the issue in Umami, something to do with that method not being re-entrant. Ironically this issue potentially makes that entire logic unnecessary. This at least means it's not a bug in the entity preloading itself, even if I don't know what the actual bug is yet.

catch changed the visibility of the branch 1237636-fail to hidden.

catch changed the visibility of the branch 1237636-with-3518179 to hidden.

catch changed the visibility of the branch 1237636-try-again to hidden.

catch’s picture

Root cause of the Umami test failure is #3546376: Use the 'yield' option instead of output buffering for twig rendering to support async rendering. I've incorporated the fix here to see if we can get to green.

catch’s picture

Title: Entity lazy multiple front loading » [PP-1] Entity lazy multiple front loading
Status: Needs work » Postponed

The nightwatch failure is some kind of twig problem in the other issue, but confirmed that issue fixed the remaining thing here, so marking postponed on that.

catch’s picture

Title: [PP-1] Entity lazy multiple front loading » Entity lazy multiple front loading
Status: Postponed » Needs review
amateescu’s picture

Do file and media entities still have to be excluded after the Twig fix?

And should we attempt to remove a similar optimization from \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent::$entityIdsToLoad here or in another issue?

berdir’s picture

Some quick notes from looking at this with xdebug a bit.

After a bit of trial and error, I ended up with a breakpoint after the suspend line with this condition $this->entityIdsToLoad && $ids != $this->entityIdsToLoad. So essentially I want to stop if there are entities to load and there are more than requested in the current fiber. This happens just twice on umami frontpage, once with a few nodes and once with a few files.

What's harder to understand and see is when it doesn't work out. I'm not getting anything on /en/articles for example, but that seems like the perfect example for this. 8 articles, each with a media and a file. is delayed rendering interfering with fibers, so that it happens outside or something like that? I can see that media render happens but there is no active fiber then. Ah, I forgot #3518668: Use Fibers for rendering views rows didn't happen yet. I'll try again with that, but that needs a rebase. not sure which makes sense to do first. but I guess both this and the path alias issue will see a major improvement with that, currently it only works in blocks or so?

because we merge entityIdsToLoad again, they're always duplicated, should we run a array_unique() already before we hit the memory cache instead of just in the else? I'm actually not 100% sure I understand the logic with that. it's also slightly weird to me that we load all the entities into $entities and then rely on the array replace at the end to only return the actually requested ones.

catch’s picture

Do file and media entities still have to be excluded after the Twig fix?

No!!!

godotislate’s picture

it's also slightly weird to me that we load all the entities into $entities and then rely on the array replace at the end to only return the actually requested ones.

Only vaguely remembering, but I think I had a comment on the other MR that array_replace() will add keys/values from the replacement arrays, so it might return more than the requested values? But that is just hypothetical, did not explore if this could actually happen.

See https://www.php.net/manual/en/function.array-replace.php#129944

catch’s picture

@godotislate

Only vaguely remembering, but I think I had a comment on the other MR that array_replace() will add keys/values from the replacement arrays, so it might return more than the requested values? But that is just hypothetical, did not explore if this could actually happen.

So this is OK because ::getFromStaticCache() (and anything else that adds to $entities) only adds items to $entities that are in the passed in IDs, so when we reach the end of the method, we should always have an equal number of IDs and entities, and the array_replace() is only there to order them.

If this needs a comment we could add one, or maybe even an assert after that line with an array_intersect/diff_key() to make sure there are no 'bonus' entities, but I think functionally it's correct.

@berdir

Ah, I forgot #3518668: Use Fibers for rendering views rows didn't happen yet. I'll try again with that, but that needs a rebase. not sure which makes sense to do first. but I guess both this and the path alias issue will see a major improvement with that, currently it only works in blocks or so?

Yes this is right. If you have two blocks with views that both list entities of the same type, that also have media/file references, then if those show an equal amount of entities, it could load them two at a time, but the views issue or something like it would be necessary to load all the referenced stuff in one go. And if the view is rendered in the main page, that doesn't get placeholder treatment, so currently nothing exciting happens for those at all. This is why the performance test changes are less dramatic than they could be.

Note that for entity references we already have the prepare view step in reference formatters that attempts to load referenced entities, so e.g. rendering 10 nodes will load all the taxonomy terms that those nodes reference. It's possible we could replace that with the preloading here (I would love to because that's another Drupal 7-era hack I introduced around the same time as the path alias preload cache) but I haven't looked into how much of a change that would be yet.

I personally think it would be better to do this issue (and the path alias one) before the views row issue for a couple of reasons:

- The more fiber loops we add to core, the more there are to convert in #3394423: Adopt the Revolt event loop for async task orchestration which needs to do them all at once. Whereas fiber suspensions either don't need to be converted in that issue or are at least just one line to convert.

- If we do this issue first, it's easy to see how much improvement we get in the views row issue (and other ones like #3518662: Use a lazy builder/placeholder for the top bar and #3081346: Support auto-placeholdering for blocks placed in Layout Builder). Whereas if we do those issues first, it would be visible over here after time with rebases (and performance test updates) but not as easily.

- even though the improvements in performance tests aren't very impressive, it can't make anything worse, and it might make some things better already.

catch’s picture

Issue summary: View changes
berdir’s picture

Status: Needs review » Needs work

re the array_replace() stuff. I don't think #75 is correct and I think it the current test only works because we only do a single load in each fiber. We should imho extend the test with an loadMultiple() call and make sure we only return requested entities.

If I set a breakpoint on umami with the breakpoint condition in #72 then I see this after I run through to the array_replace():

// The originally requested ids.
$flipped_ids = [3 => 0];
// The enriched $ids based on $this->entityIdsToLoad
$ids = [3, 18, 19]
// And $entities contains 3 items for 3, 18, 19 after the replace.
$entities = [3 => ..., 18 => ..., 19 => ...]

So, loadMultiple() definitely returns 3 entities when only one was requested. This only happens for the fiber that is resumed first, because for the others, $this->entityIdsToLoad has already been reset, so the merge there ends up with just the requested ids. That means to reproduce this, we'd need to have the loadMultiple() call first in the explicit test.

This doesn't fail on umami, because EntityStorageBase::load() explicitly returns the entity for the requested id and \Drupal\Core\Entity\EntityRepository::getCanonical() returns the first items, which is also guaranteed to be the requested one for a single load.

catch’s picture

Oof you're right - we rely on overwriting $ids so the entire premise of the first paragraph in #75 is wrong.

Good news is that's easy to fix and test.

I added a whole new (and repetitive) test method for loadMultiple(), but I think it might be sufficient to only explicitly test ::loadMultiple() and not ::load() here, in which case we could delete the original test method and go back to one.

catch’s picture

Pushed the failing test first to show the test failure: https://git.drupalcode.org/issue/drupal-1237636/-/jobs/6659448

catch’s picture

Status: Needs work » Needs review
catch’s picture

Entity loading unit tests failed, but due to wrong assumptions in the unit tests - it wasn't ensuring the returned entities were keyed correctly from the mocked ::doLoadMultiple().

godotislate’s picture

I think this looks good now, so +1 for RTBC. Thought I think @berdir mentioned he might want to test with #3518668: Use Fibers for rendering views rows, so not sure if that's still outstanding.

berdir’s picture

Status: Needs review » Needs work

Added some comments.

I tested with the views issue, didn't see a difference yet, @catch mentioned that in #75 as well and that makes sense. This is by far the easier change, it works, and we then can enable it to work on more cases and test the impact then.

godotislate’s picture

Forgot to mention that the issue title probably needs updating for clarity.

oily’s picture

Test coverage is present. Here is output of test-only:

PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.12
Configuration: /builds/issue/drupal-1237636/core/phpunit.xml.dist
.FF....                                                             7 / 7 (100%)
Time: 00:29.009, Memory: 8.00 MB
There were 2 failures:
1) Drupal\KernelTests\Core\Entity\EntityApiTest::testLazyPreLoading
Failed asserting that false is true.
/builds/issue/drupal-1237636/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php:169
2) Drupal\KernelTests\Core\Entity\EntityApiTest::testLazyPreLoadingMultiple
Failed asserting that false is true.
/builds/issue/drupal-1237636/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php:215
FAILURES!
Tests: 7, Assertions: 244, Failures: 2.
HTML output directory sites/simpletest/browser_output is not a writable directory.
PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.12
Configuration: /builds/issue/drupal-1237636/core/phpunit.xml.dist
........                                                            8 / 8 (100%)
Time: 00:00.023, Memory: 8.00 MB
OK (8 tests, 24 assertions)
Exiting with EXIT_CODE=1

This looks good.

I added 2 review comments. +1 to @berdir's 2 suggestions. Hoping his suggestions have not been superseded by other ideas?

oily’s picture

Title: Entity lazy multiple front loading » Multiple front load entities from different locations in the code using fibers
berdir’s picture

Writing out debug statements into files helps me understand what's going on, so I did that again here with this diff:

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 05aeb6587bb..bc5c15c464c 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -297,7 +297,11 @@ public function loadMultiple(?array $ids = NULL) {
// of entities to load, so that another call can load everything at
// once.
$this->entityIdsToLoad = array_merge($this->entityIdsToLoad, $ids);
+        file_put_contents('local/load/' . $_SERVER['REQUEST_TIME'] . '.txt', 'SUSPEND: ' . $this->entityTypeId . ':' . implode(',', $ids) . "\n", FILE_APPEND);
$fiber->suspend();
+        if ($this->entityIdsToLoad && $this->entityIdsToLoad != $ids) {
+          file_put_contents('local/load/' . $_SERVER['REQUEST_TIME'] . '.txt', 'BULK LOAD: ' . $this->entityTypeId . ':' . implode(',', $this->entityIdsToLoad) . "\n", FILE_APPEND);
+        }
// Another call to this method may have reset the entityIdsToLoad
// property after loading entities. Combine it with the passed in IDs
// again in case this has happened.
@@ -317,6 +321,9 @@ public function loadMultiple(?array $ids = NULL) {
// entity static cache itself).
$this->entityIdsToLoad = [];
}
+      else {
+        file_put_contents('local/load/' . $_SERVER['REQUEST_TIME'] . '.txt', 'SKIP: ' . $this->entityTypeId . ':' . implode(',', $ids) . "\n", FILE_APPEND);
+      }
}

// Try to gather any remaining entities from a 'preload' method. This method

so, I write out a line into a file in 3 cases. when suspending, with the requested ids. on resume, if we successful in aggregating multiple requests together and skip, when loading something and there's no active fiber.

On umami frontpage with disabled render cache, this results in

SKIP: user_role:authenticated
SUSPEND: node:10,9,8,7
SUSPEND: media:21
SUSPEND: user:6
SUSPEND: user:1
SUSPEND: file:41
SUSPEND: media:9
SUSPEND: file:17
SUSPEND: media:8
SUSPEND: file:15
SUSPEND: media:7
SUSPEND: file:13
SKIP: block_content:3
SKIP: block_content:1
SKIP: block_content:2
SUSPEND: shortcut:1,2
SUSPEND: node:3
SUSPEND: node:18
SUSPEND: taxonomy_term:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
SUSPEND: node:19
BULK LOAD: node:3,18,19
SUSPEND: media:18
SUSPEND: media:17
SUSPEND: media:19
BULK LOAD: media:18,17,19
SUSPEND: file:35
SUSPEND: file:33
SUSPEND: file:37
BULK LOAD: file:35,33,37

What surprised me a bit is that basically everything is in a fiber, that's because of the fiber in \Drupal\Core\Render\Renderer::executeInRenderContext(). the only exception is the 3 block_content entities being loaded as part of block access checking.

First are the nodes from the main page view and then they are being rendered, and this isn't working yet in parallel, so we'll need the views fiber issue or maybe #3548293: Use #lazy_builder / placeholdering for entity rendering for this.

Then it can load 3 nodes in bulk, then 3 medias and then 3 files. two nodes are from link fields and their access check on content blocks, and the third is from one of the views. I was confused why the second views block didn't show up, but that's because those two nodes are already loaded from the main view, those articles show up twice. medias are then a mix of one from the article and from the blocks.

=> Overall, shows that this works nicely. It aggregates all the things from all the blocks until it went through all of them, and then repeats. Even across different entity types as visible by the media case. Neat.

oily’s picture

Status: Needs work » Needs review

@berdir The 2x review comments you made are easy to implement. Not sure if @catch is needed to review them or if you can implement them and move closer to RTBTC? Thanks!

catch’s picture

Title: Multiple front load entities from different locations in the code using fibers » Lazy load multiple entities at a time using fibers

Was leaving the title for as long as possible because I'm so pleased that a wild plan from 2011 is finally coming together thanks to PHP 8.1. While the implementation is different (thankfully, otherwise we'd have needed to rewrite every entity load in all of Drupal) the behaviour (wait until the last possible moment, then load) is pretty much the same. Tried another title update for a bit more clarity.

Will take a look at the new comments tomorrow hopefully.

Thanks for #87, I did a lot of similar file_put_contents() debugging trying to get this to work across this issue and the blockers, but not quite as tidily. file_put_contents() is massively underrated. Cool that it shows it working in the places it already works, but also that there's scope to improve things further. Obviously real pages on real sites will differ massively, especially once we add layout builder / canvas/xb in there too. Another reason to get #3081346: Support auto-placeholdering for blocks placed in Layout Builder and related issues done too.

oily’s picture

Re: #89

I'm so pleased that a wild plan from 2011 is finally coming together

Great work!

I always understood the benefit of fibre (porridge, all-bran);
fibers === drupal porridge?

catch’s picture

Didn't do exactly what @berdir suggested but had another look at the whole logic and was able to simplify a fair bit I think.

berdir’s picture

Status: Needs review » Reviewed & tested by the community

I still think switching to $ids earlier would be easier to understand, but that's a minor issue, this is definitely better now.

The unit test changes are consistent with the other lines in that provider. Very weird, but consistent. Really not fond of those unit tests, but that's a different topic.

Lets do this.

catch’s picture

I still think switching to $ids earlier would be easier to understand, but that's a minor issue, this is definitely better now.

So I did try that, but tests failed. Just tried again and got it working, and found one more array acrobatics that can be simplified too. Leaving RTBC because I'm pretty sure it's what @berdir was going for with the original comment.

berdir’s picture

Yes, exactly. It's a small change but at least for me I think easier to grasp what's happening. The $entities ternary could also be a regular if () now as the else is just reassigning $ids to $ids, but that's up to personal preference.

catch’s picture

Status: Reviewed & tested by the community » Needs review

Found more things to simplify and/or align with similar code elsewhere with the method, which is enough change this could use another review now.

catch’s picture

Changed to a regular if as well, which allows us to only reset the entityIdsToLoad property if there's something in there, which should also be a bit more understandable.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new1.89 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Needs review
berdir’s picture

Ok, I think we're good here (again). Pretty excited for what this, the path alias and some other issues will allow us to do.

Speaking of that, @catch, I'm wondering if we should open an issue to "fiberize" \Drupal\block\BlockRepository::getVisibleBlocksPerRegion() to handle the b lock_content load entities. Maybe only some where access checks are non-trivial, I don't know what the overhead of fibers is. Some sites have a large amount of blocks. On umami, that would allow us to group those 3 block_content entities.

berdir’s picture

Status: Needs review » Reviewed & tested by the community
catch’s picture

I'm wondering if we should open an issue to "fiberize" \Drupal\block\BlockRepository::getVisibleBlocksPerRegion() to handle the b lock_content load entities. Maybe only some where access checks are non-trivial, I don't know what the overhead of fibers is

The PHP overhead is negligible as far as I can tell, there's no new process to fork or anything, so I think it is in the normal range of creating class instances and calling methods etc. It might have an impact if and when we try to use it for directory scanning and YAML parsing or things like that where potentially thousands of fibers could be created, part of the reason I haven't tried it for any of that low level discovery stuff yet.

Having said that, I think we need to balance adding new 'fiber loops' vs. switching to Revolt in #3394423: Adopt the Revolt event loop for async task orchestration and #3425212: [PP-1] Migrate BigPipe and Renderer fiber implementation to use Revolt Event Loop, where instead of multiple fiber loops, we'd have the one event loop that we add tasks to. So there is some overhead (at least mental/maintenance overhead) in scattering different fiber loops around different parts of core. But we should definitely open the issue to track it, even if it might end up postponed on Revolt - I've been holding off pushing the views rows issue for similar reasons. But there are also still some difficult blockers to Revolt which we don't have answers to yet.

berdir’s picture

Created #3549088: Use fibers in BlockRepository::getVisibleBlocksPerRegion() as part of the meta, also grouping this under that meta.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Amazing to see this happen.

Committed a0378ef and pushed to 11.x. Thanks!

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.

  • alexpott committed a0378efd on 11.x
    Issue #1237636 by catch, berdir, godotislate: Lazy load multiple...
catch’s picture

The last minute refactoring here introduced a bug for which we didn't have test coverage.

#3549380: Fiber entity multiple loading tweaks.

#3496369: Multiple load path aliases without the preload cache exposes the bug via existing test coverage due to different fiber suspensions happening.

catch’s picture

Tagging.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.