Part of the research plan for #4350 (see anchor comment there for the full decomposition).
This track is related but partly orthogonal to ZODB prefetch: blocks resolveuid and linkintegrity resolve UIDs one at a time, so the N+1 here is catalog queries (one searchResults(UID=...) per UID), not just object loads. Batching the catalog query is the first-order fix; ZODB prefetch only matters where full objects are loaded afterwards.
Findings (plone.restapi 10.0.1, scan 2026-07-06)
| # |
Call site |
Pattern |
| D1 |
serializer/blocks.py:80 + serializer/utils.py:26 |
resolve_uid → one catalog query + brain per UID in block data (per field, per block, recursive) |
| D2 |
services/linkintegrity/get.py:32-33 |
uuidToObject(uid) per uid in loop — catalog query + traversal each |
For Volto sites, D1 runs on every content GET with blocks — listing/teaser/grid-heavy pages resolve dozens of UIDs sequentially.
Tasks
Part of the research plan for #4350 (see anchor comment there for the full decomposition).
This track is related but partly orthogonal to ZODB prefetch: blocks
resolveuidand linkintegrity resolve UIDs one at a time, so the N+1 here is catalog queries (onesearchResults(UID=...)per UID), not just object loads. Batching the catalog query is the first-order fix; ZODB prefetch only matters where full objects are loaded afterwards.Findings (plone.restapi 10.0.1, scan 2026-07-06)
serializer/blocks.py:80+serializer/utils.py:26resolve_uid→ one catalog query + brain per UID in block data (per field, per block, recursive)services/linkintegrity/get.py:32-33uuidToObject(uid)per uid in loop — catalog query + traversal eachFor Volto sites, D1 runs on every content GET with blocks — listing/teaser/grid-heavy pages resolve dozens of UIDs sequentially.
Tasks
UID={'query': uids}, build a uid→brain map, then substitute. Cache per request.