Netpulse_SasS/server/migrations/0073_escalation_lease_token.sql
byrsapty ae07bd2a79
All checks were successful
CI / hygiene (push) Successful in 8s
CI / web (push) Successful in 1m21s
CI / server (push) Successful in 1m48s
CI / agent (push) Successful in 2m59s
Прив'язка Telegram: сторінка була, дороги до неї не було
Кнопка під сповіщенням відповідала «ваш Telegram не прив'язано» і не
давала виходу. У базі нуль прив'язок і нуль кодів за весь час.

Сторінка профілю існує, але пункту меню не мала, а єдиний вхід — ім'я
користувача в шапці — малювався за умовою «є ім'я або пошта», тоді як
/me віддавало лише пошту. В облікового запису власника, який заводить
установник і який входить ІМЕНЕМ, вона порожня. Тобто в типовій
інсталяції входу в профіль не було взагалі.

* /me віддає username (тип Me на фронтенді його вже вимагав);
* вхід у профіль малюється завжди для людини;
* пункт меню «Обліковий запис → Мій профіль», perm став необов'язковим;
* текст бота називає те, що видно на екрані;
* сторінка каналів показує стан прив'язки біля telegram-каналу.

Плюс 0073: оренда сходинки ескалації отримала lease_token. Партія
переростає 2-хвилинну оренду, і другий інстанс доставляв ту саму
сходинку паралельно з першим. Тепер запис проходить лише за збігу
токена; при розбіжності не відбувається нічого, сходинка лишається
належною.

65 міграцій, усе зелене проти справжньої бази.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 19:57:09 +03:00

93 lines
7.5 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =====================================================================
-- NetPulse :: 0073_escalation_lease_token.sql
-- Оренда сходинки ескалації отримує токен: хто взяв, той і записує.
--
-- ЩО БУЛО НЕ ТАК
--
-- 0066 дала оренді рядка (leased_until) рівно половину роботи. Вона
-- чесно закриває вікно «взяли — ще не записали»: доки оренда не
-- спливла, другий інстанс сходинку не побачить. Але оренда — це строк,
-- а не володіння, і про друге вона не знає нічого.
--
-- Строк тут дві хвилини (store.EscalationLease), а партія — до сотні
-- сходинок (alerting.escalationBatch), і кожна доставка в ній має
-- ВЛАСНИЙ мережевий таймаут: Telegram, SMTP, вебхук у чужий контур.
-- Тобто «партія довша за оренду» — це не рідкісний збіг, а звичайний
-- вечір, коли один канал підвис.
--
-- Далі рахунок простий. Оренда спливає, поки перший інстанс ще йде по
-- партії. Другий (або наступний такт того самого процесу) бачить рядок
-- вільним, бере ту саму сходинку — з тим самим step_idx, бо перший її
-- ще не просунув, — і доставляє. Через хвилину доходить черга до неї в
-- першого, і він доставляє її вдруге. О третій ночі це два дзвінки, а в
-- журналі — два рядки «надіслано», тобто доказ перетворюється на
-- виправдання: за ним не видно, що система подзвонила двічі за одну
-- сходинку.
--
-- ApplyEscalation при цьому мав ДВІ перевірки стану (stopped_at IS NULL
-- і «алерт усе ще firing»), і обидві потрібні — але обидві про АЛЕРТ.
-- Жодна не про те, чи оренда все ще наша. Звірити було нічим: у рядку
-- лежав лише строк, і строк першого інстансу нічим не відрізнявся від
-- строку другого.
--
-- ЧОМУ ТОКЕН, А НЕ ДОВША ОРЕНДА
--
-- Спокуса очевидна: зробити оренду двадцять хвилин, і партія завжди
-- вкладеться. Це не рішення, а пересунута межа. Скільки б не було
-- поставлено, знайдеться партія довша: сотня сходинок по три канали,
-- кожен зі своїм таймаутом, — це арифметика, а не патологія. Крім
-- того, довга оренда псує протилежний випадок: процес, що впав одразу
-- після взяття, тримає сходинку заручницею рівно на цей строк, а
-- сходинка ескалації — це той, кого не розбудили.
--
-- Токен знімає обидва: строк лишається коротким (впав — сходинку
-- швидко перебирають), а подвоєння закриває звірка володіння, яка від
-- строку не залежить взагалі. Це той самий fencing-токен, що й у
-- розподілених блокуваннях: власність доводиться не тим, що годинник ще
-- не пробив, а тим, що номер збігся.
--
-- ЧОМУ ТОКЕН НА РЯДОК, А НЕ НА ПАРТІЮ
--
-- Один токен на всю партію теж закрив би подвоєння, і коду було б на
-- рядок менше. Але тоді два різні прогони, що поділили одну партію
-- (SKIP LOCKED саме для цього й стоїть), розрізнялись би лише
-- випадковістю збігу, а рядок у базі не казав би, ЯКЕ взяття його
-- тримає. core.new_id() у SET обчислюється для кожного рядка окремо —
-- ціна нульова, а стовпець стає самодостатнім: за ним видно конкретне
-- взяття конкретної сходинки.
--
-- ЩО СТАНЕТЬСЯ, КОЛИ ТОКЕН НЕ ЗБІГСЯ
--
-- Нічого не станеться — і це головна вимога до цього стовпця. UPDATE
-- не чіпає ЖОДНОГО рядка: ні next_at, ні step_idx, ні leased_until
-- (інакше запізнілий інстанс зривав би оренду того, хто зараз працює).
-- Движок бачить applied = false, у мережу нічого не йде, і сходинка
-- лишається належною — тим самим step_idx, з тим самим next_at у
-- минулому. Її доводить до розв'язку той, чий токен у рядку; якщо й він
-- не дійшов — оренда спливає за дві хвилини, і наступний такт бере
-- сходинку заново. Застрягти назавжди нема від чого: жодна гілка не
-- знімає next_at і не подовжує оренду безкінечно.
--
-- СУМІСНІСТЬ
--
-- Стовпець NULL для всіх наявних рядків, і це правильний стан: NULL
-- означає «зараз ніхто не тримає». Живі оренди на момент накату
-- (щонайбільше дві хвилини) отримають токен при наступному взятті.
-- Вимоги NOT NULL немає навмисно — вільний рядок мусить уміти сказати
-- «нічий», а не носити токен-привид від позаминулого взяття.
-- =====================================================================
ALTER TABLE alr.alert_escalations
ADD COLUMN IF NOT EXISTS lease_token uuid;
COMMENT ON COLUMN alr.alert_escalations.lease_token IS
'Токен поточної оренди (fencing). Ставиться разом із leased_until при '
'взятті сходинки, звіряється при записі рішення й знімається разом з '
'орендою. NULL — сходинку зараз ніхто не тримає. Потрібен тому, що '
'обробка партії буває довшою за оренду: без звірки володіння запізнілий '
'інстанс доставив би сходинку, яку вже доставив той, хто перебрав її '
'після спливання оренди.';
COMMENT ON COLUMN alr.alert_escalations.leased_until IS
'До якого моменту сходинка вважається взятою в роботу. Строк, а не '
'володіння: володіння доводить lease_token.';