Skip to content

perf: bound filtered item-list totals (WI-1910) - #422

Merged
stefan-ernst merged 1 commit into
mainfrom
wi-1910-bounded-filtered-counts
Nov 2, 2026
Merged

stefan-ernst merged 1 commit into
mainfrom
wi-1910-bounded-filtered-counts

Conversation

@stefan-ernst

Copy link
Copy Markdown
Contributor

Closes the P1-2 finding in docs/backend-performance-scaling-review.md.

Problem

buildItemListPagePlan only set workspaceCountID for the unfiltered single-workspace list. Every other list — filters, collections, QL, search, multi-workspace — ran COUNT(*) over the full 8-join filter set with no LIMIT, so the aggregate scanned every matching row on each request while the page query beside it stayed index-bounded.

Change

Wrap the filter scan in a LIMIT:

SELECT COUNT(*) FROM (SELECT 1 <from> <where> LIMIT 10000) AS bounded_item_count
  • Below the cap the total stays exact; at the cap it reports the cap.
  • The unfiltered single-workspace path keeps its exact, cached count (cheap and join-free) and is untouched.
  • NewPaginationMeta is unchanged. The UI already refuses to paginate past 10,000 items and labels the total "(limited to 10,000)" (Pagination.svelte maxItems, passed by WorkItemListFooter.svelte), so the bound matches the contract the client already assumes.

Evidence

SQLite, 200,000 matching rows:

Query Total Elapsed
unbounded 200000 87.9 ms
bounded 10000 2.6 ms

The LIMIT is inside the subquery, so the engine stops after the cap.

Tests

Windshiftapp/core-tests-internal#22 — exact below cap, capped above cap, and page selection beyond the cap. The capped-total assertion fails against the pre-fix code.

The filtered, collection, QL and search list paths ran COUNT(*) over the
full filter join set with no LIMIT, so the aggregate scanned every matching
row on each request while the page query beside it stayed index-bounded.

Wrap the filter scan in a LIMIT of 10000. Results below the cap stay exact;
results at the cap report the cap. The UI already refuses to paginate past
10,000 items (Pagination.svelte maxItems), so the bound matches the existing
contract. The unfiltered single-workspace count stays exact and cached.

On SQLite with 200k matching rows the count drops from 87.9ms to 2.6ms.
@stefan-ernst stefan-ernst changed the title perf(repository): bound filtered item-list totals (WI-1910) perf: bound filtered item-list totals (WI-1910) Nov 2, 2026
@stefan-ernst
stefan-ernst merged commit b0296f6 into main Nov 2, 2026
18 of 19 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.

1 participant