Files
ros_control/docs/changes/019-performance-scaling/summary.md
T

37 lines
4.8 KiB
Markdown
Raw Normal View History

# Итоги: 019 — производительность и масштабирование (по ревью 2026-09-27)
Источник — ревью `docs/reviews/2026-09-27-codebase-review.md`, раздел «Производительность и масштабирование» (пункты 8–10).
## Сделано
- **п. 8 — кэш списка бакета** (`app/services/backups.py::bucket_objects`): список объектов и связи `key → backup_id` кэшируются на
`BACKUPS_CACHE_TTL` секунд (60; `0` — выключен); `sync_rows` выполняется только при реальном чтении бакета. `asyncio.Lock` с повторной
проверкой — параллельные запросы не читают бакет дважды. `invalidate()` вызывается после загрузки бэкапа и удаления;
счётчик поколений не даёт чтению, начатому до `invalidate()`, пометить кэш свежим. Принудительно — `refresh=1`
(`GET /backups`, `GET /api/v1/backups`, кнопка «Обновить список»).
- **п. 9 — БД не блокирует event loop**: 44 обработчика API/UI без `await` стали `def` (пул потоков FastAPI); обработчики, вызывающие
`jobs.start_jobs` (нужен работающий event loop для `create_task`), остались `async`. Через `asyncio.to_thread`: чтение устройства и запись
статуса в опросе (`ops.poll_device`, `ops.refresh_status`), `poller.cycle`, `jobs._finish`, строки `Backup` в `ops.run_backup`, `sync_rows`.
SQLite: `journal_mode=WAL`, `synchronous=NORMAL`, `busy_timeout=5000` (только файловая БД).
- **п. 10 — один процесс на БД** (`app/process_lock.py`): `flock` на `<файл БД>.lock` в `lifespan` до `init_db`, снимается при остановке;
второй процесс получает `RuntimeError` и не стартует. README — раздел «Ограничения».
## Найдено на ревью и исправлено
- Гонка кэша: `invalidate()` во время чтения бакета терялся — устаревший список считался свежим весь TTL. Исправлено счётчиком поколений;
мутационная проверка: без исправления новый тест падает (`4 == 5`), с исправлением проходит.
- Ошибка в тесте гонки (кэш перед сценарием был свежим) — исправлена одной строкой оркестратором.
## Проверено
- `pytest`: 24 из 24 (новые: `test_bucket_list_is_cached_between_reads`, `test_process_lock_blocks_second_process`).
Актор в событиях из теперь синхронных обработчиков (`api`, `ui:admin`) подтверждён существующими тестами.
- Стенд (порт 8001): новый код, `journal_mode=wal`, в логах без ошибок.
- Кэш: 36 файлов бакета; `refresh=1` — 0,138 с, из кэша — 0,014 с.
- Event loop: пока `POST /devices/{id}/refresh` ждал недоступное устройство (4,0 с), параллельный `GET /devices` ответил за 0,012 с.
- Второй `uvicorn` на боевой БД внутри контейнера: `RuntimeError` блокировки, `Application startup failed`; основной стенд работает, прерванных задач нет.
- Боевые данные: группы 4/4, устройства 14/14, бэкапы 45/45, задачи 71/71 — совпадают по ID; добавлены 3 события временного устройства (удалено).
Сверка — по согласованной копии (`sqlite3 backup` внутри контейнера), т. к. при WAL файл БД без `-wal` неполон.
## Оговорки
- Кэш, семафор задач и счётчики паролей — в памяти процесса; горизонтальное масштабирование по-прежнему не поддерживается (теперь явно).
- Изменения бакета извне видны не позже TTL или по «Обновить список»; `refresh=1` остаётся в адресе страницы после обновления.
- Для копирования БД с хоста теперь нужен и файл `-wal` (или `sqlite3 .backup`).
- Ручная проверка UI пользователем на момент коммита не подтверждена.