Repository navigation
perf(findings): index UPPER(title) for case-insensitive title filters - #16387
Merged
Merged
Conversation
Every case-insensitive title filter compiles to UPPER(title::text), which neither the plain title index nor the trigram index can serve, so each one read every title. On 2,000,000 findings: iexact 351 ms -> 0.4 ms, an IN list of 50 titles 333 ms -> 1.2 ms, istartswith 347 ms -> 1.4 ms. Built concurrently, then ANALYZE so the planner has statistics for the expression. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
Adversarial review: perf(findings): index UPPER(title) for case-insensitive title filtersVerdict: approve-with-nits Attempts to break it
BlockingNone. CI: 48 of 48 checks pass, including the Migration Graph Check and both Rest Framework suites. Nice to have
Verified hands-onItems 1 to 4 and 7, on a 2M-finding Postgres 16 database with this branch checked out. Checked only staticallyThe insert-cost claim (100k inserts, no measurable change). I did not re-run it. |
blakeaowens
approved these changes
Oct 16, 2026
devGregA
approved these changes
Oct 16, 2026
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.
Description
Every case-insensitive filter on a finding's title compiles to
UPPER("dojo_finding"."title"::text):iexact,istartswith,icontains, and anINlist of upper-cased values. The two existing title indexes are on the raw column (btree(title)and thegin_trgm_opsindex that global search uses for fuzzy matching), and Postgres can only use an index whose expression matches the query's. So every one of those filters reads all the titles.This adds one btree index on
UPPER(title)withtext_pattern_ops, declared onFinding.Meta.indexesand built by migration0300withAddIndexConcurrently(non-atomic, no lock ondojo_finding, the same pattern as0299).text_pattern_opslets the same index serve theLIKE 'PREFIX%'thatistartswithrenders, as well as equality. The migration then runsANALYZE dojo_finding. Without statistics for the new expression, the planner overestimated anINlist by orders of magnitude and kept walking another index, and autovacuum may not re-analyze a large, settled findings table for a long time.Measured on 2,000,000 findings (Postgres 16,
EXPLAIN ANALYZEand the real count and page queries):title__iexactINlist of 50 upper-cased titles, counttitle__istartswithtitle__icontainsCost:
icontainsandiendswithwould need a trigram index onUPPER(title)instead. That one is left out on purpose: in the same test it took a rare substring from 354 ms to 46 ms, but made a common one (24,000 matches) slower, 321 ms to 460 ms.Testing
makemigrations dojo --check --dry-run: no changes detected.migrate dojo 0300, back to0299(drops the index), and forward again on the 2,000,000-finding database. After the round trip, with no manualANALYZE, theINlist count and page queries both useidx_finding_upper_title(1.2 ms each).upper((title)::text), the same form the ORM's queries use.ruff checkover the repo with the pinnedruff==0.16.10.🤖 Generated with Claude Code