Стадия 5 — SQLite: батчинг записей вместо connect+write на каждый запрос #5

Closed
opened 2026-07-05 04:02:43 +03:00 by agent_vscode · 0 comments
Collaborator

Цель

Убрать SQLite из горячего пути /media: сейчас каждый кэш-хит = поток + новый connect + UPDATE (с busy_timeout до 5с), что душит общий threadpool и стриминг.

Полный план: plans/2026-07-05-stability-fix-plan.md

Контекст (что чиним)

  • Пре-семафорный кэш-хит /media (api_server.py:963-973): fire-and-forget asyncio.create_task(_update_access(...))to_thread → connect → UPDATE — на каждый запрос картинки.
  • Апсерты media id при рендере фида — по одному на каждое медиа (частично решается в стадии 4, п. 4.2).
  • Несколько потоков, заблокированных на локе SQLite по 5с, съедают threadpool → стриминг замирает.

Задачи

  • Модульный аккумулятор _access_updates: dict[(channel, post_id, fid), float] — кэш-хит просто пишет в словарь (на лупе, дёшево и атомарно для asyncio), никакого to_thread
  • Flush-таск раз в 60с: один connect + executemany UPDATE; под supervision из стадии 1 (п. 1.4)
  • Flush при shutdown в lifespan — не терять хвост
  • Удалить fire-and-forget asyncio.create_task(_update_access(...)) целиком
  • (если не сделано в стадии 4) upsert_media_file_ids_bulk_sync с executemany в file_io.py
  • Тесты: хит → flush → added в БД обновился; flush при shutdown; ключи с числовым каналом (-100...)

Где не наебаться

  • Типы ключей: channel всегда str(...) — рассинхрон str/int тихо ломает UPDATE (строка не находится), timestamp перестаёт обновляться, и живые файлы начнут выпадать из кэша по 20-дневному TTL при живом трафике. Тест именно на числовой канал обязателен.
  • Словарь-аккумулятор: перед executemany снимать снапшот и очищать атомарно (взять dict, заменить на пустой), чтобы не потерять обновления, пришедшие во время flush.
  • Flush-таск обязан быть supervised — иначе его смерть = молчаливое протухание кэша через 20 дней.

DoD

На кэш-хит /media — ноль обращений к SQLite в горячем пути (кроме read-кэша MIME); фоновая запись раз в минуту; тесты зелёные.

Зависимости: стадия 1 (supervision), стадия 4 (п. 4.2 — bulk upsert; можно сделать и здесь, если стадия 4 ещё не начата).

## Цель Убрать SQLite из горячего пути `/media`: сейчас каждый кэш-хит = поток + новый connect + UPDATE (с busy_timeout до 5с), что душит общий threadpool и стриминг. Полный план: [plans/2026-07-05-stability-fix-plan.md](https://gitea.vvzvlad.xyz/vvzvlad/pyrogram-bridge/src/branch/fix/stability/plans/2026-07-05-stability-fix-plan.md) ## Контекст (что чиним) - Пре-семафорный кэш-хит `/media` (api_server.py:963-973): fire-and-forget `asyncio.create_task(_update_access(...))` → `to_thread` → connect → UPDATE — на **каждый** запрос картинки. - Апсерты media id при рендере фида — по одному на каждое медиа (частично решается в стадии 4, п. 4.2). - Несколько потоков, заблокированных на локе SQLite по 5с, съедают threadpool → стриминг замирает. ## Задачи - [ ] Модульный аккумулятор `_access_updates: dict[(channel, post_id, fid), float]` — кэш-хит просто пишет в словарь (на лупе, дёшево и атомарно для asyncio), никакого to_thread - [ ] Flush-таск раз в 60с: один connect + `executemany UPDATE`; под supervision из стадии 1 (п. 1.4) - [ ] Flush при shutdown в lifespan — не терять хвост - [ ] Удалить fire-and-forget `asyncio.create_task(_update_access(...))` целиком - [ ] (если не сделано в стадии 4) `upsert_media_file_ids_bulk_sync` с `executemany` в file_io.py - [ ] Тесты: хит → flush → `added` в БД обновился; flush при shutdown; ключи с числовым каналом (`-100...`) ## Где не наебаться - **Типы ключей**: channel всегда `str(...)` — рассинхрон str/int тихо ломает UPDATE (строка не находится), timestamp перестаёт обновляться, и живые файлы начнут выпадать из кэша по 20-дневному TTL при живом трафике. Тест именно на числовой канал обязателен. - Словарь-аккумулятор: перед `executemany` снимать снапшот и очищать атомарно (взять dict, заменить на пустой), чтобы не потерять обновления, пришедшие во время flush. - Flush-таск обязан быть supervised — иначе его смерть = молчаливое протухание кэша через 20 дней. ## DoD На кэш-хит `/media` — ноль обращений к SQLite в горячем пути (кроме read-кэша MIME); фоновая запись раз в минуту; тесты зелёные. **Зависимости**: стадия 1 (supervision), стадия 4 (п. 4.2 — bulk upsert; можно сделать и здесь, если стадия 4 ещё не начата).
Sign in to join this conversation.