Кеши, PR-юнит 3 (пакет D): фиксы свипера медиа-кеша + дедуп очереди фоновых загрузок #25

Open
opened 2026-07-05 19:15:58 +03:00 by agent_vscode · 0 comments
Collaborator

ТЗ: свипер медиа-кеша

Часть принятой спеки plans/cache-optimization-spec.md. Независим от других юнитов, можно делать параллельно. Один PR. Комментарии в коде — только английские.

Задание 12 — фикс чистки БД (баг)

api_server.py ~820–836: diff и удаление строк из SQLite выполняются только if files_removed > 0. Запись старше 20 дней без файла на диске выпадает из списка в remove_old_cached_files_sync (~663–687: ветка continue без инкремента счётчика) — и остаётся в БД навсегда, если в том же проходе не удалился ни один реальный файл. Исправление:

# Compute the DB diff unconditionally: entries dropped from the list because the
# file was already gone must still be purged from SQLite.
updated_set = {...}
removed_entries = [...]
if removed_entries:
    await asyncio.to_thread(remove_media_file_ids_sync, DB_PATH, removed_entries)
    logger.info(f"cache_sweep: purged {len(removed_entries)} entries "
                f"({files_removed} files removed from disk)")

Задание 13 — интервал свипа в конфиг

Сейчас delay = 60 (~строка 811) — полный проход таблицы + os.walk всего кеша каждую минуту при политиках «20 дней»/«1 час». Добавить в config.py по существующему паттерну ключ cache_sweep_interval (env CACHE_SWEEP_INTERVAL, дефолт 900, минимум 60), использовать в cache_media_files.

Задание 14 — дедуп очереди фоновых загрузок

Сейчас download_new_files кладёт один и тот же недокачанный файл в очередь каждым проходом свипа (особенно под FloodWait). Module-level _queued_media: set[tuple[str, int, str]]:

  • в download_new_files: ключ в set'е → skip; иначе add + put_nowait (при QueueFulldiscard и break, как сейчас);
  • в background_download_worker: в finally рядом с task_done()discard(key).

Event loop однопоточный, обе точки — корутины без await между проверкой и мутацией → гонок нет.

Тесты

  1. Запись >20 дней с отсутствующим файлом при files_removed == 0 → строка удаляется из БД за один проход (регресс на задание 12).
  2. Два подряд вызова download_new_files с одним списком → в очереди один элемент.
  3. После обработки воркером ключ удалён из _queued_media (файл может быть переочередён позже).

Критерии приёмки

  • Строка SQLite с added старше 20 дней и отсутствующим файлом исчезает из БД за один проход свипа.
  • pytest зелёный.
# ТЗ: свипер медиа-кеша Часть принятой спеки `plans/cache-optimization-spec.md`. Независим от других юнитов, можно делать параллельно. Один PR. Комментарии в коде — только английские. ## Задание 12 — фикс чистки БД (баг) `api_server.py` ~820–836: diff и удаление строк из SQLite выполняются только `if files_removed > 0`. Запись старше 20 дней **без файла на диске** выпадает из списка в `remove_old_cached_files_sync` (~663–687: ветка `continue` без инкремента счётчика) — и остаётся в БД навсегда, если в том же проходе не удалился ни один реальный файл. Исправление: ```python # Compute the DB diff unconditionally: entries dropped from the list because the # file was already gone must still be purged from SQLite. updated_set = {...} removed_entries = [...] if removed_entries: await asyncio.to_thread(remove_media_file_ids_sync, DB_PATH, removed_entries) logger.info(f"cache_sweep: purged {len(removed_entries)} entries " f"({files_removed} files removed from disk)") ``` ## Задание 13 — интервал свипа в конфиг Сейчас `delay = 60` (~строка 811) — полный проход таблицы + `os.walk` всего кеша каждую минуту при политиках «20 дней»/«1 час». Добавить в `config.py` по существующему паттерну ключ `cache_sweep_interval` (env `CACHE_SWEEP_INTERVAL`, дефолт `900`, минимум `60`), использовать в `cache_media_files`. ## Задание 14 — дедуп очереди фоновых загрузок Сейчас `download_new_files` кладёт один и тот же недокачанный файл в очередь каждым проходом свипа (особенно под FloodWait). Module-level `_queued_media: set[tuple[str, int, str]]`: - в `download_new_files`: ключ в set'е → skip; иначе `add` + `put_nowait` (при `QueueFull` — `discard` и `break`, как сейчас); - в `background_download_worker`: в `finally` рядом с `task_done()` — `discard(key)`. Event loop однопоточный, обе точки — корутины без await между проверкой и мутацией → гонок нет. ## Тесты 1. Запись >20 дней с отсутствующим файлом при `files_removed == 0` → строка удаляется из БД за один проход (регресс на задание 12). 2. Два подряд вызова `download_new_files` с одним списком → в очереди один элемент. 3. После обработки воркером ключ удалён из `_queued_media` (файл может быть переочередён позже). ## Критерии приёмки - Строка SQLite с `added` старше 20 дней и отсутствующим файлом исчезает из БД за один проход свипа. - `pytest` зелёный.
Sign in to join this conversation.