fix(cache): свипер медиа-кеша — безусловный DB-diff + интервал в конфиг + дедуп очереди #45
Reference in New Issue
Block a user
Delete Branch "fix/25-media-sweeper"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Пакет D спеки cache-optimization: фиксы свипера медиа-кеша + дедуп очереди фоновых загрузок.
cache_media_filesсчитались толькоif files_removed > 0. Запись старше 20 дней БЕЗ файла на диске выпадала изremove_old_cached_files_sync(веткаcontinueбез инкремента счётчика) и оставалась в БД навсегда, если в том же проходе не удалился ни один реальный файл. Теперь диф считается безусловно:removed_entries= старый набор минус выживший; purge при непустомremoved_entriesнезависимо отfiles_removed.cache_sweep_interval(envCACHE_SWEEP_INTERVAL, дефолт 900, пол 60 — значение ниже отвергается на старте_parse_int_env) вместо хардкодаdelay=60(полный скан таблицы +os.walkвсего кеша каждую минуту — расточительно)._queued_media: enqueue пропускает уже-в-очереди ключ(str(channel),int(post_id),file_unique_id),discardвfinallyворкера (упавшая/затротленная загрузка переочередится позже),discard+breakприQueueFull.closes #25
How verified
.venv/bin/python -m pytest tests/ -q→ 455 passed (+3 новых вtest_media_sweeper.py: протухшая запись без файла приfiles_removed==0исчезает за один проход; два подрядdownload_new_files→ один элемент в очереди; ключ вычищается из_queued_mediaпосле воркера).git diff --stat— толькоapi_server.py,config.py,tests/mock_config.py+ новый тест.Внутренний цикл: coder → внутреннее ревью (семантика множеств задания 12 — инверсный баг проверен и не найден; консистентность ключа дедупа enqueue↔worker; отсутствие утечки ключа) → APPROVE, поправлен только неточный коммент про «clamp» (реально reject+exit).
Checklist
Ревью — #45
fix(cache): свипер медиа-кеша — безусловный DB-diff + интервал в конфиг + дедуп очередиВердикт: PASS ✅
Полный веер из 9 аспектов (security · stability · regressions · test-coverage · conventions · documentation · simplification · architecture · coherence). Гейт зелёный:
pytest tests/ -p no:cacheprovider→ 455 passed, exit 0. Три ключевых механизма подтверждены мутационными пробами (ревертишь фикс — падает соответствующий тест).Что проверено по каждой задаче (клик)
Задание 12 — безусловный DB-diff (главный баг). Раньше purge SQLite шёл только внутри
if files_removed > 0. Строка >20 дней, у которой файл на диске уже отсутствует, выбрасывается изupdated_media_filesвremove_old_cached_files_syncбез инкрементаfiles_removed→ приfiles_removed == 0diff не запускался и строка висела в БД вечно. Фикс:removed_entries = (строки media_files) − updated_setсчитается безусловно и purge при непустом наборе. Проверил независимо: over-purge структурно невозможен — живая строка (файл на месте) всегда попадает вupdated_media_files→ вupdated_set→ не вremoved_entries. Еслиremove_old_cached_files_syncбросит, внешний try/except не даёт diff-у выполниться (нет ложного purge).Задание 14 — дедуп очереди загрузок. Модульный
_queued_media: set. Ключ на enqueue(str(channel), int(post_id), file_unique_id)байт-в-байт совпадает сbg_keyв воркере →discardвfinallyвсегда попадает поadd, утечки слота нет. НаQueueFullключ снимается в except. Гонок нет: однопоточный event-loop, междуin-проверкой иaddнетawait. Окно корректно — слот освобождается после обработки, всё ещё отсутствующий файл переедет в очередь на следующем свипе.Задание 13 — интервал свипа в конфиг.
delay = Config["cache_sweep_interval"]вместо хардкода 60._parse_int_env("CACHE_SWEEP_INTERVAL", 900, minimum=60)— при значении ниже 60 или не-intsys.exit(1)на старте (пол подтверждён). Дефолт 900 намеренный (реже гонять скан таблицы + os.walk).⛔ DROP — кодеру НЕ делать (лог калибровки, только для оператора)
[speculative]low/high[test-coverage] тест «свежая строка (файл на месте) не выпилена безусловным diff-ом» — over-purge структурно невозможен (живая строка всегда вupdated_set); тест защищал бы гипотетическую будущую правку, а не дефект этого PR.[below-threshold]low/med[test-coverage] тест «CACHE_SWEEP_INTERVAL ниже 60 → sys.exit» — пол реально работает (проверено),_parse_int_env— дешёвая общая обвязка конфига; рисковые пути (purge/дедуп) покрыты.[speculative]low/med[test-coverage] тест discard-на-QueueFull — узкая ветка (очередь на maxsize 100), маргинально.Замечаний уровня DO/ESCALATE нет — фикс чистый, корректный и покрыт тестами на трёх load-bearing путях. Approve.