Skip to content

Stop re-fetching the collection on every operation - #35

Merged
ryanmitchell merged 1 commit into
statamic-rad-pack:mainfrom
edalzell:fix/memoize-get-or-create-index
Aug 5, 2026
Merged

Stop re-fetching the collection on every operation#35
ryanmitchell merged 1 commit into
statamic-rad-pack:mainfrom
edalzell:fix/memoize-get-or-create-index

Conversation

@edalzell

@edalzell edalzell commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The problem

Index::getOrCreateIndex() calls $collection->retrieve() every time it runs — an HTTP request to Typesense. It's called by insertDocuments(), 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 same Collections instance for the life of the client
  • Collections::__get() caches a Collection per name
  • Collection::exists() performs the retrieve() once and remembers the answer

The driver called retrieve() directly and then discarded the setExists(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 — so deleteIndex() now has to clear it explicitly:

$collection->delete();
$collection->setExists(false);

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 mock ApiCall so they assert on the number of HTTP requests actually made, and need no running Typesense server:

  • the collection is looked up only once across repeated calls
  • it's recreated after being deleted (guards the setExists(false) above)
  • it's created when missing, and not re-checked afterwards

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 the setExists(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. pint passes.

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>
@ryanmitchell
ryanmitchell merged commit 51e5584 into statamic-rad-pack:main Aug 5, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants