50 lines
2.7 KiB
Markdown
50 lines
2.7 KiB
Markdown
# Sonnet Review: SQLite для Flask-миграции
|
|
|
|
**Дата:** 2026-07-15
|
|
**Вердикт:** Жизнеспособно. Один реальный баг, остальное — конфигурация.
|
|
|
|
---
|
|
|
|
## ⛔ Критичный баг: inode split-brain
|
|
|
|
**Симптом:** `ThreadPoolExecutor` переиспользует потоки. После classify 4 воркера остаются с открытыми соединениями. `cleanup()` делает `os.remove()` → новый `connect()` создаёт новый файл (новый inode). Но старые воркеры продолжают писать в старый inode (Linux не удаляет файл пока открыт fd). Воркеры пишут в призрак, Flask читает из нового пустого файла.
|
|
|
|
**Решение:** глобальный ключ сессии в `get_conn()`:
|
|
|
|
```python
|
|
_local = threading.local()
|
|
_db_inode = None
|
|
|
|
def get_conn():
|
|
conn = getattr(_local, 'conn', None)
|
|
if conn is None or getattr(_local, 'inode', None) != _db_inode:
|
|
if conn: conn.close()
|
|
conn = sqlite3.connect(DB_PATH, check_same_thread=False)
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.execute("PRAGMA busy_timeout=5000")
|
|
_local.conn = conn
|
|
_local.inode = _db_inode
|
|
return conn
|
|
```
|
|
|
|
## ⚠️ Важные нюансы
|
|
|
|
### WAL-сателлиты
|
|
`os.remove("contracts.db")` не удаляет `.db-wal` и `.db-shm`. SQLite следующего connect'а может применить старый WAL к новому файлу. Удалять все три.
|
|
|
|
### Инициализация схемы после cleanup
|
|
После `os.remove()` → `connect()` создаёт пустую БД. Нужен немедленный `CREATE TABLE IF NOT EXISTS`.
|
|
|
|
### busy_timeout
|
|
4 воркера могут финишировать одновременно → параллельная запись. WAL сериализует, но default timeout = 0 (сразу SQLITE_BUSY). `PRAGMA busy_timeout=5000`.
|
|
|
|
### Gunicorn workers = 1
|
|
Если workers > 1 — несколько процессов поделят один `/tmp/contracts.db`. `cleanup()` одного убьёт файл другого. Зафиксировать `--workers 1`.
|
|
|
|
## ✅ Что не проблема
|
|
|
|
- WAL + несколько потоков — штатный сценарий, readers не блокируют writers
|
|
- Большие base64 в SQLite — работает
|
|
- stdlib sqlite3 — ноль зависимостей
|
|
- /tmp при перезапуске контейнера — файл исчезает, что и нужно
|