Repository navigation
[6.x] Look term associations up instead of reading through all of them - #15846
Merged
jasonvarga merged 3 commits intoNov 11, 2026
Merged
Conversation
Every taxonomy's associations index holds one row for each term an entry uses. Finding a term's entries, counting them, or finding the terms a collection uses read through every row each time, so a page that lists terms or filters entries by one did that several times over, and the cost grew with the number of entries. The index now groups its rows by slug, by value and by collection the first time one of those is asked for, and looks them up after that. The grouping is made again whenever the rows are no longer the ones it was made from, so saving or deleting an entry is seen straight away. What's stored in the cache is unchanged.
It checks that saves are seen straight away. Deleting an entry doesn't update a taxonomy's associations, which is how it already works.
DanielDarrenJones
marked this pull request as ready for review
November 11, 2026 17:01
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.
Each taxonomy's
associationsindex holds one row for every term an entry uses: 2,000 entries with five terms each is 10,000 rows. Three things read through all of them every time they're asked:TermQueryBuilder::filterUsagesWithinCollections(), for any term query limited to a collection ({{ taxonomy:tags }}on a page that belongs to a collection,Term::query()->where('collection', ...)).TermRepository::entriesCount(), once for each term whose count is shown.whereTaxonomy()andwhereTaxonomyIn()on entry queries.A listing page with a category list, a tag cloud and a filtered collection tag does this a dozen times or more, and each pass costs more as the site grows.
Associationsnow groups its rows by slug, by value and by collection the first time one of those is asked for, and looks them up after that:forSlug($slug)andforSlugs($slugs): the rows for a term, or for several, in the order they're stored.forValue($value): the rows whose stored value matches.slugsInCollections($collections): the terms used by entries in those collections.The three callers above use these. What's stored in the cache is unchanged, so there's nothing to rebuild.
Keeping the grouping right
The grouping is made from the index's items and remembered alongside the array it was made from. Before it's used, that array is compared with the items the index holds now (
===, which PHP answers immediately when it's the same array). If they differ, however that came about (push(),forgetEntry(),load(),update(),clear()), it's grouped again. So saving or deleting an entry is seen by the next lookup.One difference in matching
The old code used
Collection::where()andwhereIn(), which compare loosely, so two numeric-looking slugs such as010and10matched each other. The lookups match the exact string.Benchmark
Each page requested with and without this change, one PHP process per request with a warm Stache, run alternately for 3 rounds of 6 (Apple M1 Pro, PHP 8.5,
6.xat 5b60554). The site is a generated one with two taxonomies on its articles: 12 categories and 80 tags. The bench is at https://github.com/1994limited/statamic-bench.2,000 articles (2,700 entries in all):
Peak memory on the six faster pages is 12 to 20% lower (54 MB to 43 MB on the listing), because the filtered copies of the index are no longer made.
200 articles: the article listing is 5% faster and the category page 4%; everything else is within noise. The saving grows with the number of entries, as the cost did.
The HTML of every page is byte for byte the same before and after, at both sizes.