perf(sse+tasks): индекс SSE-соединений, getTaskTree на recursive CTE, батчи N+1

Шаги 1.4 и 1.5 плана production-готовности:
- SseConnectionIndex (byOrg/byUser), один heartbeat-таймер, протокол SSE не тронут
- getTaskTree/getTaskParentChain: WITH RECURSIVE CTE (было 1+2N запросов)
- sync initial/delta: Promise.all по формам
- embedding queue/reindex: батч-предзагрузки вместо поштучных запросов
- 8 новых тестов (96/96)
This commit is contained in:
2026-09-07 23:29:21 +03:00
parent 9528a8123a
commit 667a321341
10 changed files with 518 additions and 121 deletions

View File

@@ -204,3 +204,29 @@
- Что изменено: `server/routes/task-crud-list.routes.ts` — убраны 7 debug-логов на запрос + per-task лог contract-number; параметр limit (дефолт 20, кап 1000).
- Как проверялось: выборка покрыта индексом tasks_org_id_form_id_idx — новый индекс не нужен; vitest 88/88.
- Влияние на поиск/UX: нет.
---
## [1.4] SSE: индекс соединений
- Статус: ✅ done
- Зачем: рассылка событий не перебирает все соединения; один heartbeat вместо таймера на соединение (Фаза 1).
- Что изменено: `server/utils/sse-connection-index.ts` (новый класс: byId/byOrg/byUser); `server/routes/shared.ts` — EventBus рассылает через индекс (таргетинг org/user/user+org/broadcast с прежней tenant-семантикой), один глобальный heartbeat-таймер (unref, стоп при отсутствии соединений), убраны per-event console.log.
- Протокол НЕ тронут: id:/event:/data:, буферы 500, replay по lastEventId — клиенты совместимы.
- Как проверялось: 8 новых юнит-тестов индекса; vitest 96/96; `npm run check` чисто.
- Влияние на поиск/UX: нет.
- Подводные камни: живой прогон SSE (чат в двух вкладках, reconnect по lastEventId) — проверить на проде после деплоя (юнит-тесты покрывают маршрутизацию, не HTTP-поток).
## [1.5] N+1: recursive CTE, Promise.all, батчи
- Статус: ✅ done
- Зачем: убрать N+1 на горячих путях (Фаза 1).
- Что изменено:
- `getTaskTree` — один WITH RECURSIVE CTE + один IN-запрос (было 1+2N); защита от циклов (lvl<100, assembled-Set); формат результата прежний.
- Новый `getTaskParentChain` (CTE вверх, max 10) — MCP get_task_tree без цикла getTask.
- Батч-методы: getTasksByIds, getTaskFieldValuesByTaskIds, getTaskMessagesByIds/ByTaskIds.
- sync.routes.ts — /initial и /delta: Promise.all по формам (порядок ответа сохранён).
- embedding.service.ts — processEmbeddingQueue и reindexOrganization: батч-предзагрузки (3–4 запроса на org вместо 2–3 на элемент; 2 запроса на чанк 100 задач вместо ~200).
- Как проверялось: vitest 96/96; `npm run check` чисто; caller'ы дерева (роут /tree, MCP) — формат не изменился.
- Влияние на поиск/UX: нет.
- Подводные камни: raw CTE под PostgreSQL — при переименовании колонок tasks/forms обновить вручную (drizzle не проверяет); getRelatedTasks (граф task_relations) сознательно оставлен — кандидат на отдельный шаг.