Stop re-fetching the collection on every operation - #35
Merged
ryanmitchell merged 1 commit intoAug 5, 2026
Merged
Conversation
getOrCreateIndex() called $collection->retrieve() every time, which is an HTTP request to Typesense. It runs on every insert, delete, count and search, so a reindex spends one extra round trip per chunk and every front end search pays one before it can even start searching. The client already solves this. getCollections() returns the same Collections instance for the life of the client, that caches a Collection per name, and Collection tracks whether it exists - Collection::exists() does the retrieve once and remembers the answer. The driver was calling retrieve() directly and throwing away the setExists(true) it then recorded. Ask the collection whether it exists instead. Against a Typesense Cloud node that takes a search from ~250ms to ~205ms, with the repeat lookups going from ~44ms to nothing. Deleting has to clear the flag, because Collection::delete() leaves it set and update() deletes then recreates. Without that, update() would skip recreating the collection and every import afterwards would have nowhere to go. Co-Authored-By: Claude Opus 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.
The problem
Index::getOrCreateIndex()calls$collection->retrieve()every time it runs — an HTTP request to Typesense. It's called byinsertDocuments(),delete(),exists(),getCount(),deleteIndex()and, most importantly,searchUsingApi().So every front-end search makes two round trips instead of one: a collection lookup, and then the actual search. On a site indexing ~2,600 entries against a Typesense Cloud node I measured that lookup at ~44ms, which was 16–36% of total query latency.
The fix
The client already does this caching — the driver was just bypassing it:
Client::getCollections()returns the sameCollectionsinstance for the life of the clientCollections::__get()caches aCollectionper nameCollection::exists()performs theretrieve()once and remembers the answerThe driver called
retrieve()directly and then discarded thesetExists(true)it recorded. Asking$collection->exists()instead gets the memoisation for free, with no new state in the driver.Measured against Typesense Cloud: repeat lookups go from ~44ms to ~0ms, and a search drops from ~250ms to ~205ms.
One thing worth reviewing
Collection::delete()doesn't clear the exists flag, and the instance is cached — sodeleteIndex()now has to clear it explicitly:Without that,
update()(which deletes then recreates) would skip the recreate and every subsequent import would have nowhere to go. There's a test covering exactly this.Tests
Three new tests in
tests/Unit/GetOrCreateIndexTest.php. They mockApiCallso they assert on the number of HTTP requests actually made, and need no running Typesense server:setExists(false)above)Each fails without the corresponding half of the fix. Verified: removing the memoisation makes the first fail with
should be called exactly 1 times but called 3 times; removing thesetExists(false)makes the second fail because the recreate never happens.The 6 pre-existing errors in the suite are the integration tests that need a live Typesense server, and are unchanged by this PR.
pintpasses.