Ιστολόγιο

Πώς μία βαριά συνομιλία θα μπορούσε να ρίξει το API μας

Στις 28 Σεπτεμβρίου, περίπου 17 λεπτά μετά από μια κυκλοφορία, και οι δύο servers του API μας σε παραγωγή εξαντλήθηκαν από μνήμη την ίδια στιγμή, κάνοντας επανεκκίνηση. Για περίπου πέντε λεπτά ο load balancer δεν είχε κανέναν υγιή server για να στείλει κίνηση. Η πρώτη μας διόρθωση έδωσε στο API διπλάσια μνήμη. Η πραγματική αιτία φάνηκε αργότερα εκείνη την ημέρα: το άνοιγμα μιας συγκεκριμένης συνομιλίας ζητούσε από το API να φορτώσει 53 MB μηνυμάτων σε ένα μόνο αίτημα. Το ιστορικό συνομιλιών φορτώνεται πλέον σε σελίδες περιορισμένες κατά μέγεθος αλλά και πλήθος, και εκείνη η συνομιλία ανοίγει με μια πρώτη σελίδα 5 MB αντί να καταρρέει.

Τι έπεσε

Το API μας τρέχει ως container σε καθέναν από τους δύο servers, πίσω από έναν load balancer. Κάθε container είχε όριο μνήμης 384 MiB. Σε ηρεμία χρησιμοποιεί 130–140 MiB, οπότε το όριο φαινόταν να αφήνει άφθονο περιθώριο.

Και τα δύο containers έφτασαν στο όριο μαζί και κατέρρευσαν με το μήνυμα «JavaScript heap out of memory». Ο σωρός (heap) είναι η μνήμη που χρησιμοποιεί μια διεργασία Node.js για τα αντικείμενα με τα οποία δουλεύει. Επειδή και οι δύο servers απέτυχαν ταυτόχρονα, δεν έμενε πουθενά να δρομολογηθούν αιτήματα. Αυτό είναι το χειρότερο είδος διακοπής για μια διάταξη με δύο servers: η εφεδρεία βοηθά μόνο όταν οι αστοχίες δεν συμπίπτουν.

Η πρώτη διόρθωση: περισσότερος χώρος

Η χρονική στιγμή έδειχνε προς την κυκλοφορία. Μετά από μια κυκλοφορία, οι σειρές του agent που διακόπηκαν από το deploy επαναλαμβάνονται και αποθηκεύουν ξανά τα απομαγνητοφωνήματά τους, και αυτό το ξέσπασμα εργασίας έμοιαζε με την προφανή πηγή της αιχμής μνήμης.

Έτσι, η πρώτη αλλαγή αύξησε το όριο του API από 384 MiB σε 768 MiB. Την εφαρμόσαμε ζωντανά και στους δύο servers την ίδια μέρα, και την καταχωρίσαμε στη διαμόρφωση του deploy ώστε η επόμενη κυκλοφορία να μην επαναφέρει αθόρυβα το 384. Μια διόρθωση που γίνεται με το χέρι σε έναν server, και που η επόμενη κυκλοφορία την αναιρεί, είναι συνηθισμένος τρόπος να επιστρέψει η ίδια διακοπή.

Περισσότερη μνήμη ήταν λογικό περιθώριο ασφαλείας. Δεν ήταν εξήγηση.

Η πραγματική αιτία: ένα αίτημα, 71 MB

Αργότερα εκείνη την ημέρα αναπαράγαμε την κατάρρευση σε ένα αντίγραφο μιας συνομιλίας, χρησιμοποιώντας το παλιό όριο μνήμης της παραγωγής. Το αντίγραφο κατέρρευσε το API μόνο του, χωρίς καμία κυκλοφορία.

Όταν ανοίγετε ένα project, η web εφαρμογή ζητά από το API το ιστορικό της συνομιλίας. Το endpoint επέστρεφε τα 30 τελευταία μηνύματα ολόκληρα. Για τις περισσότερες συνομιλίες, τα 30 μηνύματα είναι λίγα. Για αυτή, τα 30 πιο πρόσφατα μηνύματα ήταν 53 MB στη βάση δεδομένων και μια απάντηση 71 MB. Αρκετές από τις απαντήσεις του agent ήταν 4–10 MB η καθεμία, κυρίως τα απομαγνητοφωνήματα των υπο-agent: των βοηθητικών agent που ξεκινά ο κύριος agent για να δουλέψουν παράλληλα σε μέρος της εργασίας, και των οποίων η πλήρης δουλειά αποθηκεύεται μαζί με την απάντηση.

Η κατασκευή αυτής της μίας απάντησης χρειαζόταν περισσότερη μνήμη σωρού απ' ό,τι είχε το API. Και κάθε άνοιγμα ή ανανέωση του project έστελνε ξανά το αίτημα. Μία μόνο συνομιλία μπορούσε να ρίξει όποιον server την έπαιρνε, και επειδή η σελίδα μπορούσε να ανανεωθεί, μπορούσε να φτάσει και στους δύο.

Το «30 μηνύματα» δεν ήταν ποτέ πραγματικό όριο μεγέθους. Ήταν όριο πλήθους, που έτυχε να είναι μικρό για τις κανονικές συνομιλίες.

Η διόρθωση: σελίδες περιορισμένες και κατά μέγεθος

Μια σελίδα ιστορικού είναι πλέον 30 μηνύματα ή περίπου 4 MB από αυτά, όποιο από τα δύο έρθει πρώτο.

Λειτουργεί σε δύο βήματα:

  1. Πρώτα ζητάμε τα μεγέθη. Το API ζητά από τη βάση τα IDs και τα αποθηκευμένα μεγέθη των πιο πρόσφατων μηνυμάτων, χωρίς τα περιεχόμενά τους. Αυτό το ερώτημα είναι φθηνό όσο μεγάλα κι αν είναι τα μηνύματα.
  2. Φορτώνουμε μόνο ό,τι χωράει. Κινούμενο από το νεότερο στο παλαιότερο, προσθέτει μηνύματα μέχρι το επόμενο να ξεπερνούσε τα 4 MB της σελίδας. Μόνο τότε φορτώνονται πλήρως αυτές οι γραμμές.

Δύο κανόνες το κρατούν ασφαλές:

  • Το πιο πρόσφατο μήνυμα μπαίνει πάντα στη σελίδα, όποιο κι αν είναι το μέγεθός του. Μια σελίδα που δεν επιστρέφει τίποτα θα άφηνε τη συνομιλία κολλημένη, χωρίς τρόπο να γυρίσει πίσω.
  • Τίποτα δεν κόβεται. Κανένα μήνυμα δεν περικόπτεται ή χάνεται. Το υπόλοιπο της συνομιλίας φορτώνεται μέσω του χειριστηρίου «Κύλισε για προηγούμενα μηνύματα», που ήδη χρησιμοποιούσαν τα παλαιότερα μηνύματα, μία σελίδα κάθε φορά.

Βρήκαμε και ένα ακόμα σημείο με το ίδιο σχήμα. Όταν σταματάτε μια σειρά, το API ελέγχει τις τελευταίες λίγες απαντήσεις για να δει αν κάποια είναι κενή και πρέπει να καθαριστεί. Το έκανε φορτώνοντας οκτώ ολόκληρα μηνύματα. Σε μια συνομιλία με απαντήσεις πολλών megabytes, αυτό είναι το ίδιο πρόβλημα σε μικρότερη μορφή. Τώρα ρωτά τη βάση μια ναι-ή-όχι ερώτηση — έχει αυτό το μήνυμα κάποιο περιεχόμενο; — χωρίς να φορτώσει το ίδιο το περιεχόμενο.

Πώς το ελέγξαμε

Στο αντίγραφο της συνομιλίας, με το παλιό όριο 384 MiB:

  • Η πρώτη σελίδα είναι 5 MB. Πριν, το ίδιο αίτημα έριχνε το API.
  • Πέντε καρτέλες του browser που ανοίγουν τη συνομιλία ταυτόχρονα φτάνουν σε αιχμή 154 MB μνήμης.
  • Η κύλιση σε όλη τη συνομιλία επιστρέφει και τα 190 μηνύματα, το καθένα μία φορά, με τη σωστή σειρά.

Η λογική επιλογής σελίδων έχει επίσης unit tests, συμπεριλαμβανομένου ενός που χτίστηκε από τα πραγματικά μεγέθη των πιο πρόσφατων μηνυμάτων εκείνης της συνομιλίας. Η πρώτη σελίδα επιστρέφει ένα μήνυμα, δηλώνει ότι υπάρχουν κι άλλα, και η επόμενη σελίδα ξεκινά από αυτό που ακολουθεί.

Τι κρατήσαμε από αυτό

  • Το πλήθος δεν είναι όριο μεγέθους. Κάθε endpoint που επιστρέφει «τα πιο πρόσφατα N» από κάτι που μπορεί να μεγαλώσει απεριόριστα χρειάζεται και ένα όριο bytes. Τα δεδομένα θα βρουν κάποια στιγμή το κενό.
  • Η προφανής ύποπτη δεν είναι πάντα η αιτία. Το deploy ήταν πραγματική αιτία για επιπλέον δουλειά, και η περισσότερη μνήμη άξιζε. Όμως η κατάρρευση αναπαράχθηκε χωρίς deploy, και χωρίς αναπαραγωγή θα είχαμε σταματήσει στην αύξηση μνήμης.
  • Δύο servers πέφτουν μαζί όταν παίρνουν το ίδιο αίτημα. Η εφεδρεία προστατεύει από τον θάνατο ενός server. Δεν προστατεύει από ένα αίτημα που ρίχνει όποιον server το φτάνει — ειδικά ένα που ο χρήστης θα ξαναστείλει φυσικά ανανεώνοντας τη σελίδα.
  • Διορθώστε το στη διαμόρφωση, όχι μόνο στο μηχάνημα. Η αύξηση μνήμης μπήκε στη διαμόρφωση του deploy την ίδια μέρα, ώστε η επόμενη κυκλοφορία να τη διατηρήσει.

Αυτή είναι η δεύτερη πρόσφατη ιστορία μνήμης που αφορά την έξοδο του agent. Η πρώτη ήταν μια ετικέτα κατάστασης που έβαζε σε ουρά 690 MB συμβολοσειρών. Αν χρησιμοποιείτε υπο-agent στο Mythex, μπορείτε να διαβάσετε πώς λειτουργούν στα docs.

Mythex · Pricing · Χτίστε οτιδήποτε με AI · Πρότυπα ιστοσελίδων και εφαρμογών · Το Mythex σε σύγκριση · Γλωσσάρι δημιουργίας εφαρμογών με AI · Δωρεάν εργαλεία δημιουργίας εφαρμογών με 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 Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)