# R4 — Слой D: нагрузка и ёмкость (приёмочный тест liderra.ru)

**Owner:** исполнитель | **Зависит от:** R0, R1 (тест-клиенты+баланс), R2 (инъекция). База: `prod-logic-map.md` §18.4, §19, §20, §21.

## Железо (важно для всего слоя)
Прод (`liderra-test`, 10.128.0.15) = **2 ГБ RAM · 2 vCPU @ доля 20% (burstable) · 20 ГБ диск**. На нём Postgres + Redis + PHP-FPM + **один** воркер очереди + nginx. **Слабый, burstable** — под длительной нагрузкой троттлит (сгорают CPU-кредиты 20%-доли), память тесная (риск swap/OOM).

## Цель
Не «сломать», а **снять кривую: как растёт загрузка с темпом**, и **спрогнозировать потолок** (лидов/сутки) против цели 10 000/сут (≈7/мин). Ориентир, не точное число — но лучше, чем вслепую.

## Принципы
- **Грузим на РАСПРЕДЕЛЕНИЕ:** лиды льём на **шеринг-источник** (лид → 3 из 7 клиентов) → каждый лид прогоняет полную раздачу (каскад + жребий + CAP=3 + 3 сделки + 3 списания + 3 лока + уведомления). На одном клиенте алгоритм не работает — замер ложный.
- **Инструментируем ВЕСЬ прогон:** каждый синк проекта / слепок / инъекция = точка замера (сервер + канал).
- **Адаптивный плавный ramp** + **параллельные правки проектов** (реальный совмещённый случай).
- **Safety-stop:** это БОЕВАЯ коробка, на ней спит lkomega → крашить нельзя.
- **Баланс:** 7 шеринг-клиентам большой тест-баланс (3000 лидов × 3 ≈ 9000 списаний).

## Предусловия
- [ ] R1/R2 готовы; 7 шеринг-клиентов с большим тест-балансом и достаточными лимитами.
- [ ] Доступ к метрикам (`/proc`, psql-статистика, `redis-cli`, `systemctl`).
- [ ] Окно: прод спит (0 реальных проектов) — но lkomega-данные на месте, safety-stop активен.

---

## Карточки уровня C

### L0 — Базовая линия + подтвердить один воркер
**Действие:**
```
ssh liderra-prod 'systemctl status liderra-queue.service --no-pager | head -15'
ssh liderra-prod 'nproc; cat /proc/loadavg; free -m | head -2; redis-cli LLEN queues:default'
```
**Ожидаем:** один процесс `queue:work redis`; loadavg низкий; **свободная память покоя** (запомни — 2 ГБ всего); очередь ≈ 0.
**Если не так:** воркеров >1 → зафиксируй; мало свободной памяти уже в покое → safety-порог ставь выше.
**Покрывает:** базовая линия, число воркеров, запас памяти.

### LD1 — Стоимость одной операции (из корректность-прогона R3)
**Что:** пока идёт корректность-прогон (R3: ~16 заведений, ~27 слепков, ~37 инъекций), снимай тайминги.
**Ожидаем:** 1 синк проекта = X сек (ярус канала), 1 `snapshot:rebuild` = Y сек, 1 лид (раздача на 3) = Z сек, при такой-то загрузке.
**Покрывает:** per-operation cost (кормит прогноз).

### LD2 — Наблюдение распределения (спокойный темп, «красиво»)
**Что:** влить ~**500–1000 лидов спокойным темпом** на шеринг-источник → увидеть картину.
**Действие:** инъекция пачкой на щадящем темпе (R2-I7).
**Ожидаем/смотрим:** **гистограмма** — кому сколько раздалось (жребий ∝ остатку лимита по 7); **списания** (ход по ступеням, §6); **блоки** (кто упёрся в лимит, кто замёрз — по share-aware `required` §21).
**Сверка:** `SELECT tenant_id, count(*) FROM supplier_lead_deliveries ... GROUP BY tenant_id`; `lead_charges` по ступеням; кто `frozen_by_balance_at`/`is_active=false`.
**Покрывает:** распределение/списание/блокировка наглядно.

### LD3 — Адаптивный ramp темпа (плавно, до ~3000 или safety-stop)
**Что:** поднимать темп ступенями ПО ФАКТУ нагрузки, не фиксированно.
**Действие:** ступени по 3–5 мин (ловим троттлинг 20%-доли), темп растёт пока коробка тянет:
```
2/мин → 5 → 10 → 20 → 40 → … (поднимаем, пока очередь стабильна и память жива)
```
На каждой ступени — M-METRICS. Останавливаемся на **safety-stop** или ~3000 лидов суммарно.
**Ожидаем:** на ~7/мин — очередь у 0 (держит 10k/сут). Дальше — точка, где **очередь начинает расти неудержимо** / CPU пегается / время-на-лид взлетает = **потолок**.
**Если не так:** очередь поехала уже на низком темпе → один воркер не тянет даже 10k → сигнал «нужно железо/воркеры».
**Покрывает:** кривая темп→нагрузка, потолок, H2.

### LD4 — Параллельные правки проектов во время ramp (совмещённый реальный случай)
**Что:** ВО ВРЕМЯ потока лидов периодически дёргать правки проектов (bulk update лимита/регионов → `SyncSupplierProjectJob` → синк к поставщику).
**Ожидаем:** замер совмещённой нагрузки — лиды (воркер/БД) + синки (канал поставщика) разом, конкурируют за один воркер. Это худший реалистичный случай.
**Сверка:** очередь содержит и `RouteSupplierLeadJob`, и `SyncSupplierProjectJob`; деньги под нагрузкой выборочно сверить (не «поехали»).
**Если не так:** деньги под нагрузкой неверны → **СТОП**, критдефект (важнее замера).
**Покрывает:** D4 пик, канал+воркер совмещённо.

### M-METRICS — Что снимать (на каждой ступени)
```
ssh liderra-prod 'redis-cli LLEN queues:default'                      # очередь
ssh liderra-prod 'cat /proc/loadavg; free -m | head -2'                # CPU/память/swap
ssh liderra-prod 'cat /proc/pressure/cpu 2>/dev/null; cat /proc/pressure/memory 2>/dev/null'  # PSI (если есть) — троттлинг/память
ssh liderra-prod 'psql -h 127.0.0.1 -U crm_app_user -d liderra -tA -c "SELECT count(*) FILTER (WHERE state=''active''), max(now()-query_start) FROM pg_stat_activity"'
```
Канал/поставщик: логи синков (ярус/латентность/повторы); глазами админка поставщика. Job-тайминг: метки `laravel.log` или wall-clock drain.
**Покрывает:** метрики наша/канал/поставщик.

### SAFETY — пороги остановки (БОЕВАЯ коробка!)
**СТОП ramp, если:**
- свободная память < ~150 МБ или пошёл **тяжёлый swap** (риск OOM на 2 ГБ);
- очередь **растёт неудержимо** (не разгребается несколько минут после снижения темпа);
- CPU PSI/loadavg долго в потолке и время-на-лид взлетело;
- любой сбой, влияющий на стабильность (это прод lkomega).
**После стопа:** кривая уже снята — потолок **экстраполируем**, крашить не нужно.

### D-SUMMARY — Вывод по ёмкости
**Заполнить:** «один воркер на этом железе (2 ГБ/20%) держит **X** лидов/сутки безопасно; 10k = **Y%**; узкое место — **…** (воркер/БД/память/канал); рекомендация — **железо мощнее / +воркеры** при **…**». Это ориентир (экстраполяция кривой).
**Покрывает:** прогноз + сигнал по железу.

### D2 — Несколько воркеров (ОТЛОЖЕНО)
Не в этом заходе. Правка `liderra-queue.service` (2/4 воркера) — owner-escape, окно, откат; + проверка гонки группы (§19.4). На 2 ГБ доп. воркеры могут упереться в память — учитывать.

---

## Verification
- [ ] L0 базовая линия (воркер один, запас памяти).
- [ ] LD1 стоимость операций снята.
- [ ] LD2 распределение/списание/блок наглядно (гистограмма).
- [ ] LD3 кривая темп→нагрузка + точка насыщения.
- [ ] LD4 совмещённый пик; деньги под нагрузкой верны.
- [ ] SAFETY соблюдён (не уронили прод).
- [ ] D-SUMMARY: X/сутки, узкое место, железо.

## Troubleshooting
| Симптом | Причина | Фикс |
|---|---|---|
| swap растёт, память к 0 | 2 ГБ тесно | **СТОП** (SAFETY), кривая снята |
| очередь не разгребается | один воркер/слабый CPU | зафиксируй темп-потолок → железо/воркеры |
| CPU-кредиты сгорели (троттлинг) | 20%-доля | это и есть потолок burstable — зафиксируй |
| деньги под нагрузкой неверны | критдефект | СТОП, доложи |

## Rollback
Инъекционные данные (≈3000 лидов × 3 ≈ 9000 сделок/списаний) убираются в R5 по маркеру `TEST-`. Если поднимал воркеры (D2) — верни `liderra-queue.service`.

## Escalation
| Ситуация | Кому | Как |
|---|---|---|
| Один воркер не тянет 10k | владелец | потолок X/сутки → железо/воркеры |
| Риск OOM/падения прода | владелец | СТОП, доложить |
| Деньги под нагрузкой неверны | владелец | СТОП, «ожидали/получили» |

## Матрица покрытия R4
| Вопрос | Карточка |
|---|---|
| воркер/базовая линия/память | L0 |
| стоимость операции | LD1 |
| распределение/списание/блок наглядно | LD2 |
| кривая темп→нагрузка, потолок | LD3 |
| совмещённый пик (лиды+правки) | LD4 |
| метрики 3 стороны | M-METRICS |
| безопасность прода | SAFETY |
| прогноз ёмкости/железо | D-SUMMARY |
| несколько воркеров (H4) | D2 (отложено) |

**Чек-лист «не пропущено»:** ✅ воркер · ✅ стоимость операции · ✅ распределение наглядно · ✅ адаптивный ramp с safety · ✅ совмещённый пик · ✅ метрики · ✅ прогноз + железо. **Дальше → R5.**
