Blogg

Hur en tung chatt kunde ha slagit ut vårt API

Den 28 september, cirka 17 minuter efter en release, tog minnet slut på båda våra produktions-API-servrar samtidigt. De startades om, och i ungefär fem minuter hade lastbalanseraren ingen frisk server att skicka trafik till. Vår första åtgärd gav API:et dubbelt så mycket minne. Den riktiga orsaken visade sig senare samma dag: när man öppnade en viss chatt bad API:et om att läsa in 53 MB meddelanden i ett enda anrop. Chatthistoriken läses nu in i sidor som begränsas både av antal och av storlek, och den chatten öppnas med en första sida på 5 MB i stället för att krascha.

Vad som gick ner

Vårt API körs som en container på var och en av två servrar bakom en lastbalanserare. Varje container hade en minnesgräns på 384 MiB. I vila använder den 130–140 MiB, så gränsen verkade ha gott om marginal.

Båda containrarna nådde gränsen samtidigt och kraschade med meddelandet ”JavaScript heap out of memory”. Heapen är det minne en Node.js-process använder för de objekt den arbetar med. Eftersom båda servrarna föll samtidigt fanns ingenstans kvar att dirigera förfrågningar till. Det är den värsta typen av driftstörning för en uppsättning med två servrar: redundans hjälper bara när felen inte inträffar samtidigt.

Den första åtgärden: mer utrymme

Tidpunkten pekade mot releasen. Efter en release återupptas agentturer som releasen avbröt, och de sparar sina transkript igen. Det arbetet verkade vara den uppenbara källan till minnestoppen.

Så den första ändringen höjde API:ets gräns från 384 MiB till 768 MiB. Vi tillämpade den live på båda servrarna samma dag och checkade in den i deploy-konfigurationen så att nästa deploy inte tyst återställer 384. En åtgärd som görs för hand på en server som nästa deploy sedan ångrar är ett vanligt sätt för samma driftstörning att komma tillbaka.

Mer minne var en rimlig säkerhetsmarginal. Det var inte en förklaring.

Den verkliga orsaken: ett anrop, 71 MB

Senare samma dag återskapade vi kraschen på en kopia av en chatt, med produktionens gamla minnesgräns. Kopian kraschade API:et på egen hand, utan någon deploy inblandad.

När du öppnar ett projekt begär webbappen chatthistoriken från API:et. Endpointen returnerade de 30 senaste meddelandena, hela. För de flesta chattar är 30 meddelanden litet. För just den här var de 30 senaste meddelandena 53 MB i databasen och ett 71 MB stort svar. Flera av agentens svar var 4–10 MB vardera, främst transkripten från underagenter: hjälpagenterna som huvudagenten startar för att arbeta på en del av en uppgift parallellt, och vars fullständiga arbete sparas tillsammans med svaret.

Att bygga det svaret tog mer heap än API:et hade. Och varje öppning eller uppdatering av projektet skickade förfrågan på nytt. En enda chatt kunde slå ut den server den hamnade på, och eftersom sidan kunde laddas om kunde den nå båda.

”30 meddelanden” hade aldrig varit en verklig gräns för storlek. Det var en gräns för antal som råkade vara liten för vanliga chattar.

Lösningen: sidor begränsade efter storlek och antal

En sida med historik är nu 30 meddelanden eller cirka 4 MB av dem, beroende på vad som kommer först.

Det fungerar i två steg:

  1. Begär storlekar först. API:et ber databasen om ID:n och lagrade storlekar för de senaste meddelandena, utan deras innehåll. Den frågan är billig oavsett hur stora meddelandena är.
  2. Läs bara in det som ryms. Från nyast till äldst lägger den till meddelanden tills nästa skulle föra sidan över 4 MB. Först då läses just de raderna in i sin helhet.

Två regler gör detta säkert:

  • Det nyaste meddelandet tas alltid med på sidan, oavsett storlek. En sida som inte returnerade något skulle låsa chatten utan något sätt att scrolla tillbaka.
  • Inget klipps bort. Inget meddelande kapas eller tas bort. Resten av chatten läses in via kontrollen ”Scrolla uppåt för tidigare meddelanden”, som äldre meddelanden redan använde, en sida i taget.

Vi hittade ytterligare ett ställe med samma form. När du stoppar en tur kontrollerar API:et de senaste svaren för att se om något är tomt och bör rensas. Det gjorde det genom att läsa in åtta hela meddelanden. I en chatt med flera megabyte stora svar är det samma problem i mindre skala. Nu ställer API:et databasen en ja-eller-nej-fråga — har det här meddelandet något innehåll? — utan att läsa in innehållet självt.

Så kontrollerade vi det

På kopian av chatten, med den gamla gränsen på 384 MiB:

  • Den första sidan är 5 MB. Tidigare kraschade samma anrop API:et.
  • Fem webbläsarflikar som öppnar chatten samtidigt når en topp på 154 MB minne.
  • Att scrolla tillbaka genom hela chatten returnerar alla 190 meddelanden, vart och ett exakt en gång, i ordning.

Logiken för sidval har också enhetstester, bland dem ett byggt på de verkliga storlekarna för den chattens senaste meddelanden. Den första sidan kommer tillbaka med ett meddelande, säger att det finns fler, och nästa sida börjar vid meddelandet efter det.

Vad vi tog med oss

  • Ett antal är ingen storleksgräns. Varje endpoint som returnerar ”de senaste N” av något som kan växa obegränsat behöver även en byte-budget. Datan hittar så småningom luckan.
  • Den uppenbara misstänkta är inte alltid orsaken. Deployen var en verklig utlösare för extra arbete, och mer minne var värt att ha. Men kraschen återskapades utan någon deploy, och utan att återskapa den hade vi stannat vid minnesökningen.
  • Två servrar fallerar tillsammans när de får samma förfrågan. Redundans skyddar mot att en server dör. Den skyddar inte mot en förfrågan som slår ut varje server den når, särskilt en som användaren naturligt försöker igen genom att ladda om.
  • Åtgärda i konfigurationen, inte bara på servern. Minnesökningen fördes in i deploy-konfigurationen samma dag, så nästa deploy behöll den.

Detta är vår andra nyliga minneshistoria som rör agentens utdata. Den första var en statusetikett som köade 690 MB strängar. Om du använder underagenter på Mythex kan du läsa om hur de fungerar i dokumentationen.

Mythex · Pricing · Bygg vad som helst med AI · Mallar för webbplatser och appar · Mythex jämfört · Ordlista för apputveckling med AI · Gratis verktyg för apputveckling med 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 Srpski Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)