AURORA-1838: updateCells без клонирования всего дерева #223

Open
aep wants to merge 2 commits from feat/212-aurora-1838-ws-immutable-update into test
Owner

Closes #212

Jira: AURORA-1838

Что сделано

  • updateCells (web/src/components/RealtimeTable/utils/cellUtils.js) больше не делает structuredClone и не мутирует вход: копируются только массивы и строки на пути к затронутой строке, незатронутые строки и поддеревья сохраняют идентичность (===). Если ничего не изменилось, возвращается тот же объект data.
  • Слияние: копия затронутой строки + рекурсивное неизменяющее слияние с прежней семантикой deepMerge.
  • Единственное сознательное изменение семантики: ключи, которых нет в row.data, теперь вставляются (раньше пропускались). Нужно для AURORA-1844 (полные строки в ответе) и AURORA-1848 (GET без null). Отмечено в тесте.
  • Несколько строк в одном сообщении: уже скопированные узлы (WeakSet) повторно не копируются.
  • Убран console.log каждого WebSocket-сообщения (useWebSocket.js).
  • В cellUtils.js импорт ./rowUtils -> ./rowUtils.js (иначе файл не импортируется из node --test).
  • Не тронуто: structuredClone перед addRootRow в useRealtimeData.js, insertCell/insertProjectRow/deleteRow, RealtimeContext.js.

Тесты

  • Новый cellUtils.test.js (14 тестов): идентичность, новые объекты на пути, вход не мутирован (рекурсивный Object.freeze), паритет с копией старой реализации (вложенные объекты, массивы, null, ключ вне data, пакет, не найдена), ожидаемое отличие (вставка ключей), пакет, строка не найдена -> тот же объект.
  • node --test по всем *.test.js фронта: зелёный; npm run build проходит. Бэк не менялся.

Микробенчмарк (node, синтетический лист 1134 строки, сообщение из 2 правок, медиана из 300)

  • старая: 3.9-4.7 мс (p95 6-10 мс)
  • новая: 0.009-0.012 мс (p95 0.01-0.03 мс)
    Критерий «<= 5 мс» трактуется как onmessage + updateCells; цена рендера React (25-30 мс) — справочная, её снижают 1837/1839.

Проверить руками

  • Два браузера: правка соавтора видна, итоги групп пересчитываются
  • Своя правка и её эхо
  • Добавление и удаление строки, добавление программы и проекта (FORM_4/STRUCTURE)
  • В консоли нет лога на каждое сообщение WebSocket

Замер до/после

Будет добавлен после ночного прогона на стенде perf/: scroll-coauthor.spec.ts с PERF_TARGETS='FORM_2/AHR/*' PERF_COAUTHOR_RATES=5 PERF_REPEATS=2, затем analysis/timeline_breakdown.py -> «Точки в��ода», useWebSocket.js:30 (было ~3,4 мс на сообщение).

Rulings I made

  • R-1: если у ключа нет значения в row.data, но row[key] истинно, сохраняется прежняя ветка (запись в поле строки); вставка в row.data только если и row[key] ложно. Цена ошибки: ключ вроде depth/sort_order не уйдёт в data (как и раньше).
  • R-2: массив верхнего уровня ключа сливается по индексам, как в старом deepMerge (паритет, покрыт тестом); массивы глубже заменяются.
  • R-3: узел строки в пакете может скопироваться повторно при обновлении той же строки (только строки-цели), корректность не страдает.

🤖 Generated with Claude Code

Closes #212 Jira: AURORA-1838 ## Что сделано - `updateCells` (web/src/components/RealtimeTable/utils/cellUtils.js) больше не делает `structuredClone` и не мутирует вход: копируются только массивы и строки на пути к затронутой строке, незатронутые строки и поддеревья сохраняют идентичность (`===`). Если ничего не изменилось, возвращается тот же объект `data`. - Слияние: копия затронутой строки + рекурсивное неизменяющее слияние с прежней семантикой `deepMerge`. - **Единственное сознательное изменение семантики:** ключи, которых нет в `row.data`, теперь вставляются (раньше пропускались). Нужно для AURORA-1844 (полные строки в ответе) и AURORA-1848 (GET без null). Отмечено в тесте. - Несколько строк в одном сообщении: уже скопированные узлы (WeakSet) повторно не копируются. - Убран `console.log` каждого WebSocket-сообщения (useWebSocket.js). - В cellUtils.js импорт `./rowUtils` -> `./rowUtils.js` (иначе файл не импортируется из node --test). - Не тронуто: `structuredClone` перед `addRootRow` в useRealtimeData.js, insertCell/insertProjectRow/deleteRow, RealtimeContext.js. ## Тесты - Новый cellUtils.test.js (14 тестов): идентичность, новые объекты на пути, вход не мутирован (рекурсивный Object.freeze), паритет с копией старой реализации (вложенные объекты, массивы, null, ключ вне data, пакет, не найдена), ожидаемое отличие (вставка ключей), пакет, строка не найдена -> тот же объект. - `node --test` по всем *.test.js фронта: зелёный; `npm run build` проходит. Бэк не менялся. ## Микробенчмарк (node, синтетический лист 1134 строки, сообщение из 2 правок, медиана из 300) - старая: 3.9-4.7 мс (p95 6-10 мс) - новая: 0.009-0.012 мс (p95 0.01-0.03 мс) Критерий «<= 5 мс» трактуется как onmessage + updateCells; цена рендера React (25-30 мс) — справочная, её снижают 1837/1839. ## Проверить руками - [ ] Два браузера: правка соавтора видна, итоги групп пересчитываются - [ ] Своя правка и её эхо - [ ] Добавление и удаление строки, добавление программы и проекта (FORM_4/STRUCTURE) - [ ] В консоли нет лога на каждое сообщение WebSocket ## Замер до/после Будет добавлен после ночного прогона на стенде perf/: `scroll-coauthor.spec.ts` с `PERF_TARGETS='FORM_2/AHR/*' PERF_COAUTHOR_RATES=5 PERF_REPEATS=2`, затем `analysis/timeline_breakdown.py` -> «Точки в��ода», `useWebSocket.js:30` (было ~3,4 мс на сообщение). ## Rulings I made - R-1: если у ключа нет значения в row.data, но `row[key]` истинно, сохраняется прежняя ветка (запись в поле строки); вставка в row.data только если и `row[key]` ложно. Цена ошибки: ключ вроде depth/sort_order не уйдёт в data (как и раньше). - R-2: массив верхнего уровня ключа сливается по индексам, как в старом deepMerge (паритет, покрыт тестом); массивы глубже заменяются. - R-3: узел строки в пакете может скопироваться повторно при обновлении той же строки (только строки-цели), корректность не страдает. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 2 commits 2026-10-28 17:10:58 +03:00
Копируются только строки на пути к затронутой, остальные сохраняют идентичность;
без изменений возвращается тот же data. Отсутствующие в row.data ключи теперь
вставляются (нужно для AURORA-1844/1848). Тесты с паритетом к прежней реализации.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

Ночной замер на стенде perf/ (29.10, масштаб 1, D0 без grow, CPU 1x, arm-ka-* остановлены; Apple M4)

Сборки фронта: «до» — test 2a28748; «после» — этот PR 4393e96. Бэк в обоих — 1850 b5a245e (D0 + 0050 + 0051): на бэке test (upd_form_cell ≈ 520 мс) rate 5 не снимается — бот не успевает завершиться за 10 с после SIGTERM и теряет итог (sent=0, все 3 повтора; наблюдение O-1). Фронт сравнивается при одинаковом бэке.
Команда: PERF_SQLSTATS_TAG=coauthor PERF_TARGETS='FORM_2/AHR/*' PERF_COAUTHOR_RATES=5 PERF_REPEATS=3 npx playwright test scenarios/scroll-coauthor.spec.ts (повтор 0 под trace, в статистику 2 повтора) → analysis/timeline_breakdown.py.

метрика до после
обработчик t.onmessage (useWebSocket), суммарно / сообщений 2308.7 мс / 722 94.0 мс / 687
на одно сообщение 3.20 мс 0.14 мс
main_thread_ms_per_step автора (2 повтора без trace) 105.3 [104.9–105.6] 101.0 [100.7–101.4]
то же, повтор под trace (RunTask/шаг) 100.8 107.1
echoed / sent, page_ws_updates 205 / 257.5, 205 201 / 246, 201

Вердикт (по рекомендации design: «обработка одного cell_updated» = обработчик onmessage + updateCells, порог ≤ 5 мс): выполнен — 0,14 мс на сообщение (было 3,2 мс; основная часть выигрыша — убранный console.log каждого сообщения). Рендер React при входящей правке этот PR не меняет: на шаг автора −4 % в статистике, повтор под trace — +6 % (в пределах шума одиночного trace). Микробенчмарк updateCells в node не снимал.

Файлы: /private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/cofront/timeline-{base,p1838}/timeline-breakdown.md, прогоны perf/results/n-cof-base, n-cof-p1838 (worktree perf-bench).

🤖 Generated with Claude Code

## Ночной замер на стенде perf/ (29.10, масштаб 1, D0 без grow, CPU 1x, arm-ka-* остановлены; Apple M4) **Сборки фронта:** «до» — `test` 2a28748; «после» — этот PR 4393e96. **Бэк в обоих — 1850 b5a245e** (D0 + 0050 + 0051): на бэке `test` (upd_form_cell ≈ 520 мс) rate 5 не снимается — бот не успевает завершиться за 10 с после SIGTERM и теряет итог (`sent=0`, все 3 повтора; наблюдение O-1). Фронт сравнивается при одинаковом бэке. **Команда:** `PERF_SQLSTATS_TAG=coauthor PERF_TARGETS='FORM_2/AHR/*' PERF_COAUTHOR_RATES=5 PERF_REPEATS=3 npx playwright test scenarios/scroll-coauthor.spec.ts` (повтор 0 под trace, в статистику 2 повтора) → `analysis/timeline_breakdown.py`. | метрика | до | после | |---|---|---| | обработчик `t.onmessage` (useWebSocket), суммарно / сообщений | 2308.7 мс / 722 | 94.0 мс / 687 | | **на одно сообщение** | **3.20 мс** | **0.14 мс** | | main_thread_ms_per_step автора (2 повтора без trace) | 105.3 [104.9–105.6] | 101.0 [100.7–101.4] | | то же, повтор под trace (RunTask/шаг) | 100.8 | 107.1 | | echoed / sent, page_ws_updates | 205 / 257.5, 205 | 201 / 246, 201 | **Вердикт** (по рекомендации design: «обработка одного cell_updated» = обработчик onmessage + updateCells, порог ≤ 5 мс): **выполнен** — 0,14 мс на сообщение (было 3,2 мс; основная часть выигрыша — убранный `console.log` каждого сообщения). Рендер React при входящей правке этот PR не меняет: на шаг автора −4 % в статистике, повтор под trace — +6 % (в пределах шума одиночного trace). Микробенчмарк `updateCells` в node не снимал. Файлы: `/private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/cofront/timeline-{base,p1838}/timeline-breakdown.md`, прогоны `perf/results/n-cof-base`, `n-cof-p1838` (worktree perf-bench). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This pull request can be merged automatically.
You are not authorized to merge this pull request.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/212-aurora-1838-ws-immutable-update:feat/212-aurora-1838-ws-immutable-update
git checkout feat/212-aurora-1838-ws-immutable-update
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/DFiP_Budget_planing#223
No description provided.