Порог: свод 2 GO −30 %, лист FORM_2/AHR −10 % и меньший размер; пороги не условие мержа, корректность (сравнение exact/strip-nulls) — жёсткое. Решения контроллера: план в две фазы, ветка B стеком на A, archive после merge B, база сравнения, замеры контроллером, pytest на PG16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
57 lines
7.6 KiB
Markdown
57 lines
7.6 KiB
Markdown
# Proposal: тяжёлые GET листов и сводов — готовый JSON без pydantic-модели на строку и без null-полей
|
||
|
||
Jira: AURORA-1848 (эпик AURORA-1834). Gitea: #233. Источник цифр: `perf/REPORT.md`, разделы М9 и М10, и `perf/report-data/20261026-s1/analysis/api-split.md` (масштаб 1, медиана 10 повторов, напрямую на :8000).
|
||
|
||
## Why
|
||
|
||
Тяжёлые GET (лист формы, свод 1, свод 2) отдают тысячи строк, в каждой сотни полей, большинство из них `null`. Ответ проходит через Python 4–5 раз: драйвер разбирает jsonb в dict (`json.loads` внутри окна `X-DB-Time-Ms`), роутер строит `SheetResponse` на каждую строку (`api/src/api/v1/forms.py:27-35`, `summary.py:29-37`), FastAPI валидирует `response_model`, сериализует в JSON, затем `GZipMiddleware` сжимает (`api/src/main.py:248`).
|
||
|
||
| запрос (масштаб 1) | полное, мс | `X-DB-Time-Ms` | остаток | доля остатка | размер |
|
||
|---|---|---|---|---|---|
|
||
| свод 2 GO, 5 563 строки | 1 057 | 718 | 339 | 32 % | 30,6 МБ |
|
||
| свод 2 RF, 9 497 строк | 1 808 | 1 229 | 578 | 32 % | 52,0 МБ |
|
||
| лист FORM_2/AHR, 2 092 строки | 813 | 726 | 87 | 11 % | 7,6 МБ |
|
||
| отчёт проекта LIMIT, 1 364 строки | 343 | 307 | 36 | 11 % | 1,5 МБ |
|
||
|
||
Сами данные уже собраны в Postgres как jsonb. Разбирать их в Python, чтобы тут же собрать обратно, — лишняя работа. Явные `null` раздувают ответ и время разбора в браузере: на масштабе 2 свод 2 — 123–195 МБ.
|
||
|
||
## What Changes
|
||
|
||
Два PR, B стеком на A (согласовано 28.10).
|
||
|
||
**PR A — готовый JSON из Postgres, ответ по смыслу тот же.**
|
||
- `GET /api/v1/form/{form_id}/sheet/{sheet}`, `GET /api/v1/svod/1/{sheet}/{year}`, `GET /api/v1/svod/2/{sheet}/{year}` отдают тело, собранное из текста строк, который вернул Postgres (`json_build_object(...)::text`). Нет pydantic-модели на строку и нет валидации `response_model`. Обёртка `{"success","message","result","count"}` прежняя.
|
||
- Отдельным коммитом то же для GET отчётов проектов в `api/src/api/v1/projects.py`, которые объявляют `BaseListResponse[SheetResponse]`.
|
||
- После разбора JSON значения те же, что и раньше. Байты могут отличаться: пробелы и запись чисел (`1.50` вместо `1.5`).
|
||
- Заголовок `X-DB-Time-Ms` сохраняется на всех этих ответах. `response_model` остаётся в декораторах ради OpenAPI.
|
||
- `perf/`: скрипт сравнения ответов до и после, плюс размер ответа на проводе в `api_split.py`.
|
||
|
||
**PR B — без null-полей в `data` + правка фронта.**
|
||
- В ответах GET листа, свода 1 и свода 2 у объектов в `data` нет полей со значением `null`, на любой глубине (`jsonb_strip_nulls`). **BREAKING** для потребителя, который отличает «ключ есть и равен null» от «ключа нет». Единственный такой потребитель — наш фронт. Выгрузка Excel и WebSocket идут своими путями и не меняются.
|
||
- Фронт: входящая правка соавтора (WebSocket `cell_updated`, эхо PATCH) применяется и к ключу, которого нет в загруженной строке. В AURORA-1838 (PR #223) `updateCells` уже вставляет отсутствующий ключ. B проверяет, что этого достаточно, и закрепляет поведение тестом.
|
||
|
||
**Критерии приёмки (согласовано 28.10).** Свод 2 GO — полное время ниже минимум на 30 % после B. Лист FORM_2/AHR — минимум на 10 %, и распакованный размер меньше. Пороги не условие мержа: PR вливается по зелёным тестам и доказанной корректности (сравнение ответов на стенде), цифры — в PR, недобор — в follow-up.
|
||
|
||
**Что НЕ меняется**
|
||
- Ответы на правку — PATCH `/cell`, `/cells`, POST/DELETE `/line`, WebSocket `cell_updated` и правки FORM_3 — по-прежнему отдают `null` явно: очистка ячейки должна дойти до соавторов.
|
||
- Выгрузка Excel и WebSocket читают данные через прежние методы репозиториев и сервисов. Эти методы остаются без изменений.
|
||
- SQL-функции и миграции. Новой миграции нет.
|
||
- Отчёты проектов в PR B: в них `null` остаются.
|
||
- Сжатие (`GZipMiddleware`, уровень 1), постраничная загрузка (М10).
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
- `sheet-read-api`: контракт ответа тяжёлых GET (лист, своды 1 и 2, отчёты проектов) — обёртка, строки, отсутствие null-полей, заголовок `X-DB-Time-Ms`, схема OpenAPI, неизменность путей записи и выгрузки, критерии производительности.
|
||
- `realtime-sheet-updates`: как фронт применяет входящие правки соавторов к строкам, загруженным без null-полей, и как показывает отсутствующие значения.
|
||
|
||
### Modified Capabilities
|
||
Нет: `openspec/specs/` пуст.
|
||
|
||
## Impact
|
||
|
||
- Бэк: `api/src/api/v1/forms.py`, `summary.py`, `projects.py` (только GET), `api/src/repository/sheet_repository.py`, `summary_repository.py`, `project_repository.py`, `api/src/services/sheet_service.py`, `summary_service.py`, `project_service.py` (новые методы рядом со старыми), новый хелпер ответа в `api/src/api/v1/`, тесты в `api/tests/unit` и `api/tests/integration`.
|
||
- Фронт (PR B): `web/src/components/RealtimeTable/utils/cellUtils.js` (если #223 не покрывает всё), тесты `cellUtils.test.js`.
|
||
- `perf/`: `analysis/api_split.py` (новое поле), новый `analysis/compare_responses.py`. В PR B — потребители GET в `perf/perfkit`, если они обращаются к ключам, которые стали отсутствовать.
|
||
- Связь с задачами: AURORA-1838 (PR #223) — B без неё не мержится. AURORA-1844/1850 — контракт строк ответа на правку с явными `null`, этот change его не трогает. AURORA-1846 (PR #232) меняет содержимое `data` FORM_4, этот change — упаковку; общий участок — фикстура `db_session` в `api/tests/integration/conftest.py`. AURORA-1847 — SQL листа FORM_2/AHR: дальнейшее ускорение листа сверх порога этого change (−10 % и меньший размер) — там.
|