Repository navigation
[BUGFIX] Keep API resources in memory per cache identifier - #187
Merged
Conversation
ApiResourceRepository::getAll() read and unserialized the t3api cache entry on every call. Serialization calls it for every entity, so a collection of N entities read the same entry N times and kept N copies of the resource graph in memory. The repository now keeps the resources it has read per cache identifier, which contains the API base path with the language prefix.
klamparski
deleted the
186-bug-serializing-a-collection-reads-and-unserializes-the-api-resource-cache-entry-once-per-entity
branch
November 5, 2026 12:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves: #186
ApiResourceRepository::getAll()read and unserialized thet3apicache entry on every call. Serialization calls itfor every entity (
AbstractEntitySubscriber::addIri()→getByEntity()). The effect on one request:productscollection of the functional test fixture read the entry 99 times;The repository is a shared service. It now keeps the resources it has read, per cache identifier. The identifier
contains the API base path with the language prefix from the URL (
/de/_api/...), so requests for anotherlanguage prefix in the same process still get their own resources. Requests that select the language with the
X-Localeheader use the base path of the default language and share one identifier.Measurements
DDEV test instances, PHP 8.4,
GET /_api/news/tags?itemsPerPage=N, 20,000 additional tags,memory_limit1 GB,median of 3 runs, logging of the
sitetest extension disabled:Tests
ApiResourceCacheDispatcherTest(functional): dispatches theproductscollection with related categories andtags through a read-counting
t3apicache backend. It expects one read; before the change there were 99.ApiResourceRepositoryTest(unit),getByEntityReadsCacheOnlyOnceForManyEntities: many lookups read thecache once.
ApiResourceRepositoryTest(unit),getAllReadsCacheOncePerApiBasePath: one read per API base path. A singlememoized value that ignores the identifier fails this test.
Unit (365) and functional (217) suites pass. PHPStan and PHP-CS-Fixer report no errors.
Considerations
the
t3apicache in the same process does not drop it. API resources come from class annotations andconfiguration, which do not change while a process runs.
ApiResourceinstances.Pagination::setParametersFromRequest()stores request parameters in theoperation.
AbstractCollectionResponsesets them before every read, so sharing is safe. Transient cache backendsalready share the instances today.
runtimecache: it has the same lifetime in practice and adds a dependency;entity class → ApiResourceindex ingetByEntity(): it saves about 80 ms for 20,000 lookups over70 resources.