perf: first_rows_ms свода 2 через raf-наблюдатель, прежний waitRows сохраняет RowsError и быстрый отказ (I-6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-29 00:48:11 +04:00
parent 9272ea2527
commit 0f7c6c5179
2 changed files with 15 additions and 12 deletions

View File

@ -279,7 +279,7 @@ cd ..
2. Скопировать `results/manifest-s1.json` в оба worktree («до» из AURORA-1842, «после» из ветки PR 3). 2. Скопировать `results/manifest-s1.json` в оба worktree («до» из AURORA-1842, «после» из ветки PR 3).
Код сценариев один (из ветки PR 3): в временном worktree «до» (вершина 1842) выполнить Код сценариев один (из ветки PR 3): в временном worktree «до» (вершина 1842) выполнить
`git checkout feat/236-aurora-1843-svod2-server-paging-perf -- perf/` без коммита; иначе «до» снимется старым `scroll.spec.ts` без новых метрик. `git checkout feat/236-aurora-1843-svod2-server-paging-perf -- perf/` без коммита; иначе «до» снимется старым `scroll.spec.ts` без новых метрик.
3. «До»: `stand.sh reset/gen/verify/api/web-build/web/down` для «до» запускать из worktree «до»: БД остаётся на старой миграции 3. «До»: `stand.sh reset/api/web-build/web/down` и `perfkit.gen.run`/`perfkit.gen.verify` (шаг 1) для «до» запускать из worktree «до»: БД остаётся на старой миграции
(иначе 0053 уронит 6-аргументные `v_svod2_go/rf`, и API «до» пойдёт в новые функции). Поднять `api`, `web-build`, `web`, снять замеры (команды ниже). (иначе 0053 уронит 6-аргументные `v_svod2_go/rf`, и API «до» пойдёт в новые функции). Поднять `api`, `web-build`, `web`, снять замеры (команды ниже).
4. Остановить сервисы. 4. Остановить сервисы.
5. «После»: `stand.sh migrate` и все сервисы «после» запускать из worktree «после»: `alembic upgrade head`, затем `api`, `web-build`, `web`; перед замером выполнить сверку (ниже); снять замеры. 5. «После»: `stand.sh migrate` и все сервисы «после» запускать из worktree «после»: `alembic upgrade head`, затем `api`, `web-build`, `web`; перед замером выполнить сверку (ниже); снять замеры.
@ -304,7 +304,7 @@ node analysis/svod2_search_oracle.mjs --check --api http://localhost:8000 \
- `json_bytes_sheet` - прежний смысл: максимум одного ответа; `json_bytes_total` - сумма всех ответов; `json_bytes_first` - сумма до первых строк; - `json_bytes_sheet` - прежний смысл: максимум одного ответа; `json_bytes_total` - сумма всех ответов; `json_bytes_first` - сумма до первых строк;
- `pages_requested` - число запросов страниц (`offset=`); на «до» всегда 0 (экран грузит всё одним запросом) - как число страниц с «после» не сравнивать; - `pages_requested` - число запросов страниц (`offset=`); на «до» всегда 0 (экран грузит всё одним запросом) - как число страниц с «после» не сравнивать;
- `placeholder_frame_share` - доля кадров прокрутки, где видна строка-заглушка; - `placeholder_frame_share` - доля кадров прокрутки, где видна строка-заглушка;
- `filter_result_ms` - от ввода в поиск до стабилизации (и ответа сервера, если он был); пишется только в записи фильтра, там же нет `json_bytes_total/first` и `pages_requested` (они включили бы исходную загрузку). - `filter_result_ms` - от ввода в поиск до стабилизации (и ответа сервера, если он был); пишется только в записи фильтра, там же нет `json_bytes_total/first`, `pages_requested` и `placeholder_frame_share` (они включили бы исходную загрузку).
Сценарий `svod2-memory`: записи `scenario=svod2-memory`, `target=svod2/<point>` (`go`, `rf`, `rf_scrolled` при `PERF_SVOD2_MEM_SCROLL=1`, `final`), Сценарий `svod2-memory`: записи `scenario=svod2-memory`, `target=svod2/<point>` (`go`, `rf`, `rf_scrolled` при `PERF_SVOD2_MEM_SCROLL=1`, `final`),
метрики `heap_used_mb`, `nodes`, `documents`, `js_event_listeners`, `rows_visible`. метрики `heap_used_mb`, `nodes`, `documents`, `js_event_listeners`, `rows_visible`.

View File

@ -198,24 +198,27 @@ async function measure(browser: import('@playwright/test').Browser, o: RunOpts)
await page.getByLabel('ССП/РФ').click(); await page.getByLabel('ССП/РФ').click();
await page.getByRole('option', { name: 'Все ССП/РФ' }).click(); await page.getByRole('option', { name: 'Все ССП/РФ' }).click();
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
// first_rows_ms: raf-наблюдатель в странице (момент, когда строк данных >= MIN_ROWS); ожидание и отказ - прежний waitRows
await page.evaluate(([sel, rs, min]) => {
const st = { at: 0 };
(window as any).__firstRows = st;
const tick = () => {
if (document.querySelectorAll(`${sel} ${rs}`).length >= (min as number)) { st.at = Date.now(); return; }
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}, [CONTAINER, rowSel, MIN_ROWS]);
tLoad = Date.now(); tLoad = Date.now();
await page.getByRole('button', { name: 'Загрузить' }).click(); await page.getByRole('button', { name: 'Загрузить' }).click();
} }
let rows: number; let rows: number;
let firstRowsMs = 0; let firstRowsMs = 0;
try { try {
rows = await waitRows(page, rowSel);
if (t.kind === 'svod2') { if (t.kind === 'svod2') {
// опрос каждый кадр (raf), а не раз в 500 мс: иначе first_rows_ms квантуется шагом опроса const at = await page.evaluate(() => (window as any).__firstRows?.at as number | undefined);
await page.waitForFunction( firstRowsMs = (at || Date.now()) - tLoad;
([sel, rs, min]) => document.querySelectorAll(`${sel} ${rs}`).length >= (min as number),
[CONTAINER, rowSel, MIN_ROWS] as const,
{ polling: 'raf', timeout: LOAD_TIMEOUT },
);
firstRowsMs = Date.now() - tLoad;
bytes.markFirstRows(); bytes.markFirstRows();
rows = await waitRows(page, rowSel);
} else {
rows = await waitRows(page, rowSel);
} }
} catch (e) { } catch (e) {
if (!(e instanceof RowsError)) throw e; if (!(e instanceof RowsError)) throw e;