Paano Maibabagsak ng Isang Mabigat na Chat ang Aming API
Noong Setyembre 28, mga 17 minuto matapos ang isang release, sabay na naubusan ng memory ang dalawa naming production API server, nag-restart, at sa loob ng mga limang minuto ay walang malusog na server ang load balancer na mapapadalhan ng trapiko. Dinoble ng una naming ayos ang memory ng API. Lumabas ang tunay na sanhi nang hapong iyon: ang pagbubukas ng isang partikular na chat ay nagpahiling sa API na mag-load ng 53 MB ng mensahe sa iisang request. Naglo-load na ngayon ang chat history sa mga pahina na may limitasyon sa laki at bilang, at ang chat na iyon ay nagbubukas na sa unang pahinang 5 MB sa halip na mag-crash.
Ano ang bumagsak
Tumatakbo ang API namin bilang container sa bawat isa sa dalawang server sa likod ng load balancer. May limit sa memory na 384 MiB ang bawat container. Sa pahinga, gumagamit ito ng 130–140 MiB, kaya mukhang maraming espasyo pa ang natitira.
Sabay na naabot ng dalawang container ang limit na iyon at nag-crash na may “JavaScript heap out of memory”. Ang heap ang memory na ginagamit ng isang Node.js process para sa mga object na hawak nito. Dahil sabay na pumalya ang dalawang server, wala nang mapaglilipatan ng request. Ito ang pinakamasamang uri ng outage para sa setup na may dalawang server: nakakatulong lang ang redundancy kapag hindi sabay na pumapalya ang mga ito.
Ang unang ayos: mas maraming espasyo
Nakatutok sa deploy ang oras. Pagkatapos ng release, itinutuloy ang mga agent turn na naantala ng deploy at muling sine-save ang kanilang transcript, at ang biglaang dami ng trabahong iyon ang mukhang halatang pinagmulan ng pagtaas ng memory.
Kaya itinaas ng unang pagbabago ang limit ng API mula 384 MiB patungong 768 MiB. Ini-apply namin ito nang live sa dalawang server nang araw ding iyon, at isinama sa deploy configuration para hindi tahimik na ibalik ng susunod na deploy ang 384. Karaniwang paraan para bumalik ang parehong outage ang ayos na ginawa nang manu-mano sa server na babaligtarin lang ng susunod na deploy.
Makatwirang safety margin ang mas maraming memory. Hindi ito paliwanag.
Ang tunay na sanhi: isang request, 71 MB
Pagkalipas ng ilang oras nang araw ding iyon, muli naming naranasan ang crash sa isang kopya ng isang chat, gamit ang lumang memory limit ng production. Ang kopya ang nag-crash sa API nang mag-isa, nang walang kinalaman ang deploy.
Kapag nagbukas ka ng project, hihingi ang web app sa API ng history ng chat. Ibinabalik ng endpoint ang pinakabagong 30 mensahe, buo. Para sa karamihan ng chat, maliit ang 30 mensahe. Para sa chat na ito, ang pinakabagong 30 mensahe ay 53 MB sa database at 71 MB na response. Ilan sa mga sagot ng agent ay 4–10 MB bawat isa, karamihan ay transcript ng sub-agents: ang mga helper agent na sinisimulan ng pangunahing agent para sa bahagi ng gawain nang sabay-sabay, na ang buong trabaho ay sine-save kasama ng sagot.
Higit sa heap na mayroon ang API ang kailangan para buuin ang isang response na iyon. At sa bawat pagbukas o pag-refresh ng project na iyon, ipinapadala ulit ang request. Kayang ibagsak ng iisang chat ang anumang server na tamaan nito, at dahil puwedeng i-reload ang page, puwede nitong maabot ang dalawa.
Kailanman ay hindi naging tunay na limitasyon sa laki ang “30 mensahe”. Limitasyon ito sa bilang na nagkataong maliit para sa normal na chat.
Ang ayos: mga pahinang may limitasyon sa laki at bilang
Ang isang pahina ng history ngayon ay 30 mensahe o mga 4 MB ng mga ito, alin man ang mauna.
Dalawang hakbang ang paraan:
- Humingi muna ng laki. Hihingi ang API sa database ng ID at naka-store na laki ng pinakabagong mensahe, nang walang mga nilalaman. Mura ang query na iyon gaano man kalaki ang mga mensahe.
- I-load lang ang kasya. Mula pinakabago hanggang pinakalumang mensahe, nagdadagdag ito hanggang ang susunod na mensahe ay lalampas sa 4 MB ng pahina. Saka lang buong ni-load ang mga row na iyon.
Dalawang panuntunan ang nagpapanatiling ligtas dito:
- Laging kasama sa pahina ang pinakabagong mensahe, anuman ang laki nito. Ang pahinang walang ibinalik ay mag-iiwan sa chat na nakabara, walang paraan para mag-scroll pabalik.
- Walang pinuputol. Walang mensaheng pinaiikli o inaalis. Ang natitirang bahagi ng chat ay nilo-load sa pamamagitan ng kontrol na “Scroll up for earlier messages” na ginagamit na ng mas lumang mga mensahe, isang pahina sa bawat pagkakataon.
May isa pang lugar na may parehong hugis na nakita namin. Kapag pinatigil mo ang isang turn, sinusuri ng API ang huling ilang sagot para tingnan kung alinman ay walang laman at dapat linisin. Ginagawa iyon sa pamamagitan ng pag-load ng walong buong mensahe. Sa chat na may mga sagot na maraming megabyte, iyon ang parehong problema sa mas maliit na anyo. Ngayon, nagtatanong na ito sa database ng oo-o-hindi: may laman ba ang mensaheng ito? nang hindi nilo-load ang mismong nilalaman.
Paano namin ito sinubok
Sa kopya ng chat, habang tumatakbo gamit ang lumang limit na 384 MiB:
- Ang unang pahina ay 5 MB. Dati, nag-crash ang API sa parehong request.
- Ang limang browser tab na sabay na nagbubukas ng chat ay umaabot sa pinakamataas na 154 MB ng memory.
- Ang pag-scroll pabalik sa buong chat ay nagbabalik ng lahat ng 190 mensahe, bawat isa ay eksaktong isang beses, sa tamang pagkakasunod.
May unit test din ang lohika sa pagpili ng pahina, kasama ang isang gawa mula sa aktuwal na laki ng pinakabagong mensahe ng chat na iyon. Ang unang pahina ay nagbabalik ng isang mensahe, nagsasabing may iba pa, at ang susunod na pahina ay nagsisimula sa kasunod nito.
Ang natutunan namin
- Hindi limitasyon sa laki ang bilang. Anumang endpoint na nagbabalik ng “ang pinakabagong N” ng bagay na maaaring lumaki nang walang hanggan ay kailangan ng byte budget din. Matutuklasan ng data ang puwang sa huli.
- Hindi palaging ito ang sanhi ang halatang pinaghihinalaan. Totoong trigger ng dagdag na trabaho ang deploy, at sulit ang mas maraming memory. Pero nag-reproduce ang crash nang walang deploy, at kung hindi namin ito na-reproduce, tumigil sana kami sa pagtaas ng memory.
- Sabay na bumabagsak ang dalawang server kapag pareho ang request na natatanggap nila. Pinoprotektahan ng redundancy laban sa pagkamatay ng isang server. Hindi ito nagpoprotekta laban sa request na pumapatay sa anumang server na maabot nito, lalo na ang isang request na kusang uulitin ng user sa pamamagitan ng pag-refresh.
- Ayusin sa config, hindi lang sa mismong makina. Isinama ang pagtaas ng memory sa deploy configuration nang araw ding iyon, kaya pinanatili ito ng susunod na deploy.
Ikalawang kamakailang kuwento ito ng memory na may kinalaman sa output ng agent. Ang una ay isang status label na naglagay sa pila ng 690 MB na string. Kung gumagamit ka ng sub-agents sa Mythex, mababasa mo kung paano sila gumagana sa docs.