Рахуємо операції, а не користувачів
Десять тисяч користувачів у базі не підказують кількість серверів. Більшість може заходити раз на місяць, а один клієнт із масовим експортом здатний покласти сервіс. Потрібні запити за секунду, робота на один запит, розмір даних і характер піків.
Розділяємо швидкі читання, записи, важкі обчислення та фонові джоби. У кожного класу свій бюджет затримки й ліміт паралельної роботи. Якщо змішати все в середню цифру, дешеві читання сховають повільні звіти, а навантажувальний тест дасть надто оптимістичний результат.
У стабільному потоці 200 RPS за середнього часу запиту 0,2 секунди означають близько 40 запитів у системі одночасно. За двох секунд це вже близько 400. Йдеться про середні величини в усталеному режимі. Формула не рахує p99 і не означає, що нам автоматично потрібні 400 з'єднань із БД.
Для моделі беремо конкретну годину продукту: клієнти відкривають замовлення, іноді змінюють запис і рідко завантажують звіт. Частота й ціна операції різні. Один відсоток важких запитів може забирати більшість CPU або читань диска. Тому частка запитів не дорівнює частці витрат, а тест має відтворювати обидві.
Піки мають форму. Одночасний вхід після розсилки навантажує холодний кеш і швидко піднімає конкурентність. Тривалий денний ріст перевіряє сталу пропускну здатність. Для цих випадків потрібні різні сценарії. Одне максимальне число RPS не показує, як сервіс переживе кожен із них і за який час повернеться.
Бюджет відповіді ділимо між кроками. Обіцяна секунда не дозволяє п'ятьом послідовним викликам мати по секунді. Частину часу заберуть мережа, серіалізація й черги. Так видно, яка залежність уже витратила обіцянку, навіть якщо кожен сервіс окремо вважає свій виклик швидким.
Розмір роботи теж обмежуємо. Список із десяти об'єктів та експорт за рік можуть потрапляти в один ендпоінт, але мати різну ціну. Сам ліміт конкурентності не контролює розмір запиту. Важким операціям задаємо обсяг вибірки, час виконання й правила фонового шляху до результату.
Ріст даних змінює модель. Порожня БД не описує наступний рік: великі акаунти, індекси й популярні об'єкти піднімуть ціну запиту. Записуємо обсяг і перекоси поряд із результатом тесту. Тоді наступне порівняння пояснить, чому старий RPS більше не вкладається, замість перетворитися на суперечку про налаштування.
Як знайти вузьке місце сервісу
Запит рахує 30 мс, потім 900 мс чекає вільне з'єднання з БД. CPU вільний, користувачу повільно. Нові інстанси додадуть охочих зайняти пул, але база не прискориться. Тому розділяємо виконання й очікування: черга, пул, блокування, виклик апстриму.
На тесті поступово піднімаємо навантаження й шукаємо місце, де throughput перестає рости. Якщо завершень більше не стає, а незавершених запитів дедалі більше, ми вже накопичуємо борг. Довга черга приховує відмову, але додає latency, пам'ять і роботу для клієнтів, які давно закрили сторінку.
Робочий ліміт обираємо нижче точки, де розвалюється час відповіді. Запас потрібен для стрибків і втрати частини ресурсів. Правило "CPU нижче 70%" тут мало допомагає: межа залежить від роботи й того, де запити чекають.
Порівнюємо вхід, успішні завершення й роботу в процесі. Так помічаємо, коли сервіс перестає наздоганяти потік. Помилки приходять пізніше: користувачі ще чекають, ресурси вже зайняті майбутніми таймаутами. Накопичення дає ранній сигнал і час обмежити тиск до масового провалу.
Один повільний трейс показує шлях, але не його поширеність. Перевіряємо, скільки запитів чекають так само, і зіставляємо з агрегованими метриками. Інакше день оптимізації рідкісного хвоста не прибере масове очікування пулу. Потрібні і конкретна історія, і масштаб того самого явища.
Очікування з'єднання відділяємо від SQL. П'ятнадцять мілісекунд виконання після п'ятисот мілісекунд очікування не завжди означають потребу в новому індексі. Причиною можуть бути зайва конкурентність, неповернуті з'єднання чи зовнішній виклик у транзакції. Вимірювання лише часу бази приховає ці причини.
Під час експерименту змінюємо одну змінну. Обмежимо паралельні звіти, залишимо ресурси й повторимо трафік. Швидші інтерактивні запити дадуть доказ конкуренції. Одночасна заміна сервера, пулу й параметрів може допомогти, але не пояснить, який крок потрібен і як повторити результат.
Після виправлення вимірюємо весь ланцюжок знову. Вузьке місце переміщується з CPU до БД чи квоти апстриму. Це очікувано. Перший успішний scale-out не стає постійним правилом додавати інстанси. Потрібно знайти нову межу й зрозуміти, який ресурс тепер контролює завершення корисної роботи.
Що кожен інстанс додає під капотом
Процес має пул до 20 з'єднань. Чотири інстанси можуть відкрити 80, двадцять уже 400. Якщо БД витримує менше, автоскейлінг застосунку впирається в її ліміт. Рахуємо бюджет для всього сервісу, залишаючи місце іншим застосункам, джобам та адмініструванню.
Ще один стопер: локальний стан. Файл залишається на диску одного інстансу, сесія в пам'яті губиться після перемикання, джоба за розкладом стартує в кожному процесі. До додавання реплік вирішуємо, де спільний стан і хто відповідає за фонову роботу.
Пул не збільшуємо автоматично зі зростанням запитів. Більше з'єднань іноді дає більше конкурентної роботи всередині БД і гіршу відповідь. Шукаємо діапазон корисного завершення без зайвого тиску. Для важких джоб та інтерактивного шляху можуть знадобитися різні бюджети, щоб один клас не забрав усе.
Файли потребують спільного сховища або явно локального сценарію. Сесії мають перевірятися будь-яким інстансом. Для джоб потрібні власник виконання або безпечні повторення. Прив'язка до одного процесу може бути тимчасовим рішенням, але його відмова тоді потребує окремої поведінки. Балансувальник не переносить цей стан сам.
Балансувальник ділить запити, але не їхню ціну. Один клієнт робить експорт, інші читають мало; з'єднання мають різну тривалість, процеси по-різному прогріваються. Дивимося розподіл роботи й latency за інстансами. Середній графік флоту може бути нормальним, поки один процес уже регулярно відмовляє.
Зменшення потужності теж тестуємо. Спершу припиняємо нову роботу, потім даємо поточній завершитися в межах строку. Незавершеним задачам потрібні повернення в чергу чи звірка результату. Без правил звичайний деплой і scale-down втрачатимуть операції навіть за здорового загального графіка сервісу.
Новий інстанс створює з'єднання, метрики, локальний кеш і читання прогріву. Одночасний старт кількох процесів може перевантажити залежність, яка вже має мало запасу. Плавний допуск трафіку та спільний бюджет роблять розширення керованим. Рахуємо цю ціну, а не лише номінальну пам'ять нового контейнера.
Не даємо перевантаженню розійтися по системі
Обмежуємо одночасні операції, довжину черги й час очікування. Швидка відмова частині запитів зберігає ресурс для решти. Можна тимчасово не будувати важкі звіти, щоб продовжувати оформлення замовлень. Пріоритети мають відповідати важливості операцій для продукту.
Ретраї без спільного бюджету підсилюють аварію. Якщо на трьох рівнях кожен виклик має до трьох спроб, один запит може дати 27 звернень до останньої залежності. Обираємо відповідального за ретрай, обмежуємо спроби й додаємо backoff із jitter, щоб клієнти не повторювали все одночасно.
Таймаут означає, що ми не дочекалися відповіді. Операція могла завершитися. Для повторного запису потрібні стабільний ключ і захист від дублікатів, пов'язаний зі зміною даних. Ключі в пам'яті процесу не переживуть рестарт. Треба узгоджено зберегти бізнес-результат і можливість повернути його при повторі.
Відмовляємо там, де це ще дешево. Коли черга зайняла пам'ять, а запит відкрив транзакцію, пізній ліміт не поверне всі витрати. Розмір входу, конкурентність і admission control перевіряємо перед дорогою частиною. Місце обмеження так само важливе, як числове значення, бо визначає ресурс уже прийнятої роботи.
Backpressure має змінювати поведінку попереднього етапу. Надійна черга лише відкладає проблему, якщо виробник завжди швидший за воркерів. Потрібні максимальний корисний вік задачі й рішення для простроченої роботи. Інакше довге зберігання перетворить короткий пік на години обробки вже непотрібних результатів.
Ретрай потрібен, якщо повтор може змінити результат. Невірний вхід не виправиться сам. Тимчасовий мережевий збій можна повторити за безпечної операції й залишку часу. Бюджет задає загальний дедлайн користувача, а не новий повний таймаут кожної спроби. Інакше запит триває довше обіцяного.
Jitter розводить повтори в часі. Однакова затримка секунду знову збере клієнтів разом. Випадковість не додає потужності, тому тривале перевантаження все одно потребує менше входу, дешевшої роботи чи ресурсу в потрібному місці. Налаштування ретраю не є способом збільшити максимальний throughput.
Скасування теж проходить шлях. Клієнт пішов, сервер далі будує звіт і викликає апстрим. Де безпечно, передаємо скасування й звільняємо ресурс. Якщо вже змінюються дані, закриття клієнтського очікування не залишає бізнес-результат невизначеним. Завершення операції та її подальше отримання розбираємо окремо.
Автоскейлінг не миттєвий
Інстанс потрібно запустити, прогріти й допустити до трафіку. Короткий сплеск може закінчитися раніше. Для відомого старту продажу потужність краще підготувати заздалегідь. Для несподіваного піка потрібні ліміти й зрозуміла відповідь, інакше сервіс впаде до появи ресурсів.
Сигнал обираємо під роботу. Воркери можуть чекати БД і майже не витрачати CPU. Для них корисні швидкість надходження, завершення й вік найстарішої задачі. Тисяча легких задач і тисяча хвилинних звітів потребують різної потужності. Нижче за ланцюжком теж є ліміти, тому воркерів не можна додавати безкінечно.
Вимірюємо повну реакцію: збір метрики, рішення контролера, старт, прогрів і готовність. Швидкий контейнер ще не означає швидку корисну потужність. Для важкого прогріву може знадобитися постійний запас. Цю затримку враховуємо в сценарії піка, а не дізнаємося про неї під час запуску.
Для черги оцінюємо завершення. Якщо вхід зупинився, кількість задач, поділена на швидкість, приблизно дає час розбору. За постійного входу важлива різниця швидкостей. Неоднакові класи рахуємо окремо: швидкі задачі можуть зробити загальне середнє оптимістичним, хоча важка партія лишається надовго.
Ріст і спад мають різну обережність. Швидке додавання допомагає, швидке видалення після короткого падіння може повернути чергу. Пороги, затримки й мінімум процесів враховують форму трафіку, прогрів і потрібний час відновлення. Перевіряємо це на навантаженні, а не копіюємо параметри чужого прикладу.
Верхню межу перевіряємо завчасно. Квоти, адреси, з'єднання й БД можуть зупинити ріст раніше бажаної кількості процесів. Досягнення межі має бути видимим. Увімкнений автоскейлінг не доводить доступність додаткового ресурсу в той момент, коли на нього покладається обіцянка продукту.
Сигнал може зникнути, запізнитися чи змінити зміст після релізу. Визначаємо поведінку контролера за поганих даних і межі помилкового скейлінгу. Автоматизація зменшує ручну роботу, коли її відмови зрозумілі. Інакше додає ще одного учасника, чиї рішення доведеться розбирати під час інциденту.
Перевіряємо навантаження разом зі збоєм
На тесті відтворюємо розміри даних, суміш операцій і поведінку клієнтів після помилки. Потім прибираємо інстанс або сповільнюємо апстрим. Capacity plan, який працює лише в ідеальних умовах, не залишає запасу на відмову.
Порівнюємо успішні операції за секунду, хвіст latency, вартість і відновлення. Фіксуємо умови й знайдену межу. Наступним кроком може бути SQL або пул, а не ще один інстанс. Результат скейлінгу видно в корисній роботі сервісу, а не кількості контейнерів.
Генератор має зберігати потрібний темп за повільних відповідей. Якщо кожен віртуальний клієнт чекає завершення перед новим запитом, зростання latency саме зменшить вхід. Гарний звіт може з'явитися тому, що тест уже не відтворює справжній пік. Модель генерації обираємо явно.
Межі генератора й мережі перевіряємо окремо. Вичерпаний CPU тестової машини дасть її стелю. Один акаунт і набір ключів створять нетипову конкуренцію чи теплий кеш. Умови мають бути відтворюваними, із записаними даними та розподілом трафіку. Інакше порівняння релізів втратить зміст.
Відмову задаємо конкретно: немає інстансу, апстрим уп'ятеро повільніший, з'єднання іноді обривається. Дивимося збережені операції й повернення. Фраза про стійкість до збоїв без умов не перевіряється. Різні аварії зачіпають різні ділянки та гарантії користувача.
Ціну рахуємо на успішну корисну операцію. Подвоєння ресурсу заради п'яти відсотків throughput може бути прийнятним на короткий пік, але дорогим постійно. Залежності й супровід входять у розрахунок. Так скейлінг лишається вирішенням задачі, а не демонстрацією кількості серверів.
Результат стає робочими обмеженнями: безпечний діапазон, запас, умови скейлінгу й дії за межею. Перевіряємо після значних змін даних або шляху. Потужність має дату й умови тесту. Вона не є незмінною властивістю, яка переживає кожен майбутній реліз без нових вимірювань.
Розбираємо пік замовлень цілком
До акції сервіс працює, на піку росте хвіст відповіді. Трейси показують швидкий SQL і довге очікування пулу, БД зайнята звітами. Нові інстанси без бюджету лише додадуть конкуренцію. Перший експеримент обмежує звіти та повторює трафік. Нам потрібен зв'язок між втручанням і корисним результатом, а не загальне відчуття, що стало швидше.
За покращення розділяємо бюджети важливого та фонового шляху. Вхід має контрольовану відмову, черга строк, ретраї дедлайн. Загублена відповідь не створює друге замовлення. Є запас на процес і прогріта потужність до відомого піка. Доступність купується не лише кількістю контейнерів: правила тиску та повтору захищають залежності, які нові процеси не прискорять.
Наступний тест зберігає вхід за повільних відповідей, використовує великі акаунти та холодний старт. Сповільнюємо апстрим, прибираємо процес. Дивимося завершення, latency й повернення. Швидкі помилки з меншою кількістю замовлень не є кращим результатом. Потрібно бачити, яку роботу сервіс зберіг і яку свідомо відхилив.
Межу переносимо в роботу разом з умовами: операції, дані, залежності й резерв. Автоскейлінг доходить лише до видимого спільного ліміту. Наступний ріст перевіряє нове вузьке місце. План стає послідовністю вимірюваних змін, а не обіцянкою безмежних контейнерів. Кожна зміна має пояснення, чому саме вона збільшить корисну пропускну здатність.
Після акції порівнюємо прогноз і реальний потік. Якщо форма відрізнялась, уточнюємо модель та умови тесту. Це не означає переписати план під один випадок. Потрібно зрозуміти, чи повториться нова форма, який ресурс вона зачепила та скільки коштує потрібний запас. Тоді наступна подія почнеться з точнішого припущення.
Операційна інструкція пояснює, що робити за межею: зменшити фон, обмежити вхід, перевірити апстрим або додати конкретний ресурс. У кожної дії є умова та перевірка результату. Саме це робить виміряну місткість корисною для чергового. Без дій графік навантаження лише показує, що сервіс уже погано працює.