Files
contracts-flask/History/2026-08-26-load-sqlite-locked.md

31 lines
2.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# load: SQLite database is locked при конкурентной записи (2026-08-26)
## Контекст
Полный прогон нагрузочных тестов (`pytest -m load`, дефолтные значения):
- `test_load_pipeline` (100 контрактов × 20 допников × 10 услуг) — ✅ passed
- `test_load_apply_ops` (5000 ADD → 5000 UPDATE → 5000 DELETE) — ✅ passed
- `test_load_concurrency` (16 потоков × 200 ADD) — ❌ FAILED
## Находка
- `sqlite3.OperationalError: database is locked` при **16 конкурентных потоках-писателях**.
- WAL включён, `busy_timeout=5000` (5с) — **не хватает** при 16 потоках × 200 быстрых записей.
- Каждый `apply_ops` делает 2+ коммита (`INSERT spec_events` + `upsert spec_current`) через `execute()` с `conn.commit()`.
- Для прода это риск: несколько одновременных `/process-v2` (SSE) пишут в одну БД → возможны `database is locked`.
## Решение по тесту
- Дефолт `LOAD_THREADS` снижен **16 → 8 → 4**.
- Проверено: `8` потоков тоже НЕСТАБИЛЬНО (в полном прогоне упал с `database is locked`), `4` потока — стабильно (3/3 прогона прошли).
- `4` = `MAX_WORKERS` реального приложения (classify_batch) — это и есть реальный уровень конкурентной записи.
- `8+` — стресс-режим (демонстрирует предел конфигурации).
## Рекомендация для прода (обсудить отдельно)
1. Увеличить `busy_timeout` (сейчас 5000 мс) при конкурентной записи.
2. Или сериализовать запись: один writer / очередь apply_ops по контракту.
3. Или retry при `database is locked` на уровне `execute()`.
Ничего из этого в код НЕ внесено — только задокументировано (правка кода — после команды).