Перейти до вмісту

Блог ·

Проєктувати, ніколи не заміщати

Один вигляд поступу над міграціями, копіями, відновленнями й перевстановленнями, без другого обліку. Дві несправності були цілком беззвучними для нас.

Автор: Provibr

Панель хостингу накопичує тривалу роботу. Міграція між двома панелями керування, резервна копія, відновлення в нову машину, контейнер, який заміщують. Кожне з цього завело власну таблицю, власний словник станів і власний екран, і так тримається, доки хтось не поставить очевидне питання: що саме зараз працює на моєму рахунку і чи щось із цього застрягло.

Спокусливою відповіддю є система завдань. Одна таблиця, один перелік станів, усе решта пише всередину. Спокуслива відповідь є також шляхом до двох джерел правди про той самий факт і до розмови з підтримкою через рік, у якій завдання каже «не вдалося», а таблиця копій каже, що архів лежить у сховищі.

Тому вигляд операцій є проєкцією. Він читає наявні рядки й пише зведення поверх них. У зворотний бік не йде ніколи нічого: жодна функція модуля дзеркала не торкається запусків копіювання, відновлення, міграції чи перевстановлень. Ті таблиці лишаються правдою, а зведенню дозволено бути застарілим, хибним чи відсутнім без того, щоб далі щось ламалося.

Одне дзеркало на таблицю й тест, що читає схему

На цьому шві лежать чотири таблиці, і кожна має рівно одну функцію, яка її проєктує.

Таблиця джерелаСтовпець стануТип операції
backup_runsstatusbackup.run
backup_restore_runsstatusbackup.restore
migration_runsstatusmigration.run
container_reinstall_intentsstatecontainer.reinstall

Останній рядок і є причиною того, що назви стовпців записано як текст, а не припущено. Та таблиця називає стовпець state, і дзеркало, що читало б status, не дістає нічого й без жодної скарги падає в гілку невдачі.

Та таблиця не є документацією. Тест виводить зі схеми бази даних, які з тих таблиць мають стовпець стану, вимагає тут запис для кожної, вимагає, щоб названа функція справді існувала, і вимагає, і це та частина, що робить справжню роботу, щоб кожен файл, який пише один із тих стовпців, називав і своє дзеркало. Пʼята таблиця, що потрапляє на шов без проєкції, є тоді червоним тестом, а не шухлядою, яка тихо перестає рухатися.

Така суворість потрібна, бо ті, хто пише, розкидані: запуск копіювання змінює стан у шести місцях одного файлу, три з них на шляху, що обробляє відповіді звіряння, яке працює вночі. Дозволити кожному з них викликати проєкцію самотужки означає, що шостий забуде, а забування невидиме. Рядок правильний. Просто екран ніколи не оновлюється.

Приймач, що існував двічі

Дзеркалення живе в пакеті, якому належать записи до бази даних, бо і серверна дія вебзастосунку, і складник шлюзу викликають ті самі функції. Той пакет не може опублікувати живу подію: він залежить від бази даних, від спільного коду й більше ні від чого, а шина повідомлень є різним обʼєктом із кожного боку.

Отже, він виставляє приймач. Кожен застосунок реєструє один, а пакет не знає, хто його заповнив. Якщо цього не зробить ніхто, запис до бази даних усе одно триває й жодна жива подія не вирушає, а шухляда оновлюється при наступному завантаженні сторінки. Це чесна деградація, і функція навмисно ніколи не кидає винятків, бо відсутня реєстрація не має права перекинути резервну копію.

Приймач був змінною модуля, і це було хибним у спосіб, що не давав жодної помилки. Next виконує свій файл вимірювання як власний пакунок, окремо від процесів маршрутів. Змінна через це існувала двічі: реєстрація потрапляла в один примірник, а функція, що надсилає, читала інший. Рядки були правильні, стани рухалися далі, шухляда стояла нерухомо, і в жодному журналі не було жодного рядка.

Це знайшли не міркуванням. Це знайшли, поставивши слухача на шину повідомлень і порахувавши: надійшла одна подія там, де мало бути три. Виправлення є гніздом на globalThis за Symbol.for, яке переживає межу пакування всередині одного процесу, та сама форма й та сама причина, що й у спільного зʼєднання з базою даних та зʼєднання з Redis.

Два уроки, що варті більше за саму несправність. Несправність без рядка помилки вимагає приладу поза кодом, який підозрюють. А «цей модуль має один примірник» є припущенням про пакувальник, а не властивістю мови.

Те саме коло робіт знайшло другий беззвучний випадок. Завершення операції та її блокування обидва лишали стовпець етапу недоторканим, тож бездоганно вдала копія назавжди лишалася на «архівується». Нічого не падало; просто останній крок ніколи не надходив.

Скасування є проханням, і один запис є доказом

Саме на скасуванні проєкція найбільше спокушається збрехати. Шухляда знає операцію, операція має кнопку скасування, і найдешевше втілення ставить позначку й закриває панель.

Тому кожен тип оголошує, чи скасування взагалі має сенс. vzdump, що триває, ми скасувати не можемо: команда в агента, завдання в гіпервізора, і ніхто не знає, що являє собою пів архіву у сховищі клієнта. Відновлення таке саме, з додатковою причиною: рядок відновлення тримає бронювання на цільовий ідентифікатор, доки лишається відкритим, а раннє закриття вивільняє той ідентифікатор тоді, коли на нього, можливо, саме сідає гість. Обидва оголошено непідтримуваними, і жодного обхідного шляху немає.

Там, де скасування можливе, вирішує все одно рядок. Міграцію можна скасувати, доки її заплановано, і вже не можна, коли допоміжна програма створює сервери на чужому хості. Перевстановлення контейнера можна скасувати аж до попереднього перегляду й уже не можна після того, як агент почав заміщати.

Коли це вдається, доказом того, що нічого не сталося, є єдиний умовний запис: перевести команду в черзі на скасовану там, де її стан ще очікує. Якщо її вже надіслано, це твердження торкається нуля рядків, а прохання чесно лишається на «попрохано». Прочитати, а потім записати означає два твердження й отже дві нагоди для когось вклинитися посередині.

І тоді важить речення. Подія каже, що команду вийняли з черги до того, як агент її отримав, а не що її скасовано. Нічого не перервали, бо нічого й не починалося. Той рядок іде до загального журналу подій, а не до поступу операції, бо поступ прибирають через девʼяносто днів, а «людина втрутилася в інфраструктуру» поступом не є.

Чотири перепони, і жодна з них не є невдачею

Чотири становища ставлять операцію на «потребує уваги», а не на «не вдалося».

vzdump повідомив OK, а архіву немає в покажчику. Спроба копіювання вичерпала свій час, і ми не знаємо, чи архів існує. Відновлення не прочитати назад або отриманого гостя не вдалося записати. Перевстановлення контейнера спинилося після того, як старого контейнера вже не було.

Назвати будь-яке з них невдачею було б брехнею з наслідком. Невдача запрошує клієнта повторити, а повторити копію означає ще десятки гігабайтів у чиємусь сховищі заради архіву, який, можливо, вже там. Повторити перевстановлення означає другий контейнер. Цикл звіряння дивиться далі, доки покажчик не дасть відповіді, а операція тим часом лишається помітно застряглою, і це саме та міра тривоги, яка потрібна.

Перепона несе також те, кому вона належить. Три з них наші; команда, що справді впала на боці клієнта, так і каже.

Пробний запуск завершується на плануванні, і це не прогалина

Міграція в пробному режимі має пʼять кроків; справжня має вісім, і три додаткові становлять запис і перерахунок. Перелік етапів містить усі шість, тож вдалий пробний запуск показує два останні як невиконані.

Це має вигляд вади смуги поступу й є правдою: пробний запуск не пише й не перераховує. Заміною були б другий тип, що подвоює девʼять записів каталогу девʼятьма мовами заради тієї самої роботи, або перелік етапів, що різниться від рядка до рядка, через що шухляда й сторінка подробиць розійшлися б у тому, скільки кроків існує. Показати правду коштувало дешевше за обидва варіанти.

Що ми занотували б

  1. Проєкція проєктує. Якщо ваш шар зведення може писати назад у джерело, це другий облік, і тепер їх у вас два.
  2. Класти шов у таблицю, яку тест може прочитати, виведену зі схеми, і змушувати його перевіряти, що кожен, хто пише стовпець стану, називає своє дзеркало. Забутий виклик не дає жодної помилки.
  3. Не припускати, що модуль має один примірник. Каркас, що виконує частину вашого коду окремим пакунком, дасть вам два, а симптомом буде тиша.
  4. Вимірювати поза підозрюваним. Слухач на шині знайшов за хвилини те, чого читання не знайшло геть ніяк.
  5. Казати, що сталося насправді. «Вийнято з черги до того, як агент її отримав» довше за «скасовано» і є єдиним точним варіантом.
  6. Лишати слово невдача для невдачі. Невідомі результати потребують власного стану, інакше люди повторюють дорогі операції, які, можливо, вже вдалися.

Для ясності про те, чим це є: описані тут типи стоять за перемикачами розгортання, вимкненими типово, тож міграції та клієнтські копії сьогодні не купити. Кілька речей лишаються недоведеними. Живу подію поступу виміряно лише для міграцій. Бік шлюзу в реєстрації приймача не випробувано зі справді запущеним шлюзом. Ланцюг агента відтворено засобами SQL. А скасування перевстановлення контейнера з екрана операцій жодного разу не проводили в браузері.

Із роботи над платформою у Provibr. Чому смуга поступу відмовляється оцінювати час завершення, є в смуга поступу, що відмовляється вгадувати, а чому міграція, що тихо пропустила крок, гірша за невдалу, є в міграція, якої ніколи не було.

Почніть з одного хоста й одного агента

Створіть обліковий запис, встановіть агента однією командою та підключіть свій перший хост Proxmox. Пробний період триває 14 днів і не потребує платіжних даних.