Blog

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

Dana 28. rujna, otprilike 17 minuta nakon izdanja, oba naša produkcijska API servera ostala su bez memorije u istom trenutku, ponovno su se pokrenula i na otprilike pet minuta load balancer nije imao nijedan zdrav server kojem bi slao promet. Naš prvi popravak dao je API-ju dvostruko više memorije. Pravi uzrok se pokazao kasnije tog dana: otvaranje jednog određenog chata tražilo je od API-ja da u jednom zahtjevu učita 53 MB poruka. Povijest chata sada se učitava u stranicama ograničenima i veličinom i brojem, a taj chat se otvara s prvom stranicom od 5 MB umjesto da padne.

Što je palo

Naš API radi kao kontejner na svakom od dva servera iza load balancera. Svaki kontejner imao je ograničenje memorije od 384 MiB. U mirovanju koristi 130–140 MiB, pa je ograničenje izgledalo kao dovoljno prostora.

Oba kontejnera su istovremeno dosegla 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. Budući da su oba servera pala istovremeno, nije bilo kamo usmjeriti zahtjeve. To je najgori oblik ispada za postavu s dva servera: redundancija pomaže samo kad padovi ne nastupe istovremeno.

Prvi popravak: više prostora

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

Zato je prva promjena digla ograničenje API-ja sa 384 MiB na 768 MiB. Primijenili smo to uživo na oba servera istog dana i zapisali u konfiguraciju deploya, da sljedeći deploy tiho ne vrati 384. Popravak napravljen ručno na serveru koji sljedeći deploy poništi čest je način na koji se isti ispad vrati.

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

Pravi uzrok: jedan zahtjev, 71 MB

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

Kad otvorite projekt, web aplikacija traži od API-ja povijest chata. Krajnja točka je vraćala najnovijih 30 poruka, cijele. Za većinu chatova 30 poruka je malo. Za ovaj, najnovijih 30 poruka bilo je 53 MB u bazi i 71 MB odgovora. Nekoliko agentovih odgovora bilo je veličine 4–10 MB svaki, uglavnom transkripti pod-agenata: pomoćnih agenata koje glavni agent pokreće da paralelno rade dio zadatka, a čiji je puni rad spremljen uz odgovor.

Izgradnja tog jednog odgovora zauzela je više heapa nego što je API imao. A svako otvaranje ili osvježavanje tog projekta ponovno je slalo zahtjev. Jedan chat mogao je srušiti bilo koji server na koji bi došao, a budući da se stranica može ponovno učitati, mogao je doći do oba.

„30 poruka” nikad nije bila stvarna granica veličine. Bila je granica broja koja je slučajno bila mala za obične chatove.

Popravak: stranice ograničene i veličinom i brojem

Stranica povijesti sada ima 30 poruka ili otprilike 4 MB njih, što god prvo dođe.

Radi u dva koraka:

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

Dva pravila to čuvaju sigurnim:

  • Najnovija poruka uvijek ide na stranicu, bez obzira na veličinu. Stranica koja ne bi vratila ništa ostavila bi chat zaglavljenim, bez načina da se vratite nazad.
  • Ništa se ne reže. Nijedna poruka se ne skraćuje niti izbacuje. Ostatak chata učitava se kroz kontrolu „Scroll up for earlier messages” koju su stariji zapisi već koristili, po jednu stranicu.

Našli smo još jedno mjesto s istim oblikom problema. Kad zaustavite korak, API provjerava zadnjih nekoliko odgovora da vidi jesu li neki prazni i treba ih očistiti. To je radio tako da je učitao osam cijelih poruka. Na chatu s odgovorima od po nekoliko megabajta to je isti problem u manjem obliku. Sada postavlja bazi jednostavno pitanje da/ne, ima li 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 srušio API.
  • Pet kartica preglednika koje istovremeno otvaraju chat na vrhuncu troše 154 MB memorije.
  • Pomicanje kroz cijeli chat vraća svih 190 poruka, svaku točno jednom, poredanih.

Logika odabira stranica ima i jedinične testove, uključujući jedan izgrađen od stvarnih veličina najnovijih poruka tog chata. Prva stranica vraća jednu poruku, javlja da ih ima još, a sljedeća stranica počinje od one iza nje.

Što smo iz toga izvukli

  • Broj nije ograničenje veličine. Svaka krajnja točka koja vraća „najnovijih N” nečega što može rasti bez granica treba i proračun u bajtovima. Podaci će na kraju pronaći prazninu.
  • Najočitiji osumnjičenik nije uvijek uzrok. Deploy je bio pravi okidač za dodatni posao, a veća memorija je vrijedila. Ali pad se reproducirao i bez deploya, i bez reprodukcije bismo se zaustavili na povećanju memorije.
  • Dva servera padaju zajedno kad dobiju isti zahtjev. Redundancija štiti od pada jednog servera. Ne štiti od zahtjeva koji ubija bilo koji server kojem dođe, pogotovo onog koji će korisnik prirodno ponoviti osvježavanjem.
  • Popravite u konfiguraciji, a ne samo na stroju. Povećanje memorije ušlo je u konfiguraciju deploya istog dana, pa ga je sljedeći deploy zadržao.

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

Mythex · Pricing · Izradite bilo što uz AI · Predlošci za web stranice i aplikacije · Mythex u usporedbi · Rječnik izrade aplikacija uz AI · Besplatni alati za izradu aplikacija uz AI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी 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 简体中文 繁體中文(香港) 繁體中文(台灣)