Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (store.go, docker-compose.yml, deploy/README.md), і
розділити їх можна було б лише індексуванням шматків. Коміти, які не
збираються, гірші за один великий — тим паче що це рівно той стан, який
перевірявся разом.
ЩО ПРАЦЮЄ НА СТЕНДІ Й ПЕРЕВІРЕНО ТАМ
0058 подієві алерти: syslog, ncm, compliance спрацьовують у мить
події; правило з нереалізованим джерелом більше не зберігається
мовчки
0059 snmp.walk і прототипи шаблонів — таблиці з динамічним індексом
описуються шаблоном, а не Go
0060 відкат конфігу: план як різниця, маскування паролів із підписом
плану, обов'язковий контрольний збір, verifying при обриві
0061 кнопки Telegram: довге опитування, авторизація не з callback_data
0062 аудит і архів хостів; тест на AST, що падає на ключі без назви
0063 RLS: три ролі, окремий пул для фонових тактів
0064 строки зберігання даних і сторінка сховища
0065 приймач SNMP-трапів; перевірено справжніми пакетами по дроту,
переклад v1→v2 за RFC 3584 дає правильний OID
0066 ескалації сповіщень
0067 алерт про вичерпання диска
0068 поля заливки конфігу переїхали в каталог профілів
Плюс: 137 тестів вебу з нуля (їх не було взагалі), одинадцять справжніх
вад, знайдених ними й виправлених, і виправлення двох інтеграційних
тестів grpcapi, які мовчки пропускались півтора року.
ЩО ЩЕ НЕ ЗАПУСКАЛОСЬ
netpulse установник: одна команда замість 18 змінних і
593 рядків інструкції
RLS з першого запуску нова інсталяція під політиками одразу;
RLS-EXISTING-INSTALL.md лишається тільки для
старих інсталяцій
.forgejo + CI раннер не зареєстрований
Ці три перевірені компіляцією й міркуванням, але не виконанням.
ГОЛОВНИЙ ВИСНОВОК ДВОХ СЕСІЙ
Зелена перевірка доводить рівно те, що вона перевіряє. Тест ізоляції RLS
був правильний і зелений — і пропустив зламаний вхід, бо перевіряв «чи
не видно чужого», коли зламалось «чи видно своє». Інтеграційні тести
grpcapi були зелені, бо не виконувались. Схема, довідник і протокол
описували те, чого в коді не існувало, і виглядало це як готове.
Тому в кожному завданні цих сесій стояла вимога назвати НЕПОКРИТЕ, а
чотири задачі закінчились не можливістю, а відмовою: правило з
нереалізованим джерелом не зберігається, профіль без команд заливки
каже про це замість мовчазної кнопки, міграція RLS валить сама себе на
таблиці без політики, тест словника аудиту падає на ключі без назви.
Подробиці — HISTORY.md, розділи за 26 і 27 серпня.
190 lines
15 KiB
SQL
190 lines
15 KiB
SQL
-- =====================================================================
|
||
-- NetPulse :: 0067_storage_alert.sql
|
||
-- Попередження про вичерпання місця: щоб диск не закінчився мовчки.
|
||
--
|
||
-- ЩО ЛИШИЛОСЬ ПІСЛЯ 0064
|
||
--
|
||
-- 0064 навчила систему МІРЯТИ: розмір бази, приріст за добу, «вистачить
|
||
-- ще на N діб». Але міряти й попереджати — різні дієслова. Цифри лежать
|
||
-- на сторінці, на яку заходять раз на квартал, а диск закінчується в
|
||
-- ніч із суботи на неділю. Перша ознака проблеми — Postgres перестав
|
||
-- приймати записи, тобто впав увесь продукт одночасно.
|
||
--
|
||
-- Сторінка без сповіщення — це прилад без сигналізації. Він чесний, він
|
||
-- показує правду, і на нього ніхто не дивиться саме в ту годину, коли
|
||
-- на нього треба подивитись.
|
||
--
|
||
-- ЧОМУ НЕ ЧЕРЕЗ ЗВИЧАЙНЕ ПРАВИЛО МЕТРИКИ
|
||
--
|
||
-- Автор 0064 лишив пропозицію: писати `db.size.bytes` і `db.days_left`
|
||
-- у ts.series на хості машини зонда — тоді наявне метричне правило
|
||
-- працює як є, і рухати движок не треба. Пропозиція перевірена й НЕ
|
||
-- працює, з чотирьох незалежних причин, кожної з яких вистачило б:
|
||
--
|
||
-- 1. Метричне правило (store/alerts.go, evalSeries) обчислюється
|
||
-- запитом із JOIN inv.devices d ON d.id = se.device_id AND
|
||
-- d.deleted_at IS NULL AND d.enabled. Ряд без хоста не дасть
|
||
-- жодного кандидата ніколи. Тобто «хост машини зонда» довелося б
|
||
-- завести в inv.devices штучним рядком.
|
||
--
|
||
-- 2. А цього робити не можна. На INSERT в inv.devices висить тригер
|
||
-- bill.assert_device_limit (0009): штучний хост займає слот
|
||
-- тарифу, а на інсталяції, яка вже вперлась у стелю плану, INSERT
|
||
-- просто впаде — тобто попередження про диск не встановиться саме
|
||
-- там, де інсталяція найщільніша. Плюс цей хост поповз би в
|
||
-- інвентар, на мапи, у масові операції й — найгірше — під наявні
|
||
-- правила «даних немає взагалі» з порожнім селектором, які
|
||
-- застосовуються ДО ВСЬОГО. Тобто попередження про диск почало б
|
||
-- із того, що підняло б хибний алерт про себе.
|
||
--
|
||
-- 3. Правила живуть у кабінеті (alr.rules.tenant_id, і evalSeries
|
||
-- фільтрує se.tenant_id). Розмір диска — факт рівня інсталяції;
|
||
-- це рівно те, що 0064 доводить про строки зберігання: чанк
|
||
-- TimescaleDB не знає кабінету. Щоб правило спрацювало в кожного,
|
||
-- довелося б множити ряд і кожен семпл на кількість кабінетів.
|
||
--
|
||
-- 4. `db.days_left` — не вимір, а частка. Її знаменник буває нулем
|
||
-- (база не росте) і від'ємним (базу почистили), а чисельник
|
||
-- невідомий доти, доки людина не вказала ємність тому. ts.samples
|
||
-- має колонку double precision NOT NULL: записати туди «немає
|
||
-- відповіді» ніяк, а записати нескінченність означає, що першим
|
||
-- зламається JSON у списку алертів. Прогноз має вміти мовчати —
|
||
-- ряд вимірів такого не вміє.
|
||
--
|
||
-- Тому перевірка живе там, де живе сам факт: у такті прибиральника
|
||
-- даних, поруч зі знімком розмірів. Алерт піднімається без правила
|
||
-- (rule_id IS NULL) і без хоста (device_id IS NULL) — обидві колонки
|
||
-- у alr.alerts і так необов'язкові з 0007, а всі читання алертів ходять
|
||
-- туди через LEFT JOIN. Розсилку робить наявний движок: алерт
|
||
-- позначається notify_pending, а TakeNotifyPending забирає його разом
|
||
-- із подієвими (0058). Жодного рядка в движку правил не змінено.
|
||
--
|
||
-- ЩО РОБИТЬ ЦЯ МІГРАЦІЯ
|
||
--
|
||
-- Нічого не створює й нічого не видаляє. Дописує чотири колонки до
|
||
-- єдиного рядка core.storage_config — місця, де 0064 вже тримає все,
|
||
-- що людина знає про том, а система не знає.
|
||
-- =====================================================================
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- 1. Де взяти вільне місце, коли його видно
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- Шлях до тому бази, ЯКЩО він видний процесу прибиральника.
|
||
--
|
||
-- 0064 сказала правду: у Postgres немає функції «скільки вільного на
|
||
-- томі», а сервер застосунку живе в іншому контейнері. У штатному
|
||
-- docker-compose так і є — база тримає том db-data:/var/lib/postgresql/
|
||
-- data, і збірник його не бачить. Тому ємність там вводить людина, і
|
||
-- це лишається робочим станом.
|
||
--
|
||
-- Але «не видно за замовчуванням» — не те саме, що «не видно ніколи».
|
||
-- Той, хто змонтує цей том у контейнер збірника хоч тільки для
|
||
-- читання, отримує справжнє вільне місце замість введеного числа. І це
|
||
-- не косметична різниця: введене число застаріває мовчки (том
|
||
-- розширили, поруч поклали дампи, WAL роздувся від застряглого слота
|
||
-- реплікації), а виміряне — ні.
|
||
--
|
||
-- Порожньо — штатно. Тоді працює шлях 0064: ємність від людини.
|
||
ALTER TABLE core.storage_config
|
||
ADD COLUMN data_path text;
|
||
|
||
COMMENT ON COLUMN core.storage_config.data_path IS
|
||
'Шлях до тому бази, якщо його змонтовано в збірник; тоді вільне місце міряється, а не вводиться';
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- 2. Саме попередження
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- Увімкнене типово. Це навмисне порушення власного правила 0064.
|
||
--
|
||
-- 0064 нічого не вмикає сама («строки не вмикаються самі, щоб оновлення
|
||
-- не забрало нічиєї історії») — і це правильно РІВНО ТОМУ, що строк
|
||
-- видаляє. Помилкове типове значення там знищує дані, і повернути їх
|
||
-- нема звідки.
|
||
--
|
||
-- Попередження не видаляє нічого. Найгірше, що може зробити помилкове
|
||
-- спрацювання, — забрати хвилину уваги чергового. Найгірше, що може
|
||
-- зробити помилкове мовчання, — покласти базу, тобто весь продукт, без
|
||
-- жодного натяку заздалегідь. Ціни несиметричні на кілька порядків, і
|
||
-- типове значення має бути на боці дешевшої помилки.
|
||
--
|
||
-- Є й друга причина, чисто практична: вимкнене типово попередження
|
||
-- вмикає лише той, хто вже думає про диск. А думає про диск той, у
|
||
-- кого він уже закінчувався. Тобто вимкнене типово воно рятує рівно
|
||
-- тих, кого рятувати вже пізно.
|
||
ALTER TABLE core.storage_config
|
||
ADD COLUMN alert_enabled boolean NOT NULL DEFAULT true;
|
||
|
||
COMMENT ON COLUMN core.storage_config.alert_enabled IS
|
||
'Чи піднімати алерт про вичерпання місця; типово так — мовчання тут коштує дорожче за зайве сповіщення';
|
||
|
||
-- Скільки діб запасу вважати попередженням.
|
||
--
|
||
-- Двадцять одна, і це не «приблизно три тижні». Це час, за який на
|
||
-- місце можна ЩОСЬ ЗРОБИТИ в організації, а не в терміналі: помітити,
|
||
-- узгодити, замовити диск або вікно обслуговування, дочекатись його.
|
||
-- Поріг, коротший за цикл узгодження, повідомляє про те, чого вже не
|
||
-- встигнути, — тобто повідомляє даремно.
|
||
--
|
||
-- Чому не 30, які вже підсвічує сама сторінка 0064: сторінка фарбує
|
||
-- смугу в бурштин, і це доречно — вона довідка, її читає той, хто вже
|
||
-- прийшов. Алерт будить. Якби він спрацьовував там само, де фарбується
|
||
-- сторінка, він спрацьовував би на кожній інсталяції, що росте
|
||
-- рівномірно, і його вимкнули б першого ж місяця.
|
||
ALTER TABLE core.storage_config
|
||
ADD COLUMN alert_days_warn int NOT NULL DEFAULT 21
|
||
CHECK (alert_days_warn BETWEEN 2 AND 365);
|
||
|
||
COMMENT ON COLUMN core.storage_config.alert_days_warn IS
|
||
'Запас у добах, з якого попереджати: час на узгодження й закупівлю, а не на команду в терміналі';
|
||
|
||
-- Скільки діб запасу вважати аварією.
|
||
--
|
||
-- Чотири. Це не округлення й не «десь тиждень»: це п'ятниця, вечір →
|
||
-- вівторок, ранок. Прогноз, знятий у п'ятницю ввечері, має пережити
|
||
-- вихідні й лишити ще один робочий день на дію. Три доби з'їдають
|
||
-- вихідні повністю й лишають нуль; п'ять — уже не аварія, а те саме
|
||
-- попередження іншими словами.
|
||
--
|
||
-- Нижче цієї межі єдина дія, що встигає, — скоротити строк зберігання:
|
||
-- drop_chunks повертає місце файловій системі негайно. Купівля диска
|
||
-- вже не встигає, і саме тому серйозність тут інша.
|
||
ALTER TABLE core.storage_config
|
||
ADD COLUMN alert_days_crit int NOT NULL DEFAULT 4
|
||
CHECK (alert_days_crit BETWEEN 1 AND 90);
|
||
|
||
COMMENT ON COLUMN core.storage_config.alert_days_crit IS
|
||
'Запас у добах, з якого це вже аварія: нижче встигає лише скорочення строку зберігання';
|
||
|
||
-- Аварійний поріг не може бути далі за попереджувальний: інакше алерт
|
||
-- народжувався б одразу аварією й пропускав би стан, заради якого
|
||
-- попередження й існує.
|
||
--
|
||
-- CHECK на рівні таблиці, а не перевірка в Go: обидва пороги міняються
|
||
-- однією формою, і суперечність між ними — саме той стан, який має
|
||
-- бути неможливим у базі, а не спійманим у застосунку.
|
||
ALTER TABLE core.storage_config
|
||
ADD CONSTRAINT storage_alert_days_order CHECK (alert_days_crit <= alert_days_warn);
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- 3. Чого тут немає
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- Немає нового значення в alr.rule_source.
|
||
--
|
||
-- Спокуса завести 'storage' і зробити його «правилом як усі» велика, і
|
||
-- вона хибна. Кожне значення цього переліку означає джерело, яке хтось
|
||
-- уміє обчислювати: опитувані живуть в EvaluateRule, подієві — в
|
||
-- приймачах подій. Джерело, якого не вміє ніхто, дає рівно те, від чого
|
||
-- застерігає 0058: правило, що виглядає працюючим і мовчить. Гірше:
|
||
-- движок, натрапивши на нього, отримав би з EvaluateRule помилку раз
|
||
-- на пів хвилини й засмітив би журнал назавжди.
|
||
--
|
||
-- Немає й окремої таблиці порогів на кабінет. Том один на інсталяцію —
|
||
-- рівно як строки зберігання в 0064, і з тієї ж причини. Два кабінети
|
||
-- з різними уявленнями про те, коли диск закінчився, — це два різні
|
||
-- уявлення про одне число.
|
||
--
|
||
-- Немає індексів. Таблиця має один рядок за побудовою (CHECK (id) на
|
||
-- boolean-первинному ключі, 0064); індекс до неї був би оздобою.
|