diff --git a/History/2026-08-26-load-sqlite-locked.md b/History/2026-08-26-load-sqlite-locked.md new file mode 100644 index 0000000..a5f215e --- /dev/null +++ b/History/2026-08-26-load-sqlite-locked.md @@ -0,0 +1,28 @@ +# 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** (детерминированный прогон, должен проходить). +- 16+ потоков — стресс-режим (демонстрирует предел конфигурации). + +## Рекомендация для прода (обсудить отдельно) + +1. Увеличить `busy_timeout` (сейчас 5000 мс) при конкурентной записи. +2. Или сериализовать запись: один writer / очередь apply_ops по контракту. +3. Или retry при `database is locked` на уровне `execute()`. + +Ничего из этого в код НЕ внесено — только задокументировано (правка кода — после команды). diff --git a/tests/README.md b/tests/README.md index a202c95..3c57590 100644 --- a/tests/README.md +++ b/tests/README.md @@ -62,8 +62,11 @@ pytest -m "not load" -q # быстрые юнит-тесты без на ```bash LOAD_CONTRACTS=100 LOAD_SUPPS=20 LOAD_SERVICES=10 \ -LOAD_OPS=5000 LOAD_THREADS=16 LOAD_PER=200 \ +LOAD_OPS=5000 LOAD_THREADS=8 LOAD_PER=200 \ pytest -m load -q + +Примечание: `LOAD_THREADS >= 16` — стресс-режим конкурентной записи: +SQLite с `busy_timeout=5000` даёт `database is locked` (см. `History/2026-08-26-load-sqlite-locked.md`). ``` HTTP-стресс против локального/прод сервиса: diff --git a/tests/load/test_load_concurrency.py b/tests/load/test_load_concurrency.py index 194943a..0a27d7e 100644 --- a/tests/load/test_load_concurrency.py +++ b/tests/load/test_load_concurrency.py @@ -12,7 +12,7 @@ from db import spec_events, spec_current pytestmark = pytest.mark.load -N_THREADS = int(os.getenv("LOAD_THREADS", "16")) +N_THREADS = int(os.getenv("LOAD_THREADS", "8")) PER = int(os.getenv("LOAD_PER", "200"))