Repository navigation
Conversation
A page of hits resolves every single-entity reference with its own find(), one ElasticSearch search per distinct referenced document. Hydrating 495 collection items on colectio.com ran 462 such searches, about nine seconds in production. collectReferences() walks a hit the way resolveProperties() does and returns the referenced ids per class, so a caller can fetch them all in one query before hydrating. Lazy collections are left out; they resolve their references only when read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A preloaded page preloads its own references in turn. Remembering which ids an enclosing page is already fetching stops a nested page from fetching them again, and ends reference cycles (Title -> Image -> Title). clear() resets it with the rest of the map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
findBy() hydrated each hit's single-entity references with find(), one search per distinct referenced document. Before hydrating, it now collects the page's referenced ids and fetches each class in one terms _id query, in chunks of 1000. Hydration then finds them in the identity map. On a local snapshot, 500 collection items referencing 363 collectibles went from 363 searches and 0.61-0.76 s to 1 search and 0.12-0.14 s. The hydrated result is identical. Ids already in the identity map, being created or being preloaded further up are skipped. A failed preload is not fatal: whatever it missed is still fetched with find() during hydration, as before. A missing document still drops the hit that references it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Counts real searches on the referenced index through _stats. A page of five titles loads its five images in one search (five before the change). Also covers a shared reference resolving to one instance, a Title <-> Image cycle, references already in the identity map, and a deleted referenced document still dropping its referencing hit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
EntityFactoryresolves every single-entity reference (a property typed as anElasticEntityInterfacewhose document stores only the id, or anSTIElasticEntity) withEntityManager::find(). That is one ElasticSearch search per distinct referenced document, run one after another.On colectio.com, the seller order list hydrates 495
CollectionItemElasticdocuments, each referencing aCollectibleElastic. That came to 462 serial searches. Profiled in production, this onefindBy()took 8.9–9.6 s, and the whole request took 16–49 s.Change
EntityFactory::collectReferences()walks a hit the wayresolveProperties()does and returns the referenced ids per class. Lazy collections (Collection,ElasticCollection) are left out; they resolve their references when first read, andpreloadRelations()already coversElasticCollection.EntityManager::createCollection()collects the references of all hits before hydrating. It fetches each class with oneterms _idquery, in chunks of 1000. Hydration then finds them in the identity map. Because the preload goes throughfindBy(), preloaded pages preload their own references too: a chain of references costs one query per level.IdentityMap::$preloadingList). This ends cycles such as Title → Image → Title.find()during hydration, exactly as before. A missing referenced document still drops the hit that references it.Measured
On a local colectio.com snapshot:
findByof 500 collection items referencing 363 collectibles, comparingv2.0with this branch, two runs each.v2.0The hydrated result is identical (same hash of itemId, collectibleId and name).
Tests
New
EntityManager/PreloadReferencesTest.phptcounts real searches on the referenced index through_stats:v2.0this fails with5 should be 1.Full suite run locally against ES 9.2.2: 684 tests. The 32 failures are exactly the same set as on
v2.0(ClearTest, theMatchAllclass missing from the installed elastic-query, InsertMultiple, Scroll, …). None are new. PHPStan level 6 is clean, and phpcs is clean on the changed files.🤖 Generated with Claude Code