Blog

Kako je jedno teško ćaskanje moglo da obori naš API

Dana 28. septembra, oko 17 minuta nakon izdanja, oba naša produkciona API servera ostala su bez memorije u istom trenutku i restartovala su se. Oko pet minuta balanser opterećenja nije imao nijedan zdrav server kome bi slao saobraćaj. Naša prva ispravka dala je API-ju dvostruko više memorije. Pravi uzrok se pokazao kasnije tog dana: otvaranje jednog određenog ćaskanja zahtevalo je od API-ja da učita 53 MB poruka u jednom zahtevu. Istorija ćaskanja sada se učitava u stranicama ograničenim i po veličini i po broju, a to ćaskanje se otvara sa prvom stranicom od 5 MB umesto da se sruši.

Šta je otišlo u kvar

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 delovalo 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 sa kojima radi. Pošto su oba servera otkazala u isto vreme, nije ostalo nijedno mesto kojem bi se zahtevi preusmerili. To je najgora vrsta ispada za postavku sa dva servera: redundansa pomaže samo kada se otkazi ne dešavaju istovremeno.

Prva ispravka: više prostora

Vremenski sled je ukazivao na izdanje. Nakon izdanja, agentovi zadaci koje je izdanje prekinulo nastavljaju se i ponovo čuvaju svoje transkripte, a taj nalet posla delovao je kao očigledan izvor skoka memorije.

Zato je prva izmena podigla ograničenje API-ja sa 384 MiB na 768 MiB. Primenili smo je uživo na oba servera istog dana i upisali je u konfiguraciju izdanja, kako sledeće izdanje ne bi tiho vratilo vrednost na 384. Ispravka napravljena ručno na serveru, koju sledeće izdanje poništi, čest je način da se isti ispad ponovi.

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

Pravi uzrok: jedan zahtev, 71 MB

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

Kada otvorite projekat, web aplikacija traži od API-ja istoriju ćaskanja. Krajnja tačka vraćala je najnovijih 30 poruka, u celosti. Za većinu ćaskanja 30 poruka je malo. Za ovo, najnovijih 30 poruka imalo je 53 MB u bazi i 71 MB u odgovoru. 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 na delu zadatka, a čiji je ceo rad sačuvan uz odgovor.

Sastavljanje tog jednog odgovora zauzimalo je više heap memorije nego što je API imao. A svako otvaranje ili osvežavanje tog projekta ponovo je slalo zahtev. Jedno ćaskanje moglo je da sruši koji god server da ga primi, a pošto se stranica mogla ponovo učitati, moglo je da stigne do oba.

„30 poruka“ nikad nije bilo stvarno ograničenje veličine. Bilo je ograničenje broja koje je slučajno bilo malo za obična ćaskanja.

Ispravka: stranice ograničene i po veličini i po broju

Stranica istorije sada sadrži 30 poruka ili oko 4 MB, šta god prvo dostigne.

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 koliko su poruke velike.
  2. Učitava samo ono što stane. Idući od najnovije ka najstarijoj, dodaje poruke dok sledeća ne bi pomerila stranicu preko 4 MB. Tek tada se samo te redove učitavaju u celosti.

Dva pravila čuvaju ovo bezbednim:

  • Najnovija poruka uvek ide na stranicu, bez obzira na veličinu. Stranica koja ne vrati ništa ostavila bi ćaskanje zaglavljenim, bez načina da se vrati unazad.
  • Ništa se ne seče. Nijedna poruka se ne skraćuje niti izostavlja. Ostatak ćaskanja učitava se preko kontrole „Pomeri se nagore za ranije poruke“ koju su stariji poruke već koristile, jedna stranica po jedna.

Našli smo još jedno mesto sa istim oblikom. Kada zaustavite agentov rad, API proverava poslednjih nekoliko odgovora da vidi da li je neki prazan i treba da bude očišćen. To je radio tako što je učitavao osam celih poruka. Na ćaskanju sa odgovorima od više megabajta, to je isti problem u manjem obliku. Sada bazi postavlja pitanje da-ili-ne — da li ova poruka ima ikakav sadržaj — bez učitavanja samog sadržaja.

Kako smo proverili

Na kopiji ćaskanja, pokrećući sa starim ograničenjem od 384 MiB:

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

Logika za biranje stranica takođe ima jedinične testove, uključujući jedan napravljen od stvarnih veličina najnovijih poruka tog ćaskanja. Prva stranica vraća jednu poruku i javlja da ima još, a sledeća stranica počinje od one posle nje.

Šta smo izvukli iz ovoga

  • Broj nije ograničenje veličine. Svaka krajnja tačka koja vraća „najnovijih N“ nečega što može neograničeno da raste treba i budžet bajtova. Podaci će pre ili kasnije otkriti tu rupu.
  • Očigledan osumnjičeni nije uvek uzrok. Izdanje je bilo pravi okidač za dodatni posao, a više memorije je vredelo imati. Ali pad se reprodukovao i bez izdanja, a bez reprodukcije bismo stali na povećanju memorije.
  • Dva servera otkazuju zajedno kada dobiju isti zahtev. Redundansa štiti od toga da jedan server umre. Ne štiti od zahteva koji ubija svaki server kome stigne, pogotovo od onog koji će korisnik prirodno ponovo poslati osvežavanjem.
  • Ispravite u konfiguraciji, ne samo na mašini. Povećanje memorije ušlo je u konfiguraciju izdanja istog dana, pa ga je sledeće izdanje zadržalo.

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

Mythex · Pricing · Pravite bilo šta uz AI · Šabloni za sajtove i aplikacije · Mythex u poređenju · Rečnik pojmova za pravljenje aplikacija uz AI · Besplatni alati za pravljenje aplikacija uz AI · English

English አማርኛ العربية Български বাংলা Bosanski 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 Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)