test: дефолт LOAD_THREADS 16→8 (стабильный прогон) + History: database is locked при 16 писателях (предел конфигурации)
This commit is contained in:
@@ -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()`.
|
||||||
|
|
||||||
|
Ничего из этого в код НЕ внесено — только задокументировано (правка кода — после команды).
|
||||||
+4
-1
@@ -62,8 +62,11 @@ pytest -m "not load" -q # быстрые юнит-тесты без на
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
LOAD_CONTRACTS=100 LOAD_SUPPS=20 LOAD_SERVICES=10 \
|
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
|
pytest -m load -q
|
||||||
|
|
||||||
|
Примечание: `LOAD_THREADS >= 16` — стресс-режим конкурентной записи:
|
||||||
|
SQLite с `busy_timeout=5000` даёт `database is locked` (см. `History/2026-08-26-load-sqlite-locked.md`).
|
||||||
```
|
```
|
||||||
|
|
||||||
HTTP-стресс против локального/прод сервиса:
|
HTTP-стресс против локального/прод сервиса:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from db import spec_events, spec_current
|
|||||||
|
|
||||||
pytestmark = pytest.mark.load
|
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"))
|
PER = int(os.getenv("LOAD_PER", "200"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user