1. Po co w ogóle chronić perymetr ChatGPT App
W klasycznej aplikacji webowej użytkownik — to przeglądarka, która dość przewidywalnie uderza w wasze endpointy. W świecie ChatGPT Apps pojawia się nowy typ klienta: LLM, który sam decyduje, kiedy i jakie narzędzia wywołać.
Model może:
- w jednej rozmowie kilka razy z rzędu wywołać ten sam tool;
- eksperymentować: „a co jeśli jeszcze raz zawołać suggest_gifts z nieco innymi parametrami?”;
- pracować równolegle dla setek użytkowników.
Dodaj do tego możliwe boty, skrypty testowe, błędy we własnym kodzie (na przykład nieskończona pętla, która stale wyzwala tool‑call) i masz niemal idealny przepis na DoS z dobrej woli.
Wisienka na torcie — koszt. Każdy tool‑call może:
- ciągnąć zewnętrzne płatne API (kurierzy, płatności, katalogi),
- wywoływać inne LLM (na przykład wyszukiwanie RAG),
- uruchamiać ciężkie zadania w tle.
Bez ograniczeń i ochrony perymetru jeden „nieudany” klient może:
- położyć wszystkie wasze backend‑serwisy za gateway (Gift API, Commerce API itd.),
- wybić limity zewnętrznych API,
- i wyraźnie podpalić budżet na modele.
Celem tego wykładu jest pokazanie, jak gateway/proxy + rate limiting + kolejki + backpressure zamieniają ten potencjalny kataklizm w system, nad którym panujemy.
Insight
Platforma ChatGPT nie zapewnia w ogóle żadnych mechanizmów ochrony twojego serwera MCP przed ruchem z zewnątrz. Dowolny klient internetowy może wysyłać do niego żądania, włącznie z narzędziami w rodzaju MCP Jam.
Wszystko, co ChatGPT może ci zaproponować, to ograniczenie ruchu przychodzącego według adresów IP, konfigurując reverse proxy (na przykład NGINX) do pracy z allowlist. Jeśli filtracja IP nie jest skonfigurowana, twój serwer MCP pozostaje całkowicie otwarty, co jest niebezpieczne. Ani dla ciebie, ani dla twoich użytkowników.
2. Proxy/Gateway jako „tarcza” przed backend‑serwisami i agentami
Najpierw przypomnijmy sobie obrazek, ale teraz przez pryzmat ochrony.
Wyobraźmy sobie typowy schemat:
flowchart LR
ChatGPT["ChatGPT / widżet"]
--> GW["MCP Gateway (Auth, Rate Limit, Logs)"]
GW --> GiftAPI["Gift REST API (dobór prezentów)"]
GW --> CommerceAPI["Commerce REST API (checkout, ACP)"]
GW --> Analytics["Analytics Service / REST API"]
GW --> Queue["Kolejka zadań"]
Queue --> Worker["Background workers"]
Gateway stoi między światem zewnętrznym (ChatGPT, webhooks, klienci testowi) a całą resztą. On:
- widzi absolutnie wszystkie żądania przychodzące;
- jako pierwszy sprawdza token i format żądania;
- umie odrzucić oczywiste bzdury (podejrzany host, dziwna path, zbyt duże body);
- podejmuje decyzję, do którego wewnętrznego serwisu REST/HTTP w ogóle warto wysłać żądanie.
Na tym poziomie pojawiają się też:
- rate limiting — ograniczamy, ile żądań można wykonać w przedziale czasu;
- najprostszy backpressure — odmawiamy, jeśli serwisy pod spodem już się dławią;
- przejście na asynchroniczność — ciężkie rzeczy od razu do kolejki, odpowiedź klientowi: „przyjęto, czekamy”.
A więc gateway to nie tylko „router”, ale i „kamizelka kuloodporna”. Najważniejsze — nie zamieniać go w „monolit całego biznesu”, o czym mówiliśmy w poprzednim wykładzie.
3. Jakie strumienie ruchu należy kontrolować
W ekosystemie ChatGPT App zwykle są trzy główne typy ruchu, które interesują nas z punktu widzenia ograniczeń i ochrony.
Po pierwsze, to MCP tool‑calls od ChatGPT. To wszystko, co przychodzi przez protokół MCP: wywołania suggest_gifts, get_product_details, create_checkout_session i innych narzędzi. Model może generować je całkiem żwawo, zwłaszcza gdy pod spodem są jeszcze Agents.
Po drugie, to żądania wychodzące od naszych backendów do zewnętrznych API. Wewnątrz serwisu możemy mieć własne rate limits na systemy zewnętrzne: katalogi, logistyka, płatności. Naruszyć je — to prosta droga do blokady, kar lub spadku jakości.
Po trzecie, to webhooki przychodzące — powiadomienia od ACP, dostawców płatności (Stripe itd.), kurierów. Przychodzą niezależnie od aktywności użytkowników. Jeśli nasz endpoint zamula lub odpowiada błędem, system zewnętrzny zacznie wykonywać ponowne próby (retries) i może zafundować „sztorm” z powtarzających się powiadomień.
Dla GiftGenius wygląda to tak:
- użytkownik i model aktywnie wywołują suggest_gifts i find_similar_gifts;
- checkout‑tool wywołuje ACP/komercyjny backend;
- po opłacie bramka płatności przysyła webhooks payment.succeeded / payment.failed.
Wszystkie te strumienie zbiegają się w jednym punkcie — Gateway, a więc właśnie tam rozsądnie jest ustawić „liczniki, filtry i korki”.
4. Rate limiting: podstawowa ochrona i oszczędność pieniędzy
Co to jest rate limiting w naszym kontekście
Rate limiting to mechanizm, który ogranicza liczbę żądań od konkretnego klienta na jednostkę czasu. Pomysł stary jak Internet, ale w kontekście ChatGPT Apps od razu rozwiązuje trzy zadania:
- nie pozwala jednemu klientowi (albo bugowi) położyć twoich serwisów;
- pomaga dotrzymać limitów zewnętrznych API;
- chroni twój portfel przed niekontrolowanymi wywołaniami modeli.
Klasyczne algorytmy:
- stałe okno (Fixed Window),
- okno przesuwne (Sliding Window),
- wiadro z tokenami (Token Bucket),
dla nas ważne jest raczej koncepcyjnie: „w ciągu minuty nie więcej niż N żądań”, „każde żądanie zjada token, tokeny uzupełniają się z prędkością X na sekundę” itd. Implementację zwykle bierze na siebie biblioteka lub API Gateway.
Gdzie ustawiać limity
Limity można ustawiać na różnych poziomach.
Na poziomie reverse proxy (Nginx, Cloudflare, AWS API Gateway) wygodnie jest:
- odcinać najbardziej dziki ruch według IP;
- ograniczać rozmiar ciała żądania;
- bronić się przed prostymi wzorcami DDoS.
Na poziomie MCP Gateway (aplikacja) warto robić bardziej „sensowny” rate limiting:
- po użytkowniku (userId z tokena),
- po organizacji (tenantId),
- po typie operacji (na przykład create_checkout_session limitujemy twardo, search — łagodniej),
- po źródle (webhook vs tool‑call).
Osobno można dodawać limity już w samych mikroserwisach dla szczególnie drogich operacji, ale to kolejny poziom szczegółowości.
Jak wybierać klucz dla limitów
Najczęstszy błąd — limitować po adresie IP. W przypadku ChatGPT to dość bezużyteczne:
- wszystkie żądania mogą iść z jednego zakresu OpenAI,
- różni użytkownicy będą „siedzieć” za tym samym IP.
O wiele ciekawsze dla nas jest:
- userId — konkretny użytkownik w twojej aplikacji;
- tenantId — organizacja (jeśli robisz B2B i jeden czat jest używany przez wielu pracowników);
- API‑token lub clientId, jeśli masz kilka integracji.
W GiftGenius zazwyczaj wystarczy userId + tenantId, wyciągnięte z tokena, który ChatGPT przekazuje w wywołaniach MCP.
Prosta implementacja rate limiting w TypeScript
Załóżmy, że mamy mały MCP Gateway na Expressie. Dodajmy najprostszy rate limiting: nie więcej niż 30 tool‑calls w minutę na jednego użytkownika.
// Prymitywny rate limiting: N żądań na minutę na userId
const WINDOW_MS = 60_000;
const MAX = 30;
const hits = new Map<string, { ts: number; count: number }>();
function rateLimit(req: Request, res: Response, next: NextFunction) {
const userId = (req.headers["x-user-id"] as string) ?? "anonymous";
const now = Date.now();
const rec = hits.get(userId) ?? { ts: now, count: 0 };
if (now - rec.ts > WINDOW_MS) { // Okno "przeterminowane" — zaczynamy od nowa
rec.ts = now;
rec.count = 0;
}
rec.count += 1;
hits.set(userId, rec);
if (rec.count > MAX) {
return res.status(429).json({
error: "rate_limit_exceeded",
retryAfterSec: 60,
message: "Too many tool calls, please retry later."
});
}
next();
}
A teraz użyjmy go w trasie MCP:
// Stosujemy middleware do wszystkich MCP tool-calls
app.post("/mcp/tools/call", rateLimit, async (req, res) => {
const result = await callBackendForTool(req.body); // REST-wywołanie do Gift/Commerce/Analytics API
res.json(result);
});
Kluczowe momenty:
- zwracamy sensowny błąd (error: "rate_limit_exceeded"), a nie po prostu 500;
- model będzie w stanie przeczytać ten błąd, zrozumieć, co zaszło, i poprawnie wyjaśnić to użytkownikowi, zamiast zaczynać halucynować.
W prawdziwej produkcji liczniki oczywiście nie żyją w pamięci jednego procesu, tylko w Redisie lub innym wspólnym magazynie, żeby wszystko działało w klastrze. Ale do zrozumienia zasady to wystarczy.
Rate limiting i limity na poziomie gatewaya chronią nas przed lawiną żądań, ale nie rozwiązują innego problemu — pojedyncze operacje wciąż mogą być bardzo ciężkie i długo trwać. Tu synchronizacja HTTP nie wystarcza i na scenę wchodzą kolejki oraz zadania asynchroniczne.
5. Kolejki i zadania asynchroniczne: gdy synchronicznie już się nie da
Problem timeoutów ChatGPT
Nawet jeśli ostrożnie ustawisz rate limiting, ChatGPT (i ogólnie klienci HTTP) nie lubią, gdy odpowiedź przychodzi bardzo długo. Platforma ogranicza czas wykonania tool‑calla i jeśli będziesz czekać, aż skończy działać jakiś „super‑rekomendacyjny” algorytm, to:
- użytkownik zobaczy wieczny spinner;
- platforma przerwie żądanie po timeoutcie;
- model uzna, że „coś poszło nie tak” i zacznie wymyślać wyjaśnienia.
Rozwiązanie: przenieść ciężkie operacje do trybu asynchronicznego. Klasyczny wzorzec:
- Gateway przyjmuje żądanie.
- Wstawia zadanie do kolejki.
- Od razu zwraca odpowiedź 202 Accepted z jobId.
- Oddzielny worker pobiera zadania z kolejki i przetwarza.
- Klient (nasz widżet albo nawet ChatGPT przez dodatkowy tool) okresowo pyta o status po jobId albo dostaje powiadomienie przez zdarzenie MCP.
W terminologii ChatGPT App wygląda to zwykle jak dwa narzędzia: pierwszy tool przyjmuje żądanie, wstawia zadanie do kolejki i zwraca jobId, drugi — pozwala modelowi lub widżetowi po tym jobId sprawdzać status i pobierać wynik. Dodatkowo te same zdarzenia o postępie można dublować przez notyfikacje MCP.
Mini‑kolejka dla GiftGenius (przykład kodu)
Załóżmy, że mamy ciężki instrument generate_large_gift_report, który może działać dziesiątki sekund. W prawdziwej aplikacji zwracałby tylko jobId, a osobny tool get_report_status pozwalałby modelowi lub widżetowi po tym jobId sprawdzać stan i pobierać wynik. Na poziomie Gateway zrobimy dla niego oddzielny endpoint z kolejką.
type Job = { id: string; payload: any };
const queue: Job[] = [];
const MAX_QUEUE = 100;
app.post("/mcp/tools/generate_report", (req, res) => {
if (queue.length >= MAX_QUEUE) {
return res.status(503).json({
error: "system_busy",
message: "System is busy, please retry later."
});
}
const job: Job = { id: crypto.randomUUID(), payload: req.body };
queue.push(job);
res.status(202).json({ jobId: job.id, status: "accepted" });
});
I prymitywny worker, który co 200 ms bierze jedno zadanie:
async function processJob(job: Job) {
// Tutaj wywołujemy prawdziwy serwis backendowy lub workflow agentowy przez REST
await handleHeavyGiftReport(job.payload);
}
setInterval(async () => {
const job = queue.shift();
if (!job) return;
await processJob(job);
}, 200);
Jasne, że to bardzo uproszczony przykład:
- w prawdziwym życiu kolejka żyje w Redisie, SQS, Kafce itd.;
- status zadania jest przechowywany gdzie indziej, żeby można go było odpytać;
- workerów jest zwykle kilka.
Ale koncepcja jest już jasna: Gateway nie trzyma żądania otwartego, aż wszystko się wykona. Przyjmuje, przekazuje do realizacji i odpowiada szybko.
6. Backpressure: jak nie utonąć we własnej kolejce
Czym backpressure różni się od rate limiting
Rate limiting odpowiada przede wszystkim na pytanie: „ile żądań może wykonywać jeden klient w przedziale czasu?”. To ochrona przed „jednym zbyt aktywnym użytkownikiem” albo bugiem po stronie konkretnego klienta.
Backpressure zaś mówi: „a ile łącznie zadań/żądań nasz system jest w stanie strawić jednocześnie, nie rozsypując się?”. To już kwestia całkowitej objętości obciążenia, niezależnie od tego, skąd przyszło.
Przykład:
- rate limiting: „użytkownik nie może wywoływać suggest_gifts częściej niż 30 razy na minutę”;
- backpressure: „w kolejce nie może być więcej niż 100 niewykonanych zadań, inaczej zaczynamy odmawiać wszystkim nowym żądaniom”.
W idealnym przypadku te mechanizmy się uzupełniają: rate limit trzyma klientów w ryzach, backpressure ratuje system, gdy i tak wpadło bardzo dużo ruchu.
Prosta implementacja ograniczenia aktywnych zadań
Jeden z najprostszych wariantów backpressure — ograniczyć liczbę wywołań aktywnych pod spodem. Na przykład: nie trzymać jednocześnie więcej niż 50 aktywnych tool‑calls do konkretnego backend‑/REST‑serwisu (Gift API, Commerce API itd.).
let activeCalls = 0;
const MAX_ACTIVE = 50;
app.post("/mcp/tools/call", async (req, res) => {
if (activeCalls >= MAX_ACTIVE) {
return res.status(429).json({
error: "gateway_overloaded",
message: "Gateway is temporarily overloaded, please retry later."
});
}
activeCalls += 1;
try {
const result = await callBackendForTool(req.body); // REST-wywołanie do Gift/Commerce/Analytics API
res.json(result);
} catch (err) {
console.error("Tool call error", err);
res.status(500).json({ error: "internal_error" });
} finally {
activeCalls -= 1;
}
});
Co się tutaj dzieje:
- dopóki liczba jednocześnie wykonywanych żądań jest mniejsza niż MAX_ACTIVE, przepuszczamy nowy call;
- jeśli limit został wyczerpany, od razu odpowiadamy sensownym błędem;
- ważne jest koniecznie zmniejszać licznik w finally, aby przy błędach nie stracić „slotów”.
To właśnie najprostszy backpressure: szczerze mówimy klientowi: „nie mogę teraz, spróbuj później”, zamiast bezmyślnie przyjmować wszystko i umierać.
W dalszej kolejności można:
- ustawiać różne MAX_ACTIVE dla różnych typów operacji (na przykład checkout niemal zawsze przepuszczać, a generowanie raportów ograniczać bardziej),
- przełączać limity dynamicznie, w zależności od metryk obciążenia.
7. Webhooki i „sztormy”: ochrona zdarzeń przychodzących
Do tej pory patrzyliśmy głównie na żądania, które inicjujemy my lub ChatGPT (tool‑calls, żądania wychodzące, async‑joby). Ale w życiu jest jeszcze jedno ważne źródło obciążenia na Gateway — webhooki przychodzące z systemów zewnętrznych.
Webhooki to druga strona medalu: jeśli tool‑calls inicjujemy my (przez model), to webhooki inicjuje serwis zewnętrzny. To właśnie ten trzeci typ ruchu z sekcji 4, którego nie kontrolujemy w czasie i częstotliwości, ale musimy umieć go strawić bez padów. Płatności, ACP, logistyka — wszystkie one wysyłają powiadomienia (webhooki) na nasz endpoint przy każdej istotnej zmianie: „płatność przeszła”, „zamówienie utworzone”, „dostawa zaktualizowała status”.
Problemy zaczynają się, gdy:
- nasz endpoint odpowiada wolno;
- odpowiada błędem;
- okresowo jest niedostępny.
Wtedy serwis zewnętrzny, zgodnie z dobrymi praktykami, zaczyna wykonywać ponowne próby (retries). I jeśli pech dopisze, dostaniesz „sztorm” webhooków — dziesiątki albo setki powtórzonych zdarzeń, które próbują się dobić do ciebie za wszelką cenę.
Żeby nie zginąć od takiej troski, na poziomie Gateway warto:
- Limitować webhooki przychodzące według źródła: na przykład „nie więcej niż 10 zdarzeń na minutę na jeden event_type od konkretnego dostawcy”.
- Sprawdzać podpis przed parsowaniem JSON: podpis HMAC lub analogiczny mechanizm pozwala odrzucać fałszywe żądania.
- Robić obsługę zdarzeń idempotentną: po event_id lub podobnym polu, aby powtórzenia nie prowadziły do duplikatów zamówień czy płatności.
- Przy silnym sztormie włączać dodatkowy backpressure: tymczasowo odpowiadać „503: spróbuj później”, jeśli serwisy downstream nie nadążają.
Najprostszy przykład (idea, a nie kod produkcyjny):
app.post("/webhooks/stripe", rateLimitWebhook, (req, res) => {
const sig = req.headers["stripe-signature"] as string;
if (!isValidSignature(req.rawBody, sig)) {
return res.status(400).send("Invalid signature");
}
const event = JSON.parse(req.body.toString());
if (isAlreadyProcessed(event.id)) {
return res.json({ received: true }); // idempotentność
}
handleStripeEvent(event);
res.json({ received: true });
});
Tu na poziomie Gateway:
- stosujemy oddzielną politykę rate limiting dla webhooków;
- weryfikujemy podpis, zanim zaufamy treści;
- bronimy się przed duplikatami przez isAlreadyProcessed.
8. Zastosowanie w GiftGenius: przykład polityki limitów i kolejek
Oderwijmy się od abstrakcji i zobaczmy, jak to może wyglądać dla naszego szkoleniowego GiftGenius.
Wyobraźmy sobie trzy kluczowe scenariusze:
- Wyszukiwanie prezentów (suggest_gifts, find_similar_gifts).
- Utworzenie zamówienia / checkout (create_checkout_session, confirm_order).
- Obsługa webhooków od dostawcy płatności i ACP.
Dla każdego scenariusza logicznie określamy:
- po jakim kluczu liczymy limit;
- ile żądań na minutę pozwalamy;
- co robimy przy przekroczeniu.
Na przykład:
| Scenariusz | Klucz limitu | Limit na minutę | Zachowanie przy przekroczeniu |
|---|---|---|---|
| Wyszukiwanie prezentów | userId | 30 | 429 + podpowiedź „zawęź parametry wyszukiwania” |
| Utworzenie zamówienia | userId + tenantId | 5 | 429 + tekst „zbyt wiele prób, sprawdź zamówienia” |
| Webhooki przychodzące | provider + eventType | 10 | 429/503, log, możliwa degradacja |
Dla webhooków zwykle sensowniej jest limitować po kombinacji „dostawca + typ zdarzenia”, a duplikaty odcinać osobnym mechanizmem idempotentności po event_id.
W kodzie zamienia się to w różne middleware’y: rateLimitSearch, rateLimitCheckout, rateLimitWebhook.
Dla ciężkich operacji, takich jak „wygeneruj duży raport PDF o prezentach z całego roku”, używamy kolejki i wzorca asynchronicznego, który pokazaliśmy wyżej. Gateway w takim przypadku:
- przyjmuje żądanie od ChatGPT;
- wstawia zadanie do kolejki;
- zwraca jobId i wskazówkę dla modelu, jak pobrać status;
- ogranicza rozmiar kolejki (backpressure), żeby nie przepełnić systemu.
Warto pamiętać: i rate limiting, i backpressure — to nie tylko bezpieczeństwo i niezawodność, ale też UX. O wiele przyjemniej usłyszeć od asystenta: „Serwis jest teraz przeciążony, spróbujmy za minutę”, niż siedzieć ze spinnerem do timeoutu albo zobaczyć „Internal Server Error”.
9. Mini‑praktykum: dodajemy ochronę do naszego MCP Gateway
Żeby materiał nie został teorią, złóżmy mini‑praktykum, które możesz zrealizować w swoim projekcie szkoleniowym.
Rate limiting dla wszystkich MCP tool‑calls
Dodaj middleware rateLimit (jak wyżej) i podłącz go do /mcp/tools/call. Na początek można przyjąć bardzo prosty limit: 30 żądań na minutę na userId. Potem poeksperymentuj:
- zmniejsz limit i zobacz, jak twój App i model na to reagują;
- zrób różne limity dla różnych typów tools, przekazując na przykład toolName do middleware.
Najprostszy backpressure według aktywnych wywołań
Dodaj licznik activeCalls i ograniczenie MAX_ACTIVE. Spróbuj zasymulować obciążenie (na przykład skryptem, który wysyła paczkę żądań) i zobacz, w którym momencie Gateway zacznie odpowiadać gateway_overloaded.
Tu ważne jest właśnie zachowanie: nie czekasz, aż wszystko padnie, tylko odmawiasz brania nowych zadań, szczerze mówiąc klientowi, że teraz jest zbyt gorąco.
Kolejka dla ciężkiego narzędzia
Wybierz jedną ciężką operację (albo sztucznie zrób ją „ciężką” — wstawiając setTimeout/długi fetch) i przenieś ją na wzorzec „kolejka + jobId”. Minimalnie:
- endpoint POST /mcp/tools/generate_report — wstawia zadanie do kolejki i zwraca jobId;
- endpoint GET /jobs/:id — zwraca status (pending, done, error, plus ewentualnie wynik);
- worker, który co X milisekund woła processJob.
To wystarczy, żeby zrozumieć, jak będzie wyglądała realna integracja z BullMQ lub innym silnikiem kolejek.
10. Typowe błędy przy ochronie perymetru
Błąd nr 1: Limitować tylko po IP.
W świecie ChatGPT Apps to prawie bezużyteczne: większość żądań przylatuje z adresów OpenAI i wszyscy twoi użytkownicy wylądują za tym samym IP. W efekcie ktoś jeden wypali limit dla wszystkich, a prawdziwy winowajca pozostanie nieznany. Poprawniej jest limitować po userId, tenantId lub tokenie, a IP używać tylko jako bardzo gruby filtr na poziomie reverse proxy.
Błąd nr 2: Zwracać gołe 500 zamiast sensownego błędu.
Jeśli przy przekroczeniu limitu lub przeciążeniu po prostu wysyłasz 500 Internal Server Error, model nic nie rozumie i zaczyna wymyślać. Natomiast zestructuryzowany błąd z kodem (rate_limit_exceeded, gateway_overloaded) i opisem zrozumiałym dla człowieka pozwala LLM poprawnie wyjaśnić sytuację użytkownikowi i, w razie potrzeby, spróbować ponownie później.
Błąd nr 3: Robić nieskończoną kolejkę bez backpressure.
Czasem kusi: „wrzućmy wszystko do kolejki, a potem się zobaczy”. W praktyce kolejka rozrasta się do tysięcy zadań, opóźnienia rosną, pamięć się kończy, a użytkownicy i tak nie widzą wyniku. Zawsze ograniczaj rozmiar kolejki i liczbę aktywnych operacji. Lepiej szczerze odmówić nowym żądaniom z 503 lub 429, niż zamienić kolejkę w czarną dziurę.
Błąd nr 4: Polegać tylko na rate limiting i ignorować webhooki.
Wielu chroni tylko ruch przychodzący od ChatGPT, a webhooki zostawia „jakoś to będzie”. Gdy dostawca płatności zacznie robić ponowne próby, to właśnie webhooki mogą urządzić ci prawdziwy sztorm. Endpointy webhooków potrzebują własnych limitów, weryfikacji podpisu i idempotentnej obsługi. Inaczej łatwo o dziesiątki duplikatów tego samego zamówienia.
Błąd nr 5: Trzymać wszystkie liczniki i kolejkę tylko w pamięci jednego instancji.
Dla projektu szkoleniowego to w porządku, ale w produkcji przy skalowaniu Gateway do kilku instancji liczniki na każdym węźle zaczną „żyć własnym życiem”, limity przestaną być globalne, a restart węzła wyzeruje kolejkę. W realnym systemie do przechowywania stanu limitów i kolejek używa się wspólnego magazynu (Redis, chmurowe kolejki itd.). Jeszcze o tym porozmawiamy w wykładach o skalowaniu i produkcji.
Błąd nr 6: Wpychać logikę biznesową do Gateway „skoro i tak jest pośrednikiem wszędzie”.
Czasem kusi: „to może od razu w Gateway zdecydujmy, jakie prezenty pokazać, bo i tak tam przychodzą żądania”. W efekcie gateway zmienia się w monolit z masą logiki, który jednocześnie jest routerem, mózgiem biznesowym i loggerem. To mocno utrudnia skalowanie i utrzymanie. Gateway powinien pozostać warstwą sieciowo‑infrastrukturalną: uwierzytelnianie, autoryzacja, limity, cache, routowanie — tak; dobór prezentów — nie.
Błąd nr 7: Myśleć, że „jesteśmy mali, nas to nie dotyczy”.
Często myślimy: „Nie mamy miliona użytkowników, obejdziemy się bez gateway/limitów”. W praktyce nawet jeden bug w kodzie klienckim (albo w promptcie, który każe modelowi zapętlać wywołanie toola) potrafi zafundować mały, ale bardzo lokalny armagedon. Podstawowy rate limiting i choćby prymitywny backpressure — to nie luksus, tylko „szczoteczka do zębów” produkcji: używać trzeba od samego początku, zanim zacznie boleć.
GO TO FULL VERSION