# План: обсуждение задачи _Создано: 2026-08-12_ ## Контекст Пишем сюда идею, договорённости и шаги, чтобы ничего не забыть. ## Идея (со слов пользователя) > Данные заявок лежат в списках SP Online. Т.к. рекомендуемый предел ~5000, а заявки > копятся бесконечно, придумать механизм «архивации»/шардинга: при достижении 5000 > элементов заводить новый список с тем же именем + числовой постфикс (`— данные_2`), > и фронт начинает писать новые заявки туда. ## Важный факт (уточнение) - **5000 = List View Threshold** — лимит на строки, сканируемые ОДНИМ запросом, а НЕ на хранение. - Список SPO хранит **до 30 млн** элементов. «Переполнения» по хранению не будет. - Реальная проблема = запросы без индекса, сканирующие >5000 строк → throttling. Лечится индексами + фильтрацией, а не разбиением списка. ## Цена шардинга по счётчику (что ломается в нашем коде) - `myRequests.service` → чтение становится UNION по N шардам на каждый тип. - `requestCode.ts` → код `type-YYMM-itemId`, itemId в новом списке снова с 1 → дубли кодов. - Списки согласования растут ещё быстрее (строки маршрута) — их тоже шардить? - Провижининг схемы/индексов/прав (Sites.Selected) для каждого нового списка на лету. - Отчётность/поиск «за период» становятся кросс-списочными. ## Варианты (развилка) - **A. Индексы + пагинация (рекомендую как базу)** — индексируем RequestStatus/Created/Author, читаем через `.top()`+paging. Закрывает throughput-проблему полностью. Кода минимум. - **B. Архивация по статусу/дате** — закрытые заявки переносим в `...— архив`, активный список остаётся маленьким. Это «правильная» версия интуиции пользователя. Разгрузка без ломки кодов. - **C. Шардинг по 5000 (исходная идея)** — крайний вариант, только если реально упрёмся в десятки млн. ## ВЫБРАННОЕ НАПРАВЛЕНИЕ (после обсуждения) Причина тормозов подтверждена пользователем: слабый интернет (Казахстан) + список зависает. Есть мандат «держать ≤5000 элементов в списке, плодить новые списки». Решение = **C (шардинг) + обязательная пагинация**. Обе части нужны: - **Шардинг** ≤5000 на список — выполняет мандат / снимает threshold-throttling (ops). - **Пагинация + фильтр по индексу** — фронт НИКОГДА не качает весь список, только маленькую страницу (`.top(50)`, фильтр по Author/RequestStatus). Именно это убирает зависания на слабом канале. Без этой части шардинг сам по себе скорость НЕ спасёт (4999 строк грузятся так же долго, как 5000). ## Кто что создаёт (решено) - Создание шардов — **бэкенд-скрипт по расписанию** (НЕ фронт). Варианты хостинга: Azure Function (timer) / Azure Automation runbook (PnP.PowerShell) / Power Automate / cron на своём сервере (есть git.tnsu.kz → своя инфра). Auth: app-only (сертификат), Sites.Selected. - Плюсы: права на создание списков не на фронте; тяжёлый провижининг мимо слабого канала юзера; нет гонок (шард готовится заранее); переиспользуем существующий PnP-провижининг. ## Контракт бэк ↔ фронт: РЕЕСТР ШАРДОВ Маленький SP-список / config-элемент. На каждый тип заявки: `базовое имя`, `номер активного шарда`, `счётчик`, (для approval — свой активный/счётчик). - Бэкенд-джоб ОБНОВЛЯЕТ (создал шард → новый активный номер). - Фронт ЧИТАЕТ (активный список для записи + полный набор для чтения). ## Роли в коде фронта (обёртки, детали позже) - `resolveActiveList(type)` → активный список для записи; все `submit.ts` зовут её вместо хардкода `DATA_LIST_TITLE`. - `resolveAllShards(type)` → все шарды для чтения; запрос по каждому с фильтром + `.top()`, склейка маленьких кусков (быстро на слабом канале). - Порог переключения — 4900 (запас до 5000). - Коды: внести номер шарда (`type-YYMM-s2-itemId`), иначе itemId в новом списке снова с 1 → дубли. - Списки согласования шардить по СВОЕМУ счётчику (растут в 3-5 раз быстрее) — проектируем после данных. ## Открытые вопросы (для следующего обсуждения) 1. Хостинг бэкенд-джоба: Azure Function / Automation / Power Automate / свой cron (git.tnsu.kz)? Что доступно и кто админит? 2. Реестр шардов — отдельный SP-список или один JSON config-элемент? (склоняюсь к маленькому списку — виден в админке) 3. ~~Формат кода с шардом?~~ РЕШЕНО: offset-схема (см. ниже). 4. Согласование: шардим сразу вместе с данными или сначала только данные, approval потом? 5. ~~Старые данные >5000 сейчас?~~ РЕШЕНО: таких списков нет, гринфилд. Разовая миграция НЕ нужна. Текущие списки = «шард №1». Настоящая архивация (прятать старые из выдачи) — опционально, потом. 6. Прогноз объёма: заявок/месяц по каждому типу — чтобы понять реальную частоту переключений. ## Решения (зафиксировано) - Направление: шардинг + обязательная пагинация (обе части). - Создание шардов: бэкенд-скрипт по расписанию (app-only), НЕ фронт. - Стык бэк↔фронт: реестр шардов. - РЕЖИМ РАБОТЫ: сейчас ТОЛЬКО план и обсуждение. Код НЕ пишем без явного подтверждения пользователя. ### Код заявки: offset-схема (сквозная нумерация) - ВАЖНО: SP-шный `ID` мы НЕ контролируем — в каждом новом списке он всегда с 1. Нельзя заставить SP стартовать с 5001. Поэтому сквозную нумерацию делаем в НАШЕМ коде, не в SP ID. - Код = `type-YYMM-{offset + itemId}`, offset = сумма финальных счётчиков всех предыдущих шардов. - Шард 1: offset 0 → `hr-2608-1` … (совпадает с текущим форматом → обратная совместимость!) - Шард 2: offset 5000 → `hr-2608-5001` … - Уникальность гарантирована. Непрерывность не идеальна (удаления → дырки в SP ID), но это ок — важна уникальность, а не отсутствие пропусков. - Финальный счётчик закрытого шарда пишет бэкенд-джоб в реестр при переключении. ### «Мои заявки» — из ВСЕХ шардов - `resolveAllShards(type)`: опрос всех шардов, каждый с фильтром по Author + `.top()`, склейка+сортировка. - Трафик мал (с каждого шарда пара строк) → быстро на слабом канале. - Оптимизация на потом: при большом числе шардов читать по умолчанию только свежие, старые — по кнопке «Показать ещё». ## Шаги (черновик, не выполняем) - [ ] Закрыть открытые вопросы 1-6 (обсуждение) - [ ] Спроектировать схему реестра шардов - [ ] Спроектировать интерфейс `resolveActiveList` / `resolveAllShards` (без реализации) - [ ] Спроектировать бэкенд-джоб (псевдокод/алгоритм) - [ ] Только после «го» — реализация ## Заметки - Паттерн доступа сегодня: у каждой формы свой `DATA_LIST_TITLE`, доступ через `getSP().web.lists.getByTitle(...)` (см. `forms/*/submit.ts`, `load.ts`, `myRequests.service`).