1. Про що ця лекція і чому це важливо
Уявіть, що ви зупинилися на етапі, коли GiftGenius самотньо живе на Vercel: один інстанс MCP Gateway (який одночасно реалізує MCP назовні й звертається до ваших REST‑сервісів), один бекенд для агентів — і все «якось працює». Це ще терпимо для пет‑проєкту й перших 100 користувачів.
Але щойно OpenAI додасть ваш застосунок у Store і він раптово потрапить у головну підбірку перед Різдвом, «один gateway на 3000‑му порту» перетвориться на дуже сумну історію. Почнуться черги tool‑викликів, тайм‑аути, помилки 500, падіння рейтингу в Store і листи від маркетингу в стилі: «А чому все лежало в пік продажів?».
Наше завдання в цій лекції — навчитися мислити про GiftGenius (і будь‑який застосунок ChatGPT) як про систему з багатьма однаковими інстансами за балансувальником. Також розберемо акуратні стратегії релізів і зрозуміємо, як швидко відкочуватися, якщо щось піде не так.
2. Горизонтальне масштабування і stateless‑дизайн
Почнемо з базової ідеї: якщо ваш MCP Gateway або внутрішній бекенд‑сервіс зберігає важливий стан у памʼяті конкретного процесу, нормально масштабувати його горизонтально майже неможливо.
Вертикальне vs горизонтальне масштабування
Спершу розберімося з термінами.
Вертикальне масштабування — це коли ви просто «накручуєте мʼязи» одному серверу: більше CPU, більше RAM. Це швидко (інколи й недорого на старті), але має жорстку межу. До того ж один інстанс стає single point of failure: якщо цей потужний монстр падає, падає все.
Горизонтальне масштабування — це коли ви запускаєте кілька екземплярів сервісу за балансувальником. Кожен інстанс відносно невеликий, не тримає нічого критичного в памʼяті, а стан живе у зовнішніх сховищах (Postgres, Redis, object storage). Ви можете вільно додавати й прибирати інстанси під навантаження.
Для MCP Gateway і бекенд‑сервісів (Gift REST API, Commerce REST API, Analytics Service / REST API тощо) горизонтальне масштабування фактично обовʼязкове. ChatGPT може раптово надіслати вам у рази більше трафіку (сезон, промо в Store, якийсь вірусний TikTok). Тож ви маєте просто додати інстанси, а не «молитися, щоб один сервер витримав».
Що таке stateless‑сервіс у контексті MCP Gateway і бекендів
Щоб горизонтальне масштабування працювало, сервіс має бути максимально stateless.
Stateless у нашому контексті означає:
- сервіс не зберігає в памʼяті унікальний, довготривалий користувацький стан, від якого залежить бізнес‑логіка;
- будь‑який важливий стан зберігається у зовнішній БД, черзі, кеші, S3‑подібному сховищі;
- якщо конкретний інстанс упав, інший інстанс може продовжити обслуговувати користувача, просто «підхопивши» контекст із зовнішнього сховища.
Для GiftGenius це означає, що:
- історія підборів подарунків користувача, його лайки/дизлайки й кошик лежать, наприклад, у Postgres;
- черги тривалих задач (масова генерація підбірок, розсилка підбірок електронною поштою) лежать у брокері на кшталт Redis/Cloud Queue;
- якщо є окремий сервіс для складних агентних workflow, він зберігає чекпойнти та довготривалу памʼять у своєму сховищі, а не в RAM одного процесу.
Інстанс MCP Gateway або будь‑якого бекенд‑сервісу перетворюється на «корову, а не домашнього улюбленця»: його можна безболісно зупинити й розгорнути знову, не втративши бізнес‑дані.
Міні‑приклад: перенесення стану з памʼяті у зовнішнє сховище
Уявімо, що ви колись зробили дуже простий MCP‑tool add_to_cart, який через gateway звертається до внутрішньої логіки. А та зберігає кошик у памʼяті процесу (так‑так, інколи так роблять у демках — і це нормально, доки ви розумієте, що в продакшні так не можна):
// ПОГАНО: кошик у памʼяті процесу бекенд-сервісу
const inMemoryCarts = new Map<string, string[]>();
export async function addToCart(userId: string, sku: string) {
const cart = inMemoryCarts.get(userId) ?? [];
cart.push(sku);
inMemoryCarts.set(userId, cart);
return cart;
}
Горизонтальне масштабування тут неможливе: один запит потрапить на інстанс A, інший — на інстанс B, і кошики в користувача будуть різні.
Правильний варіант — винести кошик у зовнішню БД або кеш. Умовно (сильно спрощено):
// ДОБРЕ: кошик у зовнішньому сховищі
import { db } from "./db";
export async function addToCart(userId: string, sku: string) {
await db.cartItems.insert({ userId, sku }); // спрощено
const cart = await db.cartItems.findMany({ where: { userId } });
return cart;
}
Тепер неважливо, який саме інстанс бекенд‑сервісу обробляє запит, що прийшов через gateway: кошик єдиний для всіх.
3. Балансування навантаження: як трафік потрапляє в кластери бекенд‑сервісів
Щойно у вас зʼявляється більше одного інстанса сервісу, потрібен хтось, хто розподілятиме запити між ними. Це як диспетчер замовлень у популярній піцерії: курʼєрів багато, клієнтів багато — без чіткої логіки буде хаос.
L4 vs L7, і чому нас переважно цікавить L7
Балансувальник може працювати на різних рівнях:
- L4 (TCP/UDP) просто перекидає байти від клієнта на один із бекендів, не надто розуміючи, який там протокол;
- L7 (HTTP) розуміє, що перед ним HTTP‑запит, уміє дивитися на шлях, заголовки, куки, інколи навіть на тіло.
Для архітектури застосунку ChatGPT із MCP Gateway і REST‑сервісами нам майже завжди потрібен L7‑балансувальник: усе спілкується по HTTP/SSE, і хочеться вміти маршрутизувати за шляхом, доменом, заголовками (наприклад, для canary‑релізів), а також робити перевірки стану.
Health checks і виведення «хворих» інстансів із ротації
Балансувальник має періодично перевіряти, що інстанси «живі». Найпростіший спосіб — мати GET /health або /readyz ендпойнт, який повертає 200 OK, якщо все гаразд.
У Node/TypeScript‑сервісі, який працює як MCP Gateway або бекенд, health check може виглядати так:
// apps/gateway/src/http/health.ts
import { type Request, type Response } from "express";
export function healthHandler(req: Request, res: Response) {
res.json({
status: "ok",
version: process.env.RELEASE_ID ?? "dev",
});
}
Балансувальник звертається кожні N секунд до /health. Якщо відповіді починають приходити з 5xx або за тайм‑аутом, цей інстанс прибирається з ротації, і нові запити туди більше не потрапляють.
Особливості для streaming / SSE
MCP Gateway доволі часто працює через SSE (Server‑Sent Events), особливо якщо ви використовуєте стримінг часткових результатів. Балансувальник має:
- підтримувати довготривалі HTTP‑зʼєднання;
- уміти враховувати такі зʼєднання під час вибору інстанса (деякі балансувальники зважають на кількість активних зʼєднань, а не лише на RPS).
Це важливо, тому що один «балакучий» tool‑виклик, який передає текст потоком 2 хвилини, висить як активне зʼєднання. Якщо таких зʼєднань забагато на одному інстансі, його потрібно тимчасово «розвантажувати» — надсилати нові зʼєднання на інші.
4. Кластери бекенд‑сервісів: розділяємо за задачами, а не звалюємо все в одну купу
Логічний наступний крок — перестати думати про один «великий бекенд‑сервіс» і розбити систему на кілька кластерів залежно від характеру навантаження та критичності.
Приклад архітектури GiftGenius за кластерами
Усі зібрані дані за модулем 16 рекомендують нам таку схему для GiftGenius:
| Кластер | Що робить | Характер навантаження | Особливості масштабування |
|---|---|---|---|
| A: Gift REST API / легкі інструменти | Пошук товарів, форматування списків, прості обчислення | Високий RPS, короткі відповіді (< 500 мс), мало CPU | Масштабуємо за CPU/RPS: багато дрібних інстансів |
| B: Agents / Heavy Jobs REST‑сервіс | Виклики LLM, складні workflow, генерація привітань | Низький RPS, довгі відповіді (10 с–2 хв), IO‑heavy | Масштабуємо за довжиною черги завдань: можна використовувати воркери |
| C: Commerce REST API / ACP | Checkout, інтеграція з платіжним провайдером, ACP | Критична надійність, жорсткі SLO | Окреме розгортання, повільні й обережні зміни |
По суті, це реалізація патерна bulkheads (відсіки): якщо кластер B раптово починає «палити CPU токенами» під час генерації складних текстів, кластер C з оплатою продовжить працювати. У нього свій пул ресурсів і своє масштабування.
Як це виглядає через Gateway
MCP Gateway, описаний у першій лекції модуля, бачить увесь вхідний MCP‑трафік і маршрутизує його за бекенд‑кластерами. Приблизно так:
- tool‑виклики list_gifts, suggest_gifts → кластер A (Gift REST API);
- tool‑виклики generate_greeting_card або складні agent‑workflow → кластер B (Agents REST‑сервіс або воркери);
- інструменти create_order, confirm_payment → кластер C (Commerce REST API).
За цим уже може стояти один спільний балансувальник або кілька балансувальників (наприклад, окремий L7‑LB перед commerce, щоб ще сильніше все ізолювати).
Можна зобразити загальну картинку:
flowchart LR
ChatGPT((ChatGPT))
GW[MCP Gateway]
LBA[LB Gift API Cluster A]
LBB[LB Agents/Workers Cluster B]
LBC[LB Commerce API Cluster C]
A1[Gift REST API A-1]
A2[Gift REST API A-2]
B1[Agents Service B-1]
B2[Agents Service B-2]
C1[Commerce REST API C-1]
C2[Commerce REST API C-2]
ChatGPT --> GW
GW -->|tools: gifts| LBA
GW -->|agents workflows| LBB
GW -->|commerce| LBC
LBA --> A1
LBA --> A2
LBB --> B1
LBB --> B2
LBC --> C1
LBC --> C2
Схема дещо ідеалізована, але показує головний принцип: різні типи навантаження — різні бекенд‑кластери за одним MCP Gateway.
5. Стратегії розгортання: навіщо потрібні blue/green і canary
Тепер перейдемо до того, як оновлювати все це господарство так, щоб користувачі нічого не помічали, а ви могли спокійно спати вночі.
Антиприклад: розгортання «поверх продакшена»
Найпростіша й водночас найнебезпечніша стратегія: ви берете чинний кластер (наприклад, кластер Gift REST API A), запускаєте новий образ поверх старого, підміняєте контейнери або перезапускаєте процеси.
У чому проблеми:
- поки частина інстансів уже нова, а частина — стара, система може поводитися непередбачувано (особливо якщо змінювалася схема БД);
- якщо щось піде не так, відкат — це нове розгортання «як було», яке може займати хвилини;
- у момент деплою цілком можливий короткий простій, коли жоден інстанс ще не запустився.
У Kubernetes і PaaS це трохи помʼякшується rolling‑оновленнями, але загальна ідея та сама: без чіткої стратегії у вас багато «сірої зони», де різні версії коду одночасно обробляють трафік.
Blue/Green‑розгортання: два середовища і миттєве перемикання
Blue/Green — це підхід, за якого у вас одночасно існують два майже ідентичні оточення: Blue (поточний продакшн) і Green (нова версія).
Схематично процес виглядає так:
- Розгортаєте нову версію (v2) у Green‑оточенні: це такий самий набір gateway + бекенд‑кластерів, тільки поки без реального трафіку.
- Проганяєте на Green усі потрібні тести: автотести, smoke‑сценарії, ручні перевірки через ChatGPT Dev Mode.
- У момент релізу перемикаєте балансувальник/маршрутизацію так, щоб 100 % бойового трафіку йшло в Green.
- Blue продовжує жити поруч як «запасний аеродром». Якщо щось піде не так, перемикаєте трафік назад за лічені секунди.
Для GiftGenius це може виглядати так: у вас є mcp-gateway-blue.example.com і mcp-gateway-green.example.com. У продакшні застосунок ChatGPT «дивиться» на офіційний MCP‑endpoint (gateway), а під час релізу ви змінюєте конфіг DNS/LB так, щоб доменне імʼя mcp-gateway.example.com вказувало вже на green.
Плюси:
- миттєвий перемикач «туди‑сюди»;
- будь‑яку проблему можна розбирати вже після відкату;
- немає стану «пів кластера нова, пів кластера стара».
Мінуси:
На час релізу потрібно тримати два повних оточення, тобто оплачувати ресурси ×2. Тому таку стратегію найчастіше застосовують для критичних бекенд‑сервісів — наприклад, commerce‑кластера C і самого MCP Gateway, де ламати checkout і вхідну точку не можна за жодних обставин.
Canary‑релізи: маленька «канарка» у вугільній шахті
Canary‑реліз — економніший варіант: ви не піднімаєте два повних продакшни, а викочуєте нову версію поступово, на невелику частку трафіку, й уважно за нею спостерігаєте.
Приблизний сценарій:
- Деплоїте версію v2 кластера Gift REST API A у той самий пул або в окремий невеликий канарковий пул.
- Налаштовуєте балансувальник або MCP Gateway так, щоб, скажімо, 1 % tool‑викликів, повʼязаних із подарунками, йшло на v2, а 99 % — на v1.
- Дивитеся на метрики: частку помилок (error rate), затримку (latency), специфічні бізнес‑метрики (конверсія, успішні checkout‑и).
- Якщо все добре — поступово збільшуєте частку: 1 % → 5 % → 10 % → 50 % → 100 %. Якщо погано — терміново відкочуєте.
У контексті застосунків ChatGPT canary особливо корисний не лише для коду, а й для експериментів із промптами: нова версія system‑promptʼа для agent‑сервісу може радикально змінити поведінку, тож краще спочатку перевірити її на невеликій вибірці користувачів.
Gateway або LB можуть визначати, який запит вважати «канарковим», за різними ознаками:
- випадково (наприклад, 1 % усіх запитів);
- за userId (частина користувачів потрапляє в експеримент назавжди);
- за спеціальним заголовком або cookie (для внутрішнього тестування).
Невеликий приклад логіки маршрутизації в псевдо‑TypeScript (для ілюстрації ідеї в gateway):
// Псевдокод у Gateway: simple random canary 5%
function routeToGiftBackendCluster(ctx: { userId?: string | null }) {
const rnd = Math.random();
if (rnd < 0.05) {
return "gift-api-v2"; // canary
}
return "gift-api-v1"; // stable
}
На практиці ви, звісно, не робитимете це через Math.random() у runtime‑коді, а винесете правила в конфіг/feature flags. Але логіка дуже схожа: частина трафіку йде на canary‑версію бекенд‑сервісу, решта — на стабільну.
6. Rollback як обовʼязкова частина стратегії
Колись давно я засвоїв хороше правило: відкат має бути швидшим за фікс.
Це означає, що якщо після релізу посипалися помилки й користувачі пишуть «все ламається», не треба героїчно лагодити баг у продакшні. Треба натиснути велику червону кнопку «відкотитися».
У контексті платформ на кшталт Vercel (на яких ми вже розгортали Next.js‑частину GiftGenius) це дуже природно: кожен деплой — immutable артефакт, і Vercel дозволяє швидко відкотитися до попереднього.
Для MCP Gateway і бекенд‑кластерів, розгорнутих у Kubernetes або іншому оркестраторі, цю роль виконує kubectl rollout undo: ви відкочуєтеся до попереднього набору podʼів і образів.
Головне — логувати й показувати версію, яка зараз обслуговує трафік. Наприклад, можна:
- додавати version у /health та інші діагностичні ендпойнти (ми вже це робили вище);
- прокидати ідентифікатор релізу через заголовки в логи (наприклад, X-Release-Id).
Міні‑приклад: Next.js‑API‑route, який віддає версію збірки для інспекції застосунку ChatGPT всередині віджета:
// apps/web/app/api/version/route.ts
export async function GET() {
return Response.json({
version: process.env.RELEASE_ID ?? "dev",
builtAt: process.env.BUILT_AT ?? "unknown",
});
}
Такий ендпойнт корисний і для відладки: ви можете запитувати в прод‑інстанса, яка саме версія зараз працює, і не гадати: «А чи точно викотився останній білд?».
7. Capacity planning: скільки інстансів потрібно під GiftGenius
Ми вже обговорили, як безпечно викочувати нові версії (blue/green, canary) і швидко відкочуватися у разі проблем. Залишилося практичне питання: скільки взагалі інстансів і яких кластерів тримати в продакшні, щоб усе це витримало реальний трафік і не розорило вас?
Без фанатизму у формули, але трохи порахувати доведеться. Масштабування слід повʼязувати з навантаженням і економікою: скільки запитів на день/секунду, скільки важких LLM‑викликів — і скільки все це коштує.
Для простоти можна мислити порядками:
- за 10 тис. запитів на день до GiftGenius (приблизно 0,1 RPS у середньому) ви легко проживете на одному‑двох інстансах MCP Gateway і парі інстансів Gift REST API/Agents‑воркерів;
- за 100 тис. запитів на день (1–2 RPS у середньому, а в піку — більше) уже варто мати 3–5 інстансів gateway + кластера Gift REST API, окремий кластер B для важких агентів і виділений commerce‑кластер;
- за 1 млн запитів на день (десятки RPS, пікові навантаження у свята) вам точно знадобляться кластери, виділені ресурси під LLM‑агентів, агресивний кеш і edge‑шар (про нього — в окремій лекції).
Це не суворі числа, а спосіб привчити себе оцінювати порядок навантаження й думати наперед: де вузькі місця, як ви масштабуватиметеся і скільки це коштуватиме.
Для GiftGenius особливо важливо готуватися до свят: Новий рік, Різдво, День святого Валентина, Чорна пʼятниця. Навантаження може зрости в рази, і вам хотілося б, щоб система це витримала.
8. Практичний міні‑приклад: еволюція розгортання GiftGenius
Щоб зібрати все докупи, намалюймо просту еволюцію розгортання GiftGenius.
Тут ми послідовно застосуємо все, про що говорили вище: stateless‑дизайн gateway і бекенд‑сервісів, балансування навантаження, окремі кластери та стратегії релізів (blue/green, canary).
Базовий рівень: один gateway + бекенд на Vercel/Kubernetes
У якийсь момент курсу ви вже це зробили: один Next.js‑додаток з Apps SDK на Vercel, у якому живе і MCP‑endpoint, і проста бекенд‑логіка (Gift/Commerce) в одному сервісі. Усе доволі монолітно.
Плюси зрозумілі: просто, дешево, мало місць, де можна помилитися.
Мінус рівно один, але критичний: це ніяк не масштабується під серйозний трафік і погано переносить оновлення.
Рівень 2: окремий MCP Gateway + кілька бекенд‑кластерів
Наступний крок:
- виносите MCP Gateway в окремий сервіс (Node/Go/NGINX+Lua — не має значення);
- запускаєте кілька інстансів Gift REST API (кластер A) і кілька воркерів/сервісів для агентів (кластер B);
- для commerce виділяєте окремий сервіс (кластер C), можливо — на окремій базі/інфраструктурі.
Уже тут вмикається класичне L7‑балансування, перевірки стану і, за можливості, горизонтальне масштабування.
Рівень 3: стратегії релізів
На цьому рівні ви додаєте:
- Blue/Green для commerce‑кластера C (і, за бажання, для MCP Gateway), щоб checkout і авторизація були максимально стабільними;
- Canary‑релізи для кластерів Gift REST API й agent‑сервісу, щоб спокійно експериментувати з новими версіями tool‑ів і агентів без ризику «покласти» весь продакшн.
Схематично:
flowchart LR
ChatGPT((ChatGPT))
GWBlue[Gateway Blue]
GWGreen[Gateway Green]
LB[Traffic Switch]
subgraph Prod
LB --> GWBlue
LB -.canary,% .-> GWGreen
end
ChatGPT --> LB
У реальності може бути трохи складніше (окремий Blue/Green лише для commerce, canary — тільки для gift‑кластерів), але ідею це ілюструє: ви завжди знаєте, яка версія куди йде. При цьому для ChatGPT усе, як і раніше, виглядає як одна MCP‑точка входу (gateway).
9. Невеликі фрагменти коду для версіонування і діагностики
Ми вже бачили health‑ендпойнт і /api/version. Додамо ще один приклад: як можна логувати версію і кластер в обробнику MCP‑toolʼа на боці gateway, щоб потім легко «зводити» метрики.
Уявімо tool suggest_gifts, який реалізований як REST‑ендпойнт у Gift REST API і викликається через gateway:
import { type McpToolHandler } from "@modelcontextprotocol/sdk";
export const suggestGifts: McpToolHandler<{
occasion: string;
budget: number;
}> = async ({ input, meta }) => {
const releaseId = process.env.RELEASE_ID ?? "dev";
const clusterId = process.env.CLUSTER_ID ?? "gift-api-A";
console.log("[suggest_gifts]", {
releaseId,
clusterId,
userId: meta.userId,
occasion: input.occasion,
});
// тут MCP Gateway за таблицею маршрутизації викликає Gift REST API,
// а сам інструмент лишається тонкою обгорткою над REST-викликом
return {
content: [{ type: "text", text: "Gift ideas..." }],
};
};
Тут ми:
- читаємо RELEASE_ID і CLUSTER_ID з env;
- пишемо їх у структуровані логи;
- далі їх легко використовувати для аналізу: «На якій версії/кластері в нас зараз сиплеться більше помилок?».
З погляду застосунку ChatGPT це взагалі прозоро, а для вас як розробника — великий плюс. Особливо в поєднанні з canary/blue‑green.
10. Типові помилки під час масштабування і розгортання застосунку ChatGPT
Помилка №1: зберігати стан сесії/користувача в памʼяті gateway або бекенд‑процесу.
Такий підхід убиває горизонтальне масштабування: щойно зʼявляється другий інстанс, стан «розшаровується» між ними. Особливо небезпечно зберігати в памʼяті кошик, результати пошуку або прогрес workflow. Усе це має жити у зовнішньому сховищі — БД, кеші або спеціалізованому сховищі для стану агента.
Помилка №2: думати, що «одного потужного сервера» достатньо.
Вертикальне масштабування зручне на старті, але погано працює за реального зростання: є фізична межа машини, один процес стає single point of failure, а ChatGPT може принести непередбачуваний сплеск трафіку. Для MCP Gateway і бекенд‑кластерів майже завжди потрібні stateless‑дизайн і кілька інстансів за балансувальником.
Помилка №3: викочувати нові версії «поверх продакшена» без чіткої стратегії.
Якщо ви просто оновлюєте контейнери/процеси в бойовому кластері, отримуєте проміжний стан, де частина трафіку йде на стару версію, а частина — на нову. А у випадку помилки відкат перетворюється на «перерозгорнути ще раз». Значно надійніше тримати або два оточення (blue/green), або принаймні окрему canary‑версію бекенд‑сервісу, куди йде мала частка трафіку.
Помилка №4: відсутність швидкого rollback‑плану.
Поганий сценарій: реліз пройшов, метрики «червоні», користувачі скаржаться, а ви лише починаєте думати, як відкотитися. Правильний сценарій: заздалегідь підготовлена можливість миттєвого відкату (blue/green‑перемикач, rollout undo, Vercel rollback), зрозумілі ідентифікатори версій у логах і health‑ендпойнтах, і жорстке правило «відкотитися спочатку, розбиратися потім».
Помилка №5: один спільний кластер «на все» без поділу за видами навантаження.
Якщо генерація вітальних текстів (LLM‑агенти) і checkout живуть в одному кластері, будь‑яка проблема на боці моделей (затримки, тайм‑аути, зростання токенів) може «покласти» й оплату. Поділ на кластери за типами завдань (Gift REST API / легкі інструменти, Agents‑heavy сервіс, Commerce REST API) та окремі ліміти/ресурси для кожного кластера — важливий крок до стійкості.
Помилка №6: відсутність звʼязку між архітектурою й економікою.
Легко захопитися ідеєю «А давайте ще піднімемо пару нод», забувши, що кожен LLM‑виклик і кожен інстанс коштують грошей. Без найпростішого capacity planningʼу (оцінки навантажень і вартості) можна або недомасштабуватися й «упустити» продакшн, або перемасштабуватися і втратити маржинальність. Тут корисно повʼязувати кількість запитів, відсоток важких LLM‑операцій і вартість хостингу з бізнес‑метриками застосунку.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ