Problem/Motivation
ContextualController::render() renders the requested IDs one after another. Each ID's links run route access checks that upcast entity parameters, so the entities load one by one, although EntityStorageBase::loadMultiple() can batch loads across Fibers since #1237636: Lazy load multiple entities at a time using fibers.
Those access checks are most of the request. Across 12 contextual/render requests on Standard and Umami pages, as editor and admin, they took 58% of request time with a warm entity cache and 72% with a cold one.
The gain is limited to users with "access contextual links". contextual.js also caches rendered links per ID in sessionStorage, so it mostly applies to the first view of a page in a session.
Proposed resolution
Render each ID in its own Fiber and resume them in a loop, as Renderer::replacePlaceholders() does. Use renderInIsolation(): renderRoot() throws a LogicException when a second call starts while the first is suspended. Duplicate IDs still render once per occurrence.
A prototype, swapped in for the controller in a functional test with access_manager decorated to count checks and queries, returned identical JSON for the same 672 access checks:
- Warm entity cache: 419 ms to 283 ms, 1,103 to 582 queries.
- Cold entity cache: 510 ms to 467 ms, 1,473 to 949 queries.
Times are single requests with query counting on; the query counts are the reliable part.
Most of the remaining cost is Edit links on moderated content. Content Moderation sets load_latest_revision on entity form routes, and revision loads don't suspend, so they stay unbatched.
Remaining tasks
- Decide whether Fiber-aware revision loading belongs here or in a follow-up.
Comments