Blog

Kako jedan težak chat može srušiti naš API

Dana 28. septembra, oko 17 minuta nakon izdanja, oba naša produkciona API servera ostala su bez memorije u isto vrijeme, restartovala su se, i otprilike pet minuta balanser opterećenja nije imao nijedan zdrav server kome bi slao saobraćaj. Naše prvo rješenje dalo je API-ju dvostruko više memorije. Pravi uzrok se pokazao kasnije tog dana: otvaranje jednog određenog chata zahtijevalo je od API-ja da učita 53 MB poruka u jednom zahtjevu. Historija chata sada se učitava u stranicama ograničenim i veličinom i brojem, a taj chat se otvara s prvom stranicom od 5 MB umjesto da padne.

Šta je palo

Naš API radi kao kontejner na svakom od dva servera iza balansera opterećenja. Svaki kontejner imao je ograničenje memorije od 384 MiB. U mirovanju koristi 130–140 MiB, pa je ograničenje izgledalo kao dovoljna rezerva.

Oba kontejnera su istovremeno dostigla to ograničenje i srušila se uz poruku „JavaScript heap out of memory”. Heap je memorija koju Node.js proces koristi za objekte s kojima radi. Pošto su oba servera otkazala istovremeno, nije ostalo mjesto kamo bi se zahtjevi usmjerili. To je najgora vrsta prekida za postavku s dva servera: redundansa pomaže samo kada se kvarovi ne dešavaju u isto vrijeme.

Prvo rješenje: više prostora

Vrijeme je upućivalo na izdanje. Nakon izdanja, potezi agenta koje je izdanje prekinulo se nastavljaju i ponovo spremaju svoje transkripte, a taj nalet posla izgledao je kao očit izvor skoka memorije.

Zato je prva promjena podigla ograničenje API-ja s 384 MiB na 768 MiB. Primijenili smo je uživo na oba servera istog dana i upisali u konfiguraciju izdanja, da sljedeće izdanje tiho ne vrati 384. Ručna popravka na serveru koju sljedeće izdanje poništi čest je način da se isti prekid ponovo pojavi.

Veća memorija bila je razumna sigurnosna rezerva. Nije bila objašnjenje.

Pravi uzrok: jedan zahtjev, 71 MB

Kasnije tog dana reprodukovali smo pad na kopiji jednog chata, koristeći staro ograničenje memorije iz produkcije. Kopija je sama srušila API, bez ikakvog izdanja u igri.

Kada otvorite projekat, web aplikacija traži od API-ja historiju chata. Endpoint je vraćao najnovijih 30 poruka, cijele. Za većinu chatova 30 poruka je malo. Za ovaj, najnovijih 30 poruka bilo je 53 MB u bazi i odgovor od 71 MB. Nekoliko odgovora agenta bilo je po 4–10 MB, uglavnom transkripti pod-agenata: pomoćnih agenata koje glavni agent pokreće da paralelno rade na dijelu zadatka, a čiji se puni rad čuva uz odgovor.

Izgradnja tog jednog odgovora zahtijevala je više heapa nego što je API imao. A svako otvaranje ili osvježavanje tog projekta ponovo je slalo zahtjev. Jedan chat mogao je srušiti onaj server na koji padne, a pošto se stranica mogla ponovo učitati, mogao je srušiti i oba.

„30 poruka” nikada nije bilo stvarno ograničenje veličine. Bilo je ograničenje broja, koje je slučajno bilo malo za obične chatove.

Rješenje: stranice ograničene i veličinom i brojem

Stranica historije sada ima 30 poruka ili oko 4 MB njih, šta god prvo nastupi.

Radi u dva koraka:

  1. Prvo traži veličine. API od baze traži ID-jeve i sačuvane veličine najnovijih poruka, bez njihovog sadržaja. Taj upit je jeftin bez obzira na to koliko su poruke velike.
  2. Učitava samo ono što stane. Idući od najnovije ka najstarijoj, dodaje poruke dok sljedeća ne bi pomjerila stranicu preko 4 MB. Tek tada se učitavaju samo ti redovi u cijelosti.

Dva pravila to čine sigurnim:

  • Najnovija poruka uvijek ide na stranicu, bez obzira na veličinu. Stranica koja ne bi vratila ništa ostavila bi chat zaglavljen, bez načina da se vrati nazad.
  • Ništa se ne siječe. Nijedna poruka se ne skraćuje niti izostavlja. Ostatak chata učitava se kroz kontrolu „Pomjeri gore za ranije poruke” koju su starije poruke već koristile, jednu stranicu po jednu.

Našli smo još jedno mjesto s istim oblikom. Kada zaustavite potez, API provjerava posljednjih nekoliko odgovora da vidi da li je neki prazan i treba ga očistiti. To je radio tako što je učitavao osam cijelih poruka. Na chatu s odgovorima od više megabajta to je isti problem u manjem obliku. Sada bazi postavlja pitanje na da ili ne — ima li ova poruka ikakav sadržaj? — bez učitavanja samog sadržaja.

Kako smo provjerili

Na kopiji chata, uz staro ograničenje od 384 MiB:

  • Prva stranica je 5 MB. Prije je isti zahtjev rušio API.
  • Pet tabova pregledača koji istovremeno otvaraju chat dostiže vrhunac od 154 MB memorije.
  • Listanje unazad kroz cijeli chat vraća svih 190 poruka, svaku tačno jednom, po redu.

Logika biranje stranica također ima jedinične testove, uključujući jedan izgrađen od stvarnih veličina najnovijih poruka tog chata. Prva stranica vraća jednu poruku, kaže da ih ima još, a sljedeća stranica počinje od one koja slijedi.

Šta smo izvukli iz ovoga

  • Broj nije ograničenje veličine. Svaki endpoint koji vraća „najnovijih N” nečega što može neograničeno rasti treba i budžet u bajtovima. Podaci će na kraju pronaći prazninu.
  • Očigledni osumnjičeni nije uvijek uzrok. Izdanje je bilo pravi okidač za dodatni posao, i više memorije je vrijedilo imati. Ali pad se reprodukovao bez izdanja, a bez te reprodukcije bili bismo stali na povećanju memorije.
  • Dva servera padaju zajedno kada dobiju isti zahtjev. Redundansa štiti od toga da jedan server umre. Ne štiti od zahtjeva koji ubija bilo koji server do kojeg dođe, naročito onog koji će korisnik prirodno ponovo pokušati osvježavanjem.
  • Popravite u konfiguraciji, ne samo na mašini. Povećanje memorije ušlo je u konfiguraciju izdanja istog dana, pa ga je sljedeće izdanje zadržalo.

Ovo je druga nedavna priča o memoriji vezana za izlaz agenta. Prva je bila oznaka statusa koja je stavila u red 690 MB stringova. Ako koristite pod-agente na Mythexu, o tome kako rade možete pročitati u dokumentaciji.

Mythex · Pricing · Izradite bilo šta uz AI · Šabloni za web stranice i aplikacije · Mythex u poređenju · Rječnik izrade aplikacija uz AI · Besplatni alati za izradu aplikacija uz AI · English

English አማርኛ العربية Български বাংলা Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)