Blogg

Hvordan én tung chat kunne tatt API-et vårt ned

Den 28. september, omtrent 17 minutter etter en utgivelse, gikk minnet tomt samtidig på begge produksjons-API-serverne våre, de startet på nytt, og i rundt fem minutter hadde lastbalanseren ingen frisk server å sende trafikk til. Vår første løsning ga API-et dobbelt så mye minne. Den virkelige årsaken dukket opp senere samme dag: å åpne én bestemt chat ba API-et om å laste 53 MB med meldinger i én enkelt forespørsel. Chatthistorikk lastes nå i sider som begrenses etter størrelse i tillegg til antall, og den chatten åpnes med en første side på 5 MB i stedet for en krasj.

Hva som gikk ned

API-et vårt kjører som en container på hver av to servere bak en lastbalanserer. Hver container hadde en minnegrense på 384 MiB. I hvile bruker den 130–140 MiB, så grensen virket som god margin.

Begge containerne traff grensen samtidig og krasjet med «JavaScript heap out of memory». Heapen er minnet en Node.js-prosess bruker til objektene den jobber med. Fordi begge serverne feilet samtidig, fantes det ingen gjenværende steder å sende forespørsler. Det er den verste typen nedetid for et oppsett med to servere: redundans hjelper bare når feilene ikke inntreffer samtidig.

Den første løsningen: mer plass

Tidspunktet pekte mot deployen. Etter en utgivelse gjenopptas agentturer som deployen avbrøt, og de lagrer transkripsjonene sine på nytt. Den arbeidsbølgen så ut som den åpenbare kilden til en minneøkning.

Så den første endringen hevet API-ets grense fra 384 MiB til 768 MiB. Vi brukte den live på begge serverne samme dag, og committet den i deploy-konfigurasjonen slik at neste deploy ikke stille satte 384 tilbake. En rettelse gjort for hånd på en server som neste deploy river tilbake, er en vanlig måte å få samme nedetid tilbake på.

Mer minne var en fornuftig sikkerhetsmargin. Det var ikke en forklaring.

Den virkelige årsaken: én forespørsel, 71 MB

Senere samme dag reproduserte vi krasjen på en kopi av én chat, med produksjonens gamle minnegrense. Kopien krasjet API-et på egen hånd, uten at noen deploy var involvert.

Når du åpner et prosjekt, ber webappen API-et om chattens historikk. Endepunktet returnerte de nyeste 30 meldingene, hele. For de fleste chatter er 30 meldinger lite. For denne var de nyeste 30 meldingene 53 MB i databasen og et svar på 71 MB. Flere av agentens svar var 4–10 MB hver, stort sett transkripsjoner fra underagenter: hjelpeagentene hovedagenten starter for å jobbe med deler av en oppgave parallelt, og hvis fulle arbeid lagres sammen med svaret.

Å bygge det ene svaret tok mer heap enn API-et hadde. Og hver gang du åpnet eller oppdaterte prosjektet, ble forespørselen sendt på nytt. En enkelt chat kunne ta ned hvilken server den traff, og fordi siden kunne lastes på nytt, kunne den nå begge.

«30 meldinger» hadde aldri vært en reell grense på størrelse. Det var en grense på antall som tilfeldigvis var liten for vanlige chatter.

Løsningen: sider begrenset etter størrelse i tillegg til antall

En historikkside er nå 30 meldinger eller omtrent 4 MB med dem, det som kommer først.

Det fungerer i to steg:

  1. Be om størrelser først. API-et ber databasen om ID-ene og de lagrede størrelsene til de nyeste meldingene, uten innholdet. Den spørringen er billig uansett hvor store meldingene er.
  2. Last bare det som får plass. Fra nyeste til eldste legger den til meldinger til neste ville sprengt siden over 4 MB. Bare disse radene lastes deretter fullt ut.

To regler gjør dette trygt:

  • Den nyeste meldingen kommer alltid med på siden, uansett størrelse. En side som ga tomt svar ville satt chatten fast, uten måte å rulle tilbake på.
  • Ingenting kuttes. Ingen melding trunkeres eller droppes. Resten av chatten lastes via kontrollen «Scroll up for earlier messages» som eldre meldinger allerede brukte, én side av gangen.

Vi fant ett sted til med samme form. Når du stopper en tur, sjekker API-et de siste svarene for å se om noen er tomme og bør ryddes opp. Det gjorde det ved å laste inn åtte hele meldinger. I en chat med flere megabyte store svar er det samme problem i mindre format. Nå spør det databasen et ja-eller-nei-spørsmål — har denne meldingen noe innhold? — uten å laste selve innholdet.

Slik sjekket vi det

På kopien av chatten, med den gamle grensen på 384 MiB:

  • Første side er 5 MB. Før krasjet samme forespørsel API-et.
  • Fem nettleserfaner som åpner chatten samtidig topper på 154 MB minne.
  • Å bla tilbake gjennom hele chatten returnerer alle 190 meldinger, hver nøyaktig én gang, i rekkefølge.

Logikken for sidevalg har også enhetstester, inkludert én bygget på de virkelige størrelsene til den chatens nyeste meldinger. Den første siden kommer tilbake med én melding, sier at det finnes flere, og neste side starter på den etter den.

Det vi tok med oss

  • En telling er ikke en størrelsesgrense. Ethvert endepunkt som returnerer «de nyeste N» av noe som kan vokse uten grense, trenger også et byte-budsjett. Dataene finner gapet til slutt.
  • Den åpenbare mistenkte er ikke alltid årsaken. Deployen var en reell utløser for ekstra arbeid, og mer minne var verdt å ha. Men krasjen reproduserte uten deploy, og uten å reprodusere den ville vi ha stoppet ved minneøkningen.
  • To servere feiler sammen når de får samme forespørsel. Redundans beskytter mot at én server dør. Den beskytter ikke mot en forespørsel som dreper enhver server den når, særlig en som brukeren naturlig prøver på nytt ved å oppdatere siden.
  • Fiks det i konfigurasjonen, ikke bare på maskinen. Minneøkningen ble lagt inn i deploy-konfigurasjonen samme dag, så neste deploy beholdt den.

Dette er vår andre nylige minnehistorie knyttet til agentens utdata. Den første var en statusetikett som køet 690 MB med strenger. Hvis du bruker underagenter på Mythex, kan du lese om hvordan de fungerer i dokumentasjonen.

Mythex · Pricing · Bygg hva som helst med KI · Maler for nettsider og apper · Mythex sammenlignet · Ordliste for app-bygging med KI · Gratis verktøy for app-bygging med KI · 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 မြန်မာ 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 简体中文 繁體中文(香港) 繁體中文(台灣)