find() fetches the document instead of searching for it - #18
Conversation
Every client exception was wrapped as ElasticSearch, so a document that is not there and a cluster that cannot be reached arrived as the same type - while findOneBy() has always raised DocumentNotFound for that very situation, and AbstractBaseService::get() means to as well. Its found() check cannot be reached today: the blanket wrap turns a 404 into ElasticSearch, and its own catch rethrows that first. Four tests asserted the imprecise type. Their intent - that asking for a deleted document fails - is unchanged; they now name which failure it is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LiMwcctmVX9zeodkjmoTnK
…g for it find() built a term query on _id and ran it through findOneBy, so looking a document up by its identifier cost a full search - query parsing, scoring, a fan-out across shards - to return the one document a GET returns directly. It matters because find() is not only called by application code. EntityFactory resolves every single-entity reference through it while hydrating, one per reference, so a page hydrating twenty entities with a handful of references each pays for hundreds of searches that were only ever document lookups. Nothing needs catching for a missing document: Get now raises DocumentNotFound, which is what find() already propagated through findOneBy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LiMwcctmVX9zeodkjmoTnK
8c85804 to
0c1e06b
Compare
|
Closing unmerged. Measured against production data, this saves ~0.20 ms of Elasticsearch time per reference — around 70 ms of an ~880 ms page — because a term search on The eager loading it was meant to help is untouched: If picked up later, the approach worth taking is batching — collect the reference |
EntityManager::find()built a term query on_idand ran it throughfindOneBy, so looking a document up by its identifier cost a full search — query parsing, scoring, a fan-out across shards — to return the one document aGETreturns directly.It matters more than it looks, because
find()is not only called by application code.EntityFactory::resolveProperties()resolves every single-entity reference through it while hydrating, one per reference:A page hydrating twenty entities with a handful of references each pays for hundreds of searches that were only ever document lookups. In the application this was measured in, that page sits at p50 ~880 ms while the query driving it takes 26 ms.
Two commits
1.
GetraisesDocumentNotFoundfor a missing document.Every client exception was wrapped as
ElasticSearch, so a document that is not there and a cluster that cannot be reached arrived as the same type. MeanwhilefindOneBy()has always raisedDocumentNotFoundfor that exact situation — andAbstractBaseService::get()means to as well, but itsfound()check cannot be reached: the blanket wrap turns a 404 intoElasticSearch, and its owncatchrethrows that first.Four tests asserted the imprecise type. Their intent — that asking for a deleted document fails — is unchanged; they now name which failure it is.
Indices\Getis untouched, it is a different class with a different question.2.
find()uses it. Nothing needs catching for the missing case:DocumentNotFoundis whatfind()already propagated throughfindOneBy.Verification
The suite has heavy inter-test pollution — whole files flip between pass and fail across runs, so totals are not comparable. Running each test file in isolation with a clean Elasticsearch between files:
v2.0)Empty diff in both directions. The 13 are pre-existing.
make phpstanclean.What I got wrong first
My first attempt added a
missingIsAnAnswerboolean toGetso it could returnfound() === falseinstead of throwing. That was a wart, and it was pointed out that this library signals missing things with exceptions throughout. It does — and the right fix was not a second style but a more precise exception, which also revealed the unreachablefound()branch above. The flag is gone.Note for the reviewer
make csfails on this branch and onv2.0untouched —core.autocrlf=trueleaves CRLF in the working tree for files nobody edited (EventManager.php,EntitySettingsLocator.php). Committed content here is LF; verified withgit show :file | file -.Not in this PR
The bigger win is fewer round trips rather than cheaper ones — batching reference resolution into an
mgetper class before hydration, sofind()hits the identity map and does no I/O at all. That is a larger change toEntityFactoryand deserves its own review.🤖 Generated with Claude Code
https://claude.ai/code/session_01LiMwcctmVX9zeodkjmoTnK