Files
ayurishchevandClaude Opus 5.5 ba8ac7be71 Кэш списка бакета, БД без блокировки event loop, один процесс на БД
Производительность и масштабирование, пункты 8–10 ревью (docs/changes/019):
- кэш списка бакета и связей файлов с копиями (BACKUPS_CACHE_TTL, 60 с):
  sync_rows только при реальном чтении бакета; сброс после бэкапа и
  удаления, счётчик поколений против гонки; refresh=1 и «Обновить список»;
- обработчики API/UI без await — обычные функции (пул потоков FastAPI),
  запуск задач остаётся async; запись статуса, задачи и sync_rows —
  через asyncio.to_thread; SQLite: WAL, synchronous=NORMAL, busy_timeout;
- файловая блокировка <файл БД>.lock: второй процесс на той же БД не
  стартует; раздел «Ограничения» в README.

Тесты: 24 из 24. Стенд (порт 8001): кэш 0,014 с против 0,138 с, второй
uvicorn на боевой БД отклонён блокировкой, боевые данные не изменены.
Ручная проверка UI пользователем на момент коммита не подтверждена.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:18:32 +03:00

4.8 KiB
Raw Permalink Blame 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 пользователем на момент коммита не подтверждена.