Які обіцянки не можна порушити

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

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

Каталог може показувати трохи застарілу кількість місць. Але під час підтвердження перевіряємо реальну доступність і зберігаємо правило останнього місця. Різні операції мають різні вимоги. Це допомагає обрати схему більше, ніж загальна обіцянка "суворої узгодженості".

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

Перехід має умову й власника. Утримання можливе для вільного місця, підтвердження потребує дійсного утримання та відповідної оплати. Скасування може вимагати повернення. Правила в UI, події й джобі легко розійдуться. Власник має бути явним, навіть коли реалізація проходить через кілька компонентів.

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

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

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

Оцінюємо навантаження й обмеження системи

У прикладі 100000 бронювань на добу дають у середньому 1,16 операції за секунду. Але відкриття запису на популярне заняття збере сотні спроб за кілька секунд. Усі конкурують за один об'єкт, і середній RPS цього не показує.

Запис 2 КБ дасть близько 200 МБ на день і 73 ГБ на рік у грубій десятковій оцінці. Це лише основні записи. Окремо рахуємо індекси, історію, WAL, бекапи та репліки. Припущення підписуємо поруч, щоб перерахувати після вимірювань.

З оцінки мають з'явитися питання до ресурсів: з'єднання, гарячий рядок, очікування клієнта, ціна зберігання. Запас "помножимо на десять" не замінює сценарій піка. Він може бути зайвим для загального потоку й недостатнім для одного заняття.

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

Гарячий об'єкт перевіряємо окремо. Тисяча спроб на тисячу занять відрізняється від тисячі спроб на останнє місце одного. Нові процеси можуть підняти конкуренцію, не прискоривши корисне підтвердження. Розподіл входу важливий поряд із загальним RPS і має бути записаний в умовах тесту.

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

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

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

Перекладаємо узгодженість у поведінку продукту

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

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

Є й буденний кейс: клієнт отримав "бронь створено", а на наступному екрані її немає. Навіть якщо репліка наздожене за секунду, він повторить дію чи напише в підтримку. Read-your-writes враховуємо в користувацькому шляху окремо від загальної моделі узгодженості.

Старе місце в каталозі має ціну: клієнт отримує відмову при підтвердженні. Це прийнятно за зрозумілого пояснення й допустимої частки випадків. Eventual consistency називає модель, але не визначає зручність продукту. Частоту й наслідки відмов треба оцінити на потрібному користувацькому шляху.

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

Кеш, пошук і аналітика додають копії, які оновлюються окремо. Їхнє відставання не мусить зупиняти бронь, але UI має розуміти його. Для щойно створеного запису іноді потрібен інший пошук або пояснення. Інакше технічно допустимий lag буде виглядати як втрачений результат.

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

Відновлення мережі теж входить у відмову. Накопичені події прийдуть в іншому порядку. Компоненти визначають чинні й замінені зміни. Зв'язок сам не прибирає конфлікти, тому завершення збою має перевірку. Потрібно повернути систему до узгодженого бізнес-стану, а не тільки до доступного каналу.

Таймаут не повідомляє результат операції

Платіжний сервіс не відповів. Він міг не отримати запит, а міг списати кошти й втратити відповідь. Повтор із новим ID ризикує створити друге списання. Тому UI показує обробку, а бекенд має з'ясувати результат.

Для ретраю зберігаємо ключ операції та спосіб отримати попередній результат. Інші дані під тим самим ключем є конфліктом. Ідемпотентність отримувача не робить нашу БД і зовнішній платіж однією транзакцією. Між ними все ще можливий збій.

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

Невідомий результат робимо станом із ID, початком і статусом. Клієнт перевірить його повторно, джоба звірить із провайдером. Без збереженого стану рестарт лишить лише здогадки за логами. Також потрібен строк очікування, після якого операція отримує наступну дію, а не висить без власника.

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

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

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

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

Що означає відновитися за годину

Доступність, допустима втрата даних і відновлення є різними вимогами. RPO задає, наскільки старою може бути відновлена копія, RTO задає цільовий час повернення сервісу. Якщо втрата однієї оплаченої броні неприпустима, погодинний бекап сам по собі не виконує вимогу.

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

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

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

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

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

Обіцянка змінюється з продуктом. Обсяг подвоїв час, нова інтеграція додала звірку. Старий тест не доводить новий RTO. План переглядаємо разом із даними й значними змінами процесу. Періодична вправа допомагає помітити різницю до того, як вона стане несподіванкою для бізнесу.

Архітектуру ще потрібно підтримувати

Якщо одна база та кілька процесів виконують гарантії й тримають навантаження, нові сервіси мають розв'язувати конкретну проблему. За кожен компонент платимо деплоєм, контрактами, моніторингом і частковими відмовами. Час команди на експлуатацію теж є обмеженням.

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

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

Межі пов'язуємо з даними й незалежною зміною. Частини, які постійно потребують спільної транзакції, після розділення отримають протокол. Команди чи навантаження можуть виправдати ціну, але причина явна. Межа процесу змінює відмови навіть тоді, коли сама бізнес-фіча виглядає так само.

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

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

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

Перевіряємо схему на останньому місці

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

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

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

Фінал є відновленням: дані, звірка платежів, повернення трафіку. Час порівнюємо з RTO, точку даних із RPO. Записуємо перевірене й припущення. Такий набір краще пояснює можливості схеми за архітектурну назву. Якщо якась перевірка не відтворена, не приписуємо їй успіх лише через зелений загальний тест.

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

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

До всіх статей