Lazy-загрузка фото: при провале make_thumbnail оригинал записан на диск, но local_path не сохранён в БД (photos.py:113-125) — вечный re-fetch + осиротевший файл #1604

Closed
opened 2026-06-16 17:17:19 +00:00 by bot-backend · 0 comments
Collaborator

Severity: medium · Категория: data-loss / wasted-IO · Файл: backend/app/api/v1/photos.py:110-131

Файл: backend/app/api/v1/photos.py:109-131 (ВНЕ выданного набора thumbs.py/init.py — верифицировано по фактическому коду, баг реальный).

Проблема: в lazy-ветке size=thumb _persist_original() (стр. 113) уже ЗАПИСАЛ оригинал на диск (стр. 68 dst.write_bytes). Затем make_thumbnail(src) (стр. 114, overwrite=False). UPDATE, проставляющий local_path / thumb_path / downloaded_at (стр. 116-125), выполняется ТОЛЬКО внутри if generated and generated.exists(): (стр. 115). Если make_thumbnail вернул None (PIL не открыл формат, битый upstream, OOM при энкоде), код проваливается на стр. 128-131 -> 302 redirect, а колонка local_path в БД осталась пустой.

Импакт: оригинал лежит на диске, но БД о нём не знает. На каждом следующем запросе шорткат if local_path and Path(local_path).exists() (стр. 96) не срабатывает (колонка пустая) -> код снова идёт в _fetch_upstream + _persist_original: повторная сетевая загрузка 1-3 МБ к внешнему DOM.РФ CDN и перезапись того же файла при КАЖДОМ запросе грида. Файл осиротевший (не отслеживается, не переиспользуется). При массовом открытии грида — десятки лишних upstream-запросов и постоянный disk-write. Пересекается с Bug #0 (неатомарная запись thumb) как один из триггеров None.

Фикс: сохранять local_path в БД сразу после _persist_original, независимо от успеха генерации thumb. Вынести UPDATE local_path/downloaded_at из-под условия на thumb; thumb_path обновлять отдельно только при наличии generated:

src = _persist_original(obj_id, file_id, photo_name, data)
db.execute(text("UPDATE domrf_kn_photos SET local_path=:lp, downloaded_at=NOW() WHERE obj_id=:o AND obj_file_id=:f"), {"lp": str(src), "o": obj_id, "f": file_id})
generated = make_thumbnail(src)
if generated and generated.exists():
    db.execute(text("UPDATE domrf_kn_photos SET thumb_path=:tp WHERE obj_id=:o AND obj_file_id=:f"), {"tp": str(generated), "o": obj_id, "f": file_id})
    db.commit()
    return FileResponse(str(generated), media_type="image/webp", headers=headers)
db.commit()
# далее redirect — но при следующем запросе сработает шорткат стр. 96 и thumb сгенерится из локального оригинала без сети

Почему баг: Оригинал лежит на диске, но БД о нём не знает. На каждом следующем запросе шорткат if local_path and Path(local_path).exists() (стр. 96) не срабатывает (колонка пустая), и код снова идёт в _fetch_upstream + _persist_original — повторная сетевая загрузка 1-3 МБ к DOM.РФ и перезапись того же файла при КАЖДОМ запросе грида. Файл осиротевший (не отслеживается, не переиспользуется). При массовом открытии грида это десятки лишних upstream-запросов к внешнему CDN и постоянный disk-write.

Как чинить: Сохранять local_path в БД сразу после _persist_original, независимо от исхода make_thumbnail (отдельный UPDATE local_path+downloaded_at, либо переставить UPDATE local_path до проверки generated). Тогда повторный запрос пойдёт по ветке стр. 96 и не будет качать upstream заново.


deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.85 · unit B30

**Severity:** medium · **Категория:** data-loss / wasted-IO · **Файл:** `backend/app/api/v1/photos.py:110-131` Файл: backend/app/api/v1/photos.py:109-131 (ВНЕ выданного набора thumbs.py/__init__.py — верифицировано по фактическому коду, баг реальный). Проблема: в lazy-ветке size=thumb _persist_original() (стр. 113) уже ЗАПИСАЛ оригинал на диск (стр. 68 dst.write_bytes). Затем make_thumbnail(src) (стр. 114, overwrite=False). UPDATE, проставляющий local_path / thumb_path / downloaded_at (стр. 116-125), выполняется ТОЛЬКО внутри `if generated and generated.exists():` (стр. 115). Если make_thumbnail вернул None (PIL не открыл формат, битый upstream, OOM при энкоде), код проваливается на стр. 128-131 -> 302 redirect, а колонка local_path в БД осталась пустой. Импакт: оригинал лежит на диске, но БД о нём не знает. На каждом следующем запросе шорткат `if local_path and Path(local_path).exists()` (стр. 96) не срабатывает (колонка пустая) -> код снова идёт в _fetch_upstream + _persist_original: повторная сетевая загрузка 1-3 МБ к внешнему DOM.РФ CDN и перезапись того же файла при КАЖДОМ запросе грида. Файл осиротевший (не отслеживается, не переиспользуется). При массовом открытии грида — десятки лишних upstream-запросов и постоянный disk-write. Пересекается с Bug #0 (неатомарная запись thumb) как один из триггеров None. Фикс: сохранять local_path в БД сразу после _persist_original, независимо от успеха генерации thumb. Вынести UPDATE local_path/downloaded_at из-под условия на thumb; thumb_path обновлять отдельно только при наличии generated: ```python src = _persist_original(obj_id, file_id, photo_name, data) db.execute(text("UPDATE domrf_kn_photos SET local_path=:lp, downloaded_at=NOW() WHERE obj_id=:o AND obj_file_id=:f"), {"lp": str(src), "o": obj_id, "f": file_id}) generated = make_thumbnail(src) if generated and generated.exists(): db.execute(text("UPDATE domrf_kn_photos SET thumb_path=:tp WHERE obj_id=:o AND obj_file_id=:f"), {"tp": str(generated), "o": obj_id, "f": file_id}) db.commit() return FileResponse(str(generated), media_type="image/webp", headers=headers) db.commit() # далее redirect — но при следующем запросе сработает шорткат стр. 96 и thumb сгенерится из локального оригинала без сети ``` **Почему баг:** Оригинал лежит на диске, но БД о нём не знает. На каждом следующем запросе шорткат `if local_path and Path(local_path).exists()` (стр. 96) не срабатывает (колонка пустая), и код снова идёт в _fetch_upstream + _persist_original — повторная сетевая загрузка 1-3 МБ к DOM.РФ и перезапись того же файла при КАЖДОМ запросе грида. Файл осиротевший (не отслеживается, не переиспользуется). При массовом открытии грида это десятки лишних upstream-запросов к внешнему CDN и постоянный disk-write. **Как чинить:** Сохранять local_path в БД сразу после _persist_original, независимо от исхода make_thumbnail (отдельный UPDATE local_path+downloaded_at, либо переставить UPDATE local_path до проверки generated). Тогда повторный запрос пойдёт по ветке стр. 96 и не будет качать upstream заново. --- <sub>deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.85 · unit B30</sub>
bot-backend added the
week ревью 1
label 2026-06-16 17:17:19 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1604
No description provided.