Як сендер потрапляє в 🧊, скільки там сидить, що має статися, щоб його випустили, і за яких умов замість випуску він отримує 🚨 Need Check. Окремо: що з цього реально керується групами.
Єдиний живий тригер сьогодні — лічильник failed_in_row. Кожен фейл кастомерської відправки проходить через record_failed_send(): лічильник +1, рядок історії 📉 (EVENT 109), і одразу виклик check_deliverability_limits().
message_sent з success=false, вебхук message_failed, і 125-таймаут без guid (детектор таймаутів у scheduler). Фінальна спроба після retry лічильник не інкрементує, лише запускає перевірку.failed_in_row ≥ 2 за замовчуванням, або Group.failed_in_row активної на цей момент групи.Два інші тригери, які видно в коді, вимкнені: check_blocking_servers (24h fail-rate) не стоїть у розкладі, __check_messages_limitation (ліміти 900/24h і OTP_LIMITS_1_HOUR) закоментований. У prod за 7 днів усі 10 648 стартів cooldown мають причину «failed in row».
otp_service/servers/deliverability_limits.py:40–153, otp_service/webhooks.py:268–345, otp_service/scheduler.py:386
Тривалість визначає cooldown_times — скільки cooldown-ів сендер уже пройшов з моменту останнього скидання. Це драбина з двох сходинок і обривом:
Світла частина смуги — мінімум діапазону, насичена — випадковий добір усередині діапазону (random.randint).
next_send_date = cooldown_end_date + 10 хв. Сендер вийшов, але кастомерські повідомлення отримує ще через 10 хвилин.check_cooldown_servers ходить кожні 2 хв (jitter до 5), тож реальний вихід відстає від cooldown_end_date на 0–7 хв.cooldown_times повертається в 0check_deliverability_limits() у гілці «поріг ще не досягнуто» — тобто спрацьовує лише в момент чергового фейлу. Сендер, який доставляє без жодного фейлу, лічильник не скидає, і він чекає на нього до першої невдачі.reset_deliverability_counters), старт preheat, chat-preheat.failed_in_row.deliverability_limits.py:100–153 (драбина), 205–250 (start_cooldown), cooldown.py:36–160
Cooldown не закінчується сам по себе в cooldown_end_date. У цей момент планувальник виносить вердикт: чи довів сендер, що вміє доставляти. Доказом є одна внутрішня server-to-server відправка (warm-up), запланована ще в start_cooldown().
preheat_otp_sender_names (кожні 5 хв, jitter 15) бере сендерів у cooldown лише в останні 75 хв до end_date, шле p2p-повідомлення іншому сендеру, записує cooldown_warmup_uuid / sent_date (EVENT 104 🧊📨).message_sent від сендера (service=imessage) або inbound на приймачі → cooldown_warmup_delivered=True (EVENT 105 🧊✅). Вікно 3 год, uuid не обов'язковий: будь-яка успішна p2p-доставка в cooldown зараховується.Warm-up пішов, підтвердження нема, продовжень < 2. Cooldown +55–70 хв, новий warm-up через 20–30 хв. Якщо підтвердження прийде під час продовження — випуск одразу (early release).
sent ∧ ¬delivered ∧ ext < 2Warm-up доставлено. Єдиний вердикт, що обнуляє fail-open streak.
sent ∧ deliveredПродовження вичерпані, підтвердження так і нема. Тиша не доказ бану (приймач офлайн, Pusher), тому випускаємо, але рахуємо.
sent ∧ ¬delivered ∧ ext = 2Після продовження повторний warm-up так і не диспатчився (девайс офлайн, нема пари).
¬sent ∧ ext > 0Warm-up жодного разу не пішов, хоча cooldown тривав ≥ 30 хв: слот дозрів, а девайс відмовив усім спробам. Це теж доказ.
¬sent ∧ ext = 0 ∧ тривалість ≥ 30 хвCooldown коротший за 30 хв, warm-up не вмістився. Доказів нема ні за, ні проти.
¬sent ∧ ext = 0 ∧ тривалість < 30 хвПри будь-якому випуску: cooldown=False, failed_in_row=0, cooldown_start_date=None; історія отримує рядок «Cooldown finished 💧» (EVENT 100) з вердиктом у note.
Адмін-дія «Finish cooldown» не обходить вердикт
Вона лише ставить cooldown_end_date = now. Наступний тік планувальника винесе вердикт за загальними правилами: якщо cooldown уже тривав ≥ 30 хв і warm-up не пішов, це зарахується як 📵 fail-open і збільшить streak.
cooldown.py:36–160 (вердикти), 163–203 (продовження), 206–245 (підтвердження); preheat.py:211–320; webhooks → confirm_cooldown_warmup_delivery
Cooldown переводить сендер у 🚨 двома незалежними механізмами. Вони рахують різне і мають різний почерк в історії.
cooldown_times (менше 11 доставок за 14 год між фейлами).check_deliverability_limits бачить cooldown_times ≥ 5 і викликає flag_cooldown_server(): статус NEED_CHECK одразу, без cooldown.Telegram: 📵 «Need to check SIM card… due to cooldown many times», топік SIM card. Пропускається, якщо сервер у STATUS_REBOOT (фейли рахуються далі, флаг спрацює на наступному).
Час від cooldown #1 до NC: медіана 34 год (p25 33, p75 37). Це і є сума драбини: 3 × ~1,5 год + 2 × ~13 год плюс паузи на фейли.
Рядок історії без note
flag_cooldown_server не виставляє status_note, тож усі 441 рядок NC цього шляху в Load History порожні. Шлях B підписує себе явно. Варто дописати note на кшталт «cooldown ladder exhausted (#5); failed in row: N».
cooldown_failopen_streak +1.Ліміт: 3 поспіль для звичайного сендера, 5 для preheating=True (preheat-сендери підтверджують warm-up у ~53% проти 84%, тому їм довша повідь).
Telegram: 🧊🚩 «flagged NEED CHECK: N consecutive cooldowns released without warm-up delivery confirmation», топік Need Check. Note в історії: «… (streak 3/3) → NEED CHECK 🚩».
За 7 днів: 74 через ⚠️, 29 через 📵, 3 через 🔇. 13 із 106 — preheating з лімітом 5.
Решта NC за тиждень: 10 з адмінки і 12 від chat-preheat (внутрішня відправка впала в SMS).
deliverability_limits.py:100–101, 252–275 (шлях A); cooldown.py:127–143 (шлях B)
| Cooldown | Стартів | Медіана плану, хв | Мін | Макс |
|---|---|---|---|---|
| #1 | 8 404 | 67 | 10 | 120 |
| #2 | 778 | 85 | 11 | 120 |
| #3 | 556 | 85 | 10 | 120 |
| #4 | 459 | 773 | 12 | 840 |
| #5 | 451 | 775 | 15 | 840 |
Мінімуми 10–15 хв на кожній сходинці — це сендери в групах: групова затримка заміняє драбину повністю (див. розділ 6). Різкий спад 8 404 → 778 означає, що більшість сендерів після #1 встигають назбирати 11 доставок і скинути лічильник.
| Діапазон | Стартів | Звідки |
|---|---|---|
| < 15 хв | 443 | групи 10–30 / 15–25; warm-up не вміщується → завжди 💤 |
| 15–30 хв | 2 626 | групи «Shortest», «Short» (15–45) |
| 30–50 хв | 747 | групи 15–45 / 30–90 |
| 50–100 хв | 4 178 | дефолт #1–#3, група «Medium» |
| 1,5–3 год | 1 837 | дефолт #1–#3 (верх діапазону) + продовження |
| > 10 год | 817 | дефолт #4–#5 |
| Вердикт | Виходів | З них → NC |
|---|---|---|
| ✅ підтверджено | 9 108 | 0 |
| ⚠️ не підтверджено після 2 продовжень | 572 | 74 |
| 📵 без warm-up, ≥ 30 хв | 415 | 29 |
| 💤 без warm-up, < 30 хв | 395 | 0 |
| 🔇 повторний warm-up не пішов | 71 | 3 |
Warm-up відправок 13 060, підтверджень 9 230, продовжень 3 252. Тобто ~86% виходів проходять з доказом доставки, ~10% fail-open, ~4% нейтральних.
Група (otp_service.models.Group) — це набір параметрів з часовим вікном. Сендер може бути в кількох групах; у момент фейлу береться та, чиє вікно накриває поточний час, з найвищим priority. Якщо груп нема або жодна не активна зараз — дефолти з розділу 2.
next_send_date), хвилини. Не про cooldown, але саме це визначає, як швидко сендер назбирає 11 доставок для скидання cooldown_times.Group.clean).Чого група не змінює
Продовження (55–70 хв × 2), пороги streak (3 / 5), правило 30 хв для 📵, розклад warm-up слота, скидання cooldown_times через 11 доставок і сам обрив на cooldown_times ≥ 5. Це константи в коді.
| cooldown_delay | Warm-up | Типовий вердикт | Наслідок |
|---|---|---|---|
| < 15 хв | не вміщується | 💤 | Гейт фактично вимкнено: сендер виходить без доказу, streak не росте. Мертвий девайс іде в NC лише шляхом A. |
| 15–30 хв | слот на +5–10 хв, впритул | ✅ або 💤 | Часто встигає. Якщо не пішов — усе одно нейтрально, бо тривалість < 30 хв. |
| 30–100 хв | end − 25…35 хв | ✅ / ⚠️📵 | Повний гейт: і продовження, і streak. Оптимум за співвідношенням простою до доказовості. |
| ≥ 100 хв | end − 55…70 хв | як дефолт | Поводиться як штатна драбина #1–#3, лише без стрибка до 12–14 год на #4–#5. |
cooldown_delay 15–45: п'ять cooldown-ів проходять за 3–5 год замість ~34, і шлях A спрацьовує в межах робочої зміни. Ціна — менше часу «відлежатись» для живого, але тимчасово нестабільного акаунта.failed_in_row 3–4 плюс cooldown_delay 120–240: рідше входить, довше сидить, warm-up гарантовано вміщується.| Група | prio | Вікно | fails | cooldown, хв | send, хв | Active | New | інші |
|---|---|---|---|---|---|---|---|---|
| High frequency | 100 | 08:00–17:00 | 2 | 480–720 | 4–6 | 0 | 0 | 0 |
| Short cooldown after 1 fail | 95 | завжди | 1 | 15–45 | 8–12 | 0 | 0 | 0 |
| Short cooldown | 90 | завжди | 2 | 15–45 | 8–12 | 19 | 18 | 228 |
| Shortest cooldown | 85 | завжди | 2 | 10–30 | 8–12 | 31 | 22 | 301 |
| Medium cooldown | 80 | завжди | 2 | 30–90 | 8–12 | 12 | 21 | 235 |
| Shortest cooldown #2 | 75 | завжди | 2 | 15–25 | 8–12 | 37 | 50 | 445 |
| Long cooldown | 70 | завжди | 2 | 60–120 | 8–12 | 0 | 0 | 0 |
| Short cooldown #2 | 50 | завжди | 2 | 15–45 | 8–12 | 33 | 31 | 389 |
| Short cooldown, longer send intervals | 40 | завжди | 2 | 15–45 | 12–16 | 0 | 0 | 0 |
З 660 Active сендерів у групах лише 132; решта 528 живуть на дефолтній драбині. «Інші» — переважно Deleted / Lost / Need Check, що лишились у групах як хвіст від старих батчів. Жодна група з вікном не використовується: «High frequency» порожня.
groups у формі Server.Authorization: <MANAGE_SERVERS_API_AUTH_KEY>, префікс api/v1/4fa5e0fd/servers/):# групи
GET groups/ список
POST groups/ створити: name, priority, failed_in_row,
cooldown_delay_from, cooldown_delay_to,
send_from_message_interval, send_to_message_interval,
from_time, to_time ("HH:MM[:SS]" або null)
GET groups/<uuid>/ деталі
PATCH groups/<uuid>/ змінити будь-яке з полів вище
DELETE groups/<uuid>/
# прив'язки, серверо-центрично (до 500 items за запит)
POST groups/assignments/ set: items[{server:{region,unit} | uuid, groups:[uuid…]}]
POST groups/assignments/add/ add
POST groups/assignments/remove/ remove
# прив'язки, групо-центрично
POST groups/<uuid>/servers/ set список серверів групи
POST groups/<uuid>/servers/add/
POST groups/<uuid>/servers/remove/
Зміна параметрів групи діє з наступного фейлу: уже запущений cooldown свою cooldown_end_date не перераховує.
otp_service/models.py:1177–1215 (Group), 632–642 (get_group_for_current_time); otp_service/servers/groups.py; otp_service/urls_statistics.py:26–35