Skip to content

[6.x] Speed up Stache entry file filtering on multisite - #15825

Merged
jasonvarga merged 3 commits into
statamic:6.xfrom
ryanmitchell:stache-multisite-entry-filter
Nov 11, 2026
Merged

jasonvarga merged 3 commits into
statamic:6.xfrom
ryanmitchell:stache-multisite-entry-filter

Conversation

@ryanmitchell

Copy link
Copy Markdown
Contributor

When multisite is enabled, CollectionEntriesStore::getItemFilter() called $this->collection()->sites() for every file it filtered. That goes through Collection::findByHandle(), which fetches the collection from the Stache cache store each time. The filter runs whenever a store's files are listed: on a cold build, and on every request while the watcher is enabled.

This PR resolves the store directory and the collection's sites once per traversal and has the filter read those values.

  • A new protected Store::traverseFiles() method wraps the existing Traverser::filter([$this, 'getItemFilter'])->traverse($this) call. Both places that list files now use it.
  • CollectionEntriesStore overrides it to resolve the directory and sites before traversing, and clears them afterwards.
  • getItemFilter() still works on its own. When called directly, outside a traversal, it falls back to the previous per-file lookup.

Benchmark

10,000 entry files in a two-site collection, using the in-memory test cache. With the default file cache, each lookup also reads from disk, so the saving should be larger.

Time per traversal
Before ~362ms
After ~91ms

Single-site installs only save a little, since they never did the collection lookup.

Tests

I added a test that checks a multisite collection only gets files from its own sites, both through getItemFilter() directly and through traverseFiles().

🤖 Generated with Claude Code

ryanmitchell and others added 3 commits November 9, 2026 21:54
With multisite enabled, CollectionEntriesStore::getItemFilter() looked up
the collection (from the cache store) for every file. Resolve the store
directory and the collection's sites once per traversal instead, via a
new protected Store::traverseFiles() method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-filter

# Conflicts:
#	src/Stache/Stores/CollectionEntriesStore.php
#	tests/Stache/Stores/EntriesStoreTest.php
…eparate traversal hook

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@jasonvarga

Copy link
Copy Markdown
Member

Thanks Ryan

I pushed a small tweak that avoids that repeated lookup by reusing findCollection() from #15826, instead of adding a new traverseFiles() hook. paths() and handleFileChanges() already run inside makeItemsInBulk(), so the filter now gets the same memoization the entry-building code uses. That way there's only one mechanism for this in the class:

$collection = $this->collection ?? $this->findCollection($this->childKey);

if (! $collection->sites()->contains($site)) {
    return false;
}

I swapped the test for one that checks the collection is only looked up once while filtering on multisite.

Benchmarks (10,002 files over 3 sites, collection on 2 of them, real file cache):

6.x This change Original PR
Traversal ~546ms ~151ms ~110ms
Full paths() ~1450ms ~1090ms ~1030ms

Cache reads of the collection go from 10,002 down to 1. Your original version was ~40ms faster on raw traversal, but that's within noise once entries are built, so I went with the simpler approach.

@jasonvarga
jasonvarga merged commit b90f379 into statamic:6.x Nov 11, 2026
61 checks passed
@ryanmitchell

Copy link
Copy Markdown
Contributor Author

Awesome. Thanks.

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