Yksi raskas chat voisi kaataa koko API:n
Syyskuun 28. päivänä, noin 17 minuuttia julkaisun jälkeen, molemmat tuotantoon käytetyt API-palvelimemme loppuivat muistista samaan aikaan, käynnistyivät uudelleen, ja noin viiden minuutin ajan kuormantasaaja ei löytänyt yhtäkään terveää palvelinta, jolle liikennettä lähettää. Ensimmäinen korjauksemme antoi API:lle kaksinkertaisen muistin. Todellinen syy paljastui myöhemmin samana päivänä: yhden tietyn chatin avaaminen pyysi API:a lataamaan 53 MB viestejä yhdellä pyynnöllä. Chat-historia ladataan nyt sivuina, joita rajaa koko lisäksi myös määrä, ja tuo chat avautuu 5 MB:n ensimmäisellä sivulla kaatumisen sijaan.
Mikä meni alas
API toimii konttina kummallakin kahdesta palvelimesta kuormantasaajan takana. Jokaisen kontin muistiraja oli 384 MiB. Levossa se käyttää 130–140 MiB, joten raja vaikutti antavan paljon varaa.
Molemmat kontit osuivat rajaan yhtä aikaa ja kaatuivat viestillä „JavaScript heap out of memory”. Keko on muisti, jota Node.js-prosessi käyttää työskennellessään olioiden kanssa. Koska molemmat palvelimet kaatuivat samalla kertaa, pyynnöille ei ollut minnekään reitittää. Se on pahin mahdollinen katkos kahden palvelimen järjestelyssä: varmuus auttaa vain, kun vikoja ei tule samaan aikaan.
Ensimmäinen korjaus: enemmän tilaa
Ajoitus viittasi julkaisuun. Julkaisun jälkeen agenttivuorot, jotka julkaisu keskeytti, jatkuvat ja tallentavat transkriptinsä uudelleen, ja tämä työpurske näytti ilmeiseltä muistipiikin lähteeltä.
Ensimmäinen muutos nostikin API:n rajan 384 MiB:stä 768 MiB:iin. Otimme sen käyttöön molemmilla palvelimilla samana päivänä ja kirjasimme sen julkaisun konfiguraatioon, ettei seuraava julkaisu palauttaisi hiljaa arvoa 384. Käsin palvelimelle tehty korjaus, jonka seuraava julkaisu kumoaa, on tavallinen tapa saada sama katkos takaisin.
Suurempi muisti oli järkevä turvamarginaali. Se ei ollut selitys.
Todellinen syy: yksi pyyntö, 71 MB
Myöhemmin samana päivänä toistimme kaatumisen chatin kopiolla tuotannon vanhalla muistirajalla. Kopio kaatoi API:n yksinään, ilman julkaisua.
Kun avaat projektin, web-sovellus pyytää API:lta chatin historian. Päätepiste palautti uusimmat 30 viestiä kokonaisina. Useimmissa chateissa 30 viestiä on pieni määrä. Tässä chatissa uusimmat 30 viestiä olivat tietokannassa 53 MB ja vastaus 71 MB. Useat agentin vastaukset olivat 4–10 MB kukin, enimmäkseen aliagenttien transkriptit: apuagentit, joita pääagentti käynnistää työskentelemään osan tehtävästä rinnakkain, ja niiden koko työ tallennetaan vastauksen mukana.
Tuon yhden vastauksen rakentaminen vaati enemmän keko-muistia kuin API:lla oli. Ja jokainen projektin avaus tai päivitys lähetti pyynnön uudelleen. Yksi chat saattoi kaataa minkä tahansa palvelimen, johon se osui, ja koska sivun saattoi ladata uudelleen, se saattoi osua molempiin.
„30 viestiä” ei ollut koskaan oikea koon raja. Se oli lukumäärän raja, joka sattui olemaan pieni tavallisille chateille.
Korjaus: sivut rajattuina koon mukaan lukumäärän lisäksi
Historiasivu on nyt enintään 30 viestiä tai noin 4 MB niitä, sen mukaan kumpi täyttyy ensin.
Se toimii kahdessa vaiheessa:
- Kysy ensin koot. API pyytää tietokannalta uusimpien viestien tunnukset ja tallennetut koot ilman sisältöjä. Tuo kysely on halpa riippumatta siitä, kuinka isoja viestit ovat.
- Lataa vain se, mikä mahtuu. Käymällä uusimmasta vanhimpaan API lisää viestejä, kunnes seuraava ylittäisi 4 MB:n rajan. Vain nuo rivit ladataan sitten kokonaisina.
Kaksi sääntöä pitää tämän turvallisena:
- Uusin viesti menee aina sivulle, kokoon katsomatta. Sivu, joka ei palauttaisi mitään, jättäisi chatin jumiin ilman tapaa selata taaksepäin.
- Mitään ei leikata. Yhtään viestiä ei katkaista eikä pudoteta. Loput chatista latautuu „Vieri ylös aiempiin viesteihin” -toiminnolla, jota vanhemmat viestit jo käyttivät, yksi sivu kerrallaan.
Löysimme vielä yhden kohdan samalla muodolla. Kun pysäytät vuoron, API tarkistaa viimeiset vastaukset nähdäkseen, onko jokin tyhjä ja siivottava. Se teki sen lataamalla kahdeksan kokonaista viestiä. Multi-megatavuisten vastausten chatissa tämä on sama ongelma pienemmässä muodossa. Nyt se kysyy tietokannalta kyllä/ei-kysymyksen — onko tässä viestissä mitään sisältöä? — lataamatta itse sisältöä.
Miten tarkistimme sen
Chatin kopiolla, ajettuna vanhalla 384 MiB:n rajalla:
- Ensimmäinen sivu on 5 MB. Ennen sama pyyntö kaatoi API:n.
- Viisi selainvälilehteä, jotka avaavat chatin samaan aikaan, huippu on 154 MB muistia.
- Chatin selaaminen taaksepäin koko matkan palauttaa kaikki 190 viestiä, jokaisen kerran, oikeassa järjestyksessä.
Sivujen valintalogiikalla on myös yksikkötestit, mukaan lukien yksi, joka on rakennettu tuon chatin uusimpien viestien todellisista kooista. Ensimmäinen sivu palautuu yhdellä viestillä, kertoo että lisää on, ja seuraava sivu alkaa sitä seuraavasta.
Mitä opimme
- Lukumäärä ei ole kokoraja. Jokaisen päätepisteen, joka palauttaa „uusimmat N” jostain, mikä voi kasvaa rajattomasti, tarvitsee myös tavumäärärajan. Data löytää aukon ennen pitkää.
- Ilmeisin epäilty ei aina ole syy. Julkaisu oli todellinen laukaisija ylimääräiselle työlle, ja lisämuisti oli arvokas. Mutta kaatuminen toistui ilman julkaisua, eikä ilman toistoa olisimme pysähtyneet muistin kasvattamiseen.
- Kaksi palvelinta kaatuu yhdessä, kun ne saavat saman pyynnön. Redundanssi suojaa yhden palvelimen kaatumiselta. Se ei suojaa pyynnöltä, joka tappaa jokaisen palvelimen, johon se osuu — etenkin pyynnöltä, jonka käyttäjä luonnollisesti yrittää uudelleen päivittämällä sivun.
- Korjaa konfiguraatiossa, ei vain koneella. Muistinlisäys meni julkaisun konfiguraatioon samana päivänä, joten seuraava julkaisu säilytti sen.
Tämä on toinen viimeaikainen muistitarinamme, joka liittyy agentin tulosteeseen. Ensimmäinen oli edistymisteksti, joka jonotti 690 MB merkkijonoja. Jos käytät aliagentteja Mythexissä, voit lukea niiden toiminnasta dokumentaatiosta.