Блог ·
Оновлено о третій ночі, навмисно
Автоматичне оновлення агента типово ввімкнене на 03:00. Цікавим було те, які становища витрачають слот, наскільки далеко сягає надолуження, і одна ніч із двома.
Автор: Provibr
Агент, що працює у власній мережі клієнта, отримує нову збірку кожні кілька днів. До цього раунду оновлення було кнопкою, яку хтось мав натиснути, тож на практиці воно ставалося тоді, коли комусь випадало подивитися. Побажання вміщалося в одне речення: кожен може задати своєму агентові розклад оновлення, кілька моментів на день, типово ввімкнений на третю ночі.
Речення легке. Раундом це зробило те, що запланований перезапуск інфраструктури має багато способів бути мовчки хибним, і майже всі вони ззовні мають гарний вигляд.
Типове значення стоїть на стовпці, і в цьому все побажання
«Типово ввімкнено для всіх» читається двома способами. Записане в коді застосунку, воно стосується агентів, створених відтепер, а це виключає саме тих клієнтів, які вже мають одного. Записане як типове значення стовпця в міграції, воно стосується кожного наявного рядка однією командою.
Отже, це типове значення стовпця: булеве значення, що починається з істини, і масив JSON, що починається з єдиного запису 03:00. Виміряно після того, як міграція виконалася на базі розробки: кожен наявний агент увімкнений, кожен на третю, а щойно створений агент, якому не передали жодного з двох значень, отримав той самий стан. Остання перевірка є тією, що доводить: роботу виконує типове значення, а не скрипт засівання.
Моменти становлять настінний час у часовому поясі платформи, а не UTC. Часового поясу для кожної організації немає, і панель показує все в тому єдиному поясі, тож розклад, збережений у UTC, влітку зсунувся б на годину, а той, хто ввів третю, зазнав би переривання о пʼятій.
Два шари на одному регулярному виразі
Момент становить HH:MM на сітці з пʼяти хвилин, і цей взірець існує двічі: один раз у перевірнику схеми, який відхиляє поле форми зрозумілим повідомленням, і один раз як обмеження бази даних, що відхиляє все, що ніколи не проходило крізь ту форму.
Обмеження перевірки не може містити підзапиту, тож «кожен елемент цього масиву є чинним моментом» не може бути перевіркою існування над розгорнутим масивом. Що бути може, так це предикат шляху JSON, який добирає саме хибні елементи, загорнутий у перевірку, що цей добір порожній.
Перша версія того предиката була тонко марною, і мала вигляд повної:
$[*] ? ( !(@ like_regex "^([01][0-9]|2[0-3]):(00|05|10|15|20|25|30|35|40|45|50|55)$") )Збіг регулярного виразу з чимось, що не є рядком, у шляху JSON не є хибним. Він є Unknown. А заперечення Unknown теж є Unknown, і фільтр лишає тільки ті елементи, для яких предикат справді істинний. Добір хибних елементів повертався тому порожнім для масиву чисел, і обмеження це приймало. Виміряно проти PostgreSQL 17.10: [3], [null] і [{"a":1}] пройшли всі три.
Виправлення становить одну умову спереду: елементи, тип яких не є рядком, хибні за визначенням. Усі три тепер відхилено. Це саме той клас помилки, який і далі коштує нам раундів: сторожа, яка нічого не ловить і має вигляд повної, і жоден тест не падає, бо не було проти чого падати.
Пʼять кроків, і порядок є раундом
Щохвилини робітник запитує, хто має розклад, передає рядки чистому планувальнику, який вирішує, який слот настав, а потім проходить пʼять кроків на агента. Резервування є кроком чотири, а не першим, і кожен крок перед ним стоїть там зі своєї причини.
| Крок | Якщо відповідь ні |
|---|---|
| Чи є новіша збірка? | Слот усе одно резервують, позначаючи як нічого робити |
| Чи агент зʼєднаний? | Не резервують нічого, і наступна хвилина пробує знову |
| Чи оновлення вже триває? | Не резервують нічого; перше все одно виконає слот |
| Зарезервувати слот | Вирішує унікальний покажчик; той, хто програв, не надсилає нічого |
| Надіслати команду | Та сама функція, що й кнопка в панелі |
Перші два рядки становлять свідомі протилежності, і ця несиметричність є найцікавішим рішенням раунду.
Нічого робити витрачає слот, бо відповідь усередині слоту вже змінитися не може: маніфест випуску на диску той самий протягом усього вікна, тож переоцінювати щохвилини коштує запитів і не дає нічого. І це дає клієнтові що побачити. Сторінка агента, яка каже «03:00, уже свіжий», відрізняється від розкладу, що не працює. Порожній список не відрізняється.
Бути поза мережею не витрачає нічого, бо слот, зарезервований, поки вузол саме перезапускається, витрачений назавжди, і тоді той клієнт мовчки втрачає ніч. Наступна хвилина пробує знову, поки вікно надолуження не закриється; після цього агент просто бере наступний слот.
Пʼятнадцять хвилин, а не шість годин
Резервні копії мають на цій платформі вікно надолуження на шість годин. Це вікно має пʼятнадцять хвилин, і різниця полягає в тому, що псується, коли бути щедрим.
Копія, зроблена о девʼятій ранку замість третьої, лишається копією. Агент, який перезапускається о девʼятій ранку замість третьої, становить переривання посеред робочого дня, а третя ночі є саме тим, що клієнт вибрав, щоб цього уникнути. Пʼятнадцять хвилин покривають те, задля чого це вікно справді існує, щойно перезапущений шлюз чи кілька пропущених обходів, і нічого більше.
Планувальник дивиться на два календарні дні назад, а не на один, і це не недогляд. Момент 23:55 з вікном надолуження припадає, одразу після півночі, на календарний день учора; дивлячись лише на сьогодні, той слот за побудовою ніколи не настав би.
Два гальма, з двох різних причин
Пʼять агентів на організацію за обхід, і двадцять пʼять загалом.
Гальмо на організацію стосується смуги клієнта, а не нашої. Двійковий файл важить близько двадцяти двох мегабайтів, хостер із сорока агентами зазвичай має всі сорок на 03:00, бо це типове значення, а сорок одночасних завантажень виходять одним каналом. Пʼять за хвилину розтягують флот із сорока на вісім хвилин, зручно в межах пʼятнадцятихвилинного вікна.
Загальне гальмо існує тому, що перше діє на організацію: зі ста організаціями це означало б пʼятсот оновлень за хвилину. Решта приходить наступної хвилини, так само в межах вікна.
Над усім цим немає жодного прапорця розгортання, що для нас незвично. Вимикач уже стоїть у клієнта, на стовпці; другий прапорець на рівні платформи обіцяв би те, що стовпець обіцяє вже. Що є, так це аварійне гальмо для нас: поставити нуль в інтервал опитування зупиняє таймер, не змінюючи жодного клієнтського налаштування.
Ніч із двома третіми годинами
Останньої неділі жовтня настінний час 03:00 існує двічі, а останньої неділі березня 02:30 не існує зовсім. На обидва відповідає одна спільна допоміжна функція, не скопійована в цей модуль, саме тому, що алгоритм літнього часу, підтримуваний у двох місцях, розходиться один день на рік.
Та функція відповідає на двозначний настінний час першим із двох моментів, а на неіснуючий наступним моментом, який існує. Наслідки є саме тими, яких хочеться. Довгої ночі 03:00 працює один раз, а не двічі; виміряно: один слот. Короткої ночі 02:30 просто стає 03:30 за місцевим часом замість того, щоб бути пропущеною, бо не пропускати становить безпечний бік тієї монети. Обидва випадки закріплені тестами.
Дні проходять як календарні дні, а не додаванням сталої кількості мілісекунд: додати двадцять чотири години через перехід означає пропустити день або зробити один двічі.
Кому кажуть і як часто
Звіряння, що доповідає про наслідки, живе в робітнику, а не на шляху надсилання, і повідомляє лише розклад. Ручний клік уже мав того, хто дивився.
Сповіщення групуються за організацією, бо сорок агентів у день випуску не мають ставати сорока рядками в скриньці. Про невдачу повідомляють щонайбільше раз на агента за календарний день. А позначку часу, що записує зважений намір повідомити, пишуть і тоді, коли рішенням було змовчати, щоб тишу можна було відрізнити від робітника, який туди ніколи не дійшов.
Заплановане оновлення навмисно не зʼявляється кореневою операцією в шухляді задач: о третій ночі не чекає ніхто, а шухляда, повна нічних рядків, марна для того, задля чого існує. Слідом є рядок запуску, подія і сповіщення. Та подія має власний рід, бо перебіг не має права стверджувати, ніби о третій клієнт сам щось запустив.
Що б ми записали
- «Типово для всіх» є типовим значенням стовпця. У коді застосунку це означає «для всіх відтепер», а це виключає саме тих людей, про яких ви думали.
- Unknown не є хибним. У шляху JSON предикат на значенні хибного типу не є ні істинним, ні хибним, і заперечення не допомагає. Спершу перевіряти тип.
- Вирішувати для кожного становища, чи витрачає воно слот. Нічого робити і не досягти становлять протилежні відповіді, і обидві невидимі, якщо їх поміняти місцями.
- Вікно надолуження є обіцянкою про переривання, а не про повноту. Скопіювати число із системи з іншою поведінкою відмови означає взяти хибне.
- Гальмувати там, де це справді відчувають. Сорок агентів в одній клієнтській мережі ділять один канал, і саме це є межею, а не наш маршрут завантаження.
- Розвʼязати літній час один раз. Дві копії того самого міркування згодні між собою до останньої неділі жовтня.
Для ясності щодо того, чим це є: розклад працює в продуктиві й типово ввімкнений. Кілька речей лишилися недоведеними. Робітника не випробували в справді запущеному шлюзі, жоден справжній агент ще не виконав запланованого оновлення, сповіщення про невдачу з його добовим усуненням повторів не спостерігали наскрізь, а екран перечитали двома мовами з девʼяти.
Із платформної роботи Provibr. Чому агент відмовляється від оновлення, якого не може перевірити, описано в як підписане оновлення агента відмовляє саме собі, а чому агент зʼєднується назовні замість слухати, описано в вихідний агент замість перенаправлення портів.