Skip to content

perf(db): индекси за подредбата на неполагащите сортове в листовете (пълен scan → index walk)#211

Closed
B353N wants to merge 1 commit into
midt-bg:mainfrom
B353N:perf/list-sort-indexes
Closed

Conversation

@B353N

@B353N B353N commented Jul 4, 2026

Copy link
Copy Markdown

Какво и защо

Листовите страници странират с keyset ORDER BY <sortExpr> <dir>, <id> <dir> LIMIT N. Дефолтните сортове имат съвпадащ индекс и SQLite върви по него, спирайки на LIMIT. Шест избираеми от потребителя сорта нямаха съвпадащ индекс - при тях планировчикът прави SCAN <table> + USE TEMP B-TREE FOR ORDER BY, тоест сканира и сортира цялата таблица преди LIMIT на всяка заявка (включително на всяка keyset страница). D1 таксува сканираните редове, така че това е реален разход и латентност.

Засегнати сортове (проверено с EXPLAIN QUERY PLAN на реален sqlite3, без ANALYZE - точно както е в прод D1; pipeline-ът не пуска ANALYZE):

Страница Сорт без индекс Причина
/contracts date-desc, date-asc idx_contracts_signed е на голата колона signed_at, а заявката подрежда по COALESCE(signed_at, …) - изразът не съвпада
/companies count, authorities няма индекс на contracts / authorities колоните
/authorities count, avg няма индекс на contracts / avg_eur

Дефолтните сортове (value-desc/won/spent) вече имаха индекси и не са засегнати.

Поправка: миграция 0002_list_sort_indexes.sql добавя по един индекс за всеки липсващ сорт, съвпадащ с точния ORDER BY израз плюс keyset id tiebreak в същата посока - SQLite нито сортира, нито буферира. Адитивна и идемпотентна (IF NOT EXISTS); rollup таблиците се опресняват с DELETE+INSERT (никога DROP), затова индексите преживяват всеки ETL ship.

Измерено локално при 200k договора: date-desc първа страница пада от ~0.30s (пълен scan + sort) на под 10ms (index walk).

Доказателство (тест)

packages/db/src/list-sort-indexes.test.ts прилага миграционната верига на реален sqlite3 и за всеки от шестте сорта проверява:

  • ПРЕДИ 0002: планът съдържа USE TEMP B-TREE FOR ORDER BY (доказва дефекта на текущия main);
  • СЛЕД 0002: планът върви по новия индекс и USE TEMP B-TREE FOR ORDER BY изчезва.

Свързан issue

Няма пряк issue; отговаря на стандарта в docs/review-security.md „D1 разход и индекси" (горещите пътища да ползват индекс, проверено с EXPLAIN QUERY PLAN).

Бележка за координация

Този клон номерира миграцията 0002. PR #210 (feat/similar-contracts) също въвежда 0002_cpv_division_stats.sql. Ако #210 се мерджне пръв, тази миграция трябва да се преномерира на 0003 преди merge (двете имена не се сблъскват фатално - wrangler ги прилага по азбучен ред - но конвенцията иска уникален пореден номер).

Вид промяна

  • perf — без промяна в поведението (само добавени индекси; резултатите на заявките са идентични)

Как е тествано

  • pnpm typecheck - минава.
  • pnpm --filter @sigma/db test - минава (195 теста, вкл. 12 нови от proof теста).
  • pnpm lint - чисто.

Чеклист

  • Комитите следват conventional commits и нямат Co-Authored-By: trailer
  • PR-ът е с един логически обхват и е от форк към midt-bg/sigma:main
  • pnpm typecheck минава
  • pnpm test (за засегнатия пакет) минава
  • pnpm lint е чисто
  • Няма комитнати тайни, .env* или .dev.vars
  • Миграцията е адитивна и идемпотентна (IF NOT EXISTS)

🤖 Generated with Claude Code

The list pages keyset-paginate with ORDER BY <sortExpr> <dir>, <id> <dir> LIMIT N.
Six user-selectable sorts had no matching index, so the planner fell back to a
full table SCAN + temp-B-tree ORDER BY on every page (D1 bills rows scanned):

  /contracts   date-desc, date-asc   (idx_contracts_signed is on the bare column,
                                       not the COALESCE(signed_at, ...) expr the query uses)
  /companies   count, authorities
  /authorities count, avg

Add one index per missing sort, matching the exact ORDER BY expression plus the
keyset id tiebreak, so SQLite walks the index and stops at LIMIT. Additive,
idempotent; rollup tables are DELETE+INSERT-refreshed so the indexes survive ships.
A sqlite3 EXPLAIN QUERY PLAN test proves each sort full-scans before and index-walks
after.
@B353N

B353N commented Jul 4, 2026

Copy link
Copy Markdown
Author

Заменен от #212, който консолидира същите индекси заедно с JSON-LD hardening поправката в един бранч.

@B353N B353N closed this Jul 4, 2026
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