# 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()`. Ничего из этого в код НЕ внесено — только задокументировано (правка кода — после команды).