როგორ შეიძლებოდა ერთმა მძიმე ჩატს API ჩამოერთმია
28 სექტემბერს, გამოშვებიდან დაახლოებით 17 წუთში, ჩვენი ორივე წარმოების API სერვერი ერთდროულად ამოიწურა მეხსიერება, გადაიტვირთა, და დაახლოებით ხუთი წუთით დატვირთვის ბალანსერს არ ჰყავდა ჯანმრთელი სერვერი, რომელსაც ტრაფიკს გადასცემდა. ჩვენმა პირველმა შესწორებამ API-ს მეხსიერება ორჯერ გაუზარდა. ნამდვილი მიზეზი იმავე დღეს გამოჩნდა: ერთი კონკრეტული ჩატის გახსნა API-ს ერთ მოთხოვნაში 53 მბ შეტყობინების ჩატვირთვას სთხოვდა. ჩატის ისტორია ახლა გვერდებით იტვირთება, რომლებიც ზომითაც არის შეზღუდული და არა მხოლოდ რაოდენობით, და ის ჩატი ახლა იხსნება 5 მბ-იანი პირველი გვერდით და არა ავარიით.
რა გამოვიდა მწყობრიდან
ჩვენი API ორ სერვერზე, თითოეულში კონტეინერად მუშაობს, ბალანსერის უკან. თითოეულ კონტეინერს 384 MiB მეხსიერების ლიმიტი ჰქონდა. უმოქმედოდ ის 130–140 MiB-ს იყენებს, ასე რომ ლიმიტი საკმარის თავისუფალ ადგილად მოჩანდა.
ორივე კონტეინერმა ერთად მიაღწია ამ ლიმიტს და „JavaScript heap out of memory“-ით ჩამოინგრა. heap არის მეხსიერება, რომელსაც Node.js პროცესი იყენებს ობიექტებისთვის, რომლებთანაც მუშაობს. რადგან ორივე სერვერი ერთდროულად ჩამოინგრა, მოთხოვნების გადასამისამართებლად აღარაფერი დარჩა. ეს ყველაზე ცუდი ტიპის გათიშვაა ორსერვერიანი მოწყობისთვის: რეზერვირება მხოლოდ მაშინ გვეხმარება, როცა ავარიები ერთდროულად არ ხდება.
პირველი შესწორება: მეტი ადგილი
დრო დეპლოის მიმართ მიუთითებდა. გამოშვების შემდეგ, დეპლოის მიერ შეწყვეტილი აგენტის ციკლები ისევ იწყება და თავიანთ ტრანსკრიპტებს ხელახლა ინახავენ, და ამ სამუშაოს აფეთქებას მეხსიერების მატების აშკარა წყარო ეჩვენა.
ამიტომ პირველი ცვლილება API-ს ლიმიტს 384 MiB-დან 768 MiB-მდე ზრდიდა. ის ორივე სერვერზე პირდაპირ იმავე დღეს გავუშვით, და დეპლოის კონფიგურაციაში ჩავწერეთ, რომ შემდეგმა დეპლოიმ 384 ისევ არ დააბრუნოს. ხელით გაკეთებული შესწორება სერვერზე, რომელსაც შემდეგი დეპლოი აუქმებს, იმ ავარიის დაბრუნების ჩვეულებრივი გზაა.
მეტი მეხსიერება გონივრული სარეზერვო ზღვარი იყო. ახსნა არ იყო.
ნამდვილი მიზეზი: ერთი მოთხოვნა, 71 მბ
მოგვიანებით იმავე დღეს ავარია ჩატის ასლზე განვაახლეთ, წარმოების ძველი მეხსიერების ლიმიტით. ასლი API-ს მარტო ჩამოაგდო, დეპლოის გარეშე.
როცა პროექტს ხსნით, ვებ აპი API-საგან ჩატის ისტორიას ითხოვს. ბოლო 30 შეტყობინებას პირველად აბრუნებდა სრულად. ბევრი ჩატისთვის 30 შეტყობინება პატარაა. ამ ჩატისთვის ბოლო 30 შეტყობინება ბაზაში 53 მბ იყო და პასუხში 71 მბ. აგენტის ზოგიერთი პასუხი 4–10 მბ იყო თითო, უმეტესად ქვე-აგენტების ტრანსკრიპტები: დამხმარე აგენტები, რომლებსაც მთავარი აგენტი ამოცანის ნაწილზე პარალელურად მუშაობისთვის იწყებს, და მათი სრული სამუშაო პასუხთან ერთად ინახება.
ამ ერთი პასუხის აშენებას API-ს ხელმისაწვდომზე მეტი heap დასჭირდა. და ყოველი პროექტის გახსნა ან განახლება მოთხოვნას ხელახლა აგზავნიდა. ერთი ჩატი შეიძლებოდა ჩამოეგდო სერვერი, რომელზეც მოხვდებოდა, და რადგან გვერდის გადატვირთვა შეიძლებოდა, ის ორივეს მიაღწევდა.
„30 შეტყობინება“ არასდროს ყოფილა ზომის რეალური ლიმიტი. ეს იყო რაოდენობის ლიმიტი, რომელიც ჩვეულებრივი ჩატებისთვის მოხდენილად პატარა იყო.
გამოსწორება: გვერდები ზომითაც და რაოდენობითაც შეზღუდული
ისტორიის გვერდი ახლა არის 30 შეტყობინება ან დაახლოებით 4 მბ მათგან, რომელიც ადრე მოვა.
მუშაობს ორ ეტაპად:
- ჯერ ზომები მოითხოვება. API ბაზას სთხოვს უახლესი შეტყობინებების ID-ებსა და შენახულ ზომებს, შიგთავსის გარეშე. ეს მოთხოვნა იაფია, მიუხედავად იმისა, რამდენად დიდია შეტყობინებები.
- იტვირთება მხოლოდ ის, რაც ეტევა. უახლესიდან უძველესისკენ მიმავალი, ის ამატებს შეტყობინებებს მანამ, სანამ შემდეგი გვერდს 4 მბ-ზე მეტით არ გადააჭარბებს. მხოლოდ ამ რიგების სრული ჩატვირთვა ხდება.
ორი წესი ამას უსაფრთხოს ხდის:
- უახლესი შეტყობინება ყოველთვის შედის გვერდზე, რა ზომისაც უნდა იყოს. გვერდი, რომელიც არაფერს აბრუნებს, ჩატს გაჩერებულს დატოვებს და უკან სქროლვის საშუალებას არ მისცემს.
- არაფერი იჭრება. არც ერთი შეტყობინება არ იჭრება ან იკარგება. დანარჩენი ჩატი იტვირთება „ადრინდელი შეტყობინებების ნახვისთვის ზევით გადაახვიეთ“ ღილაკით, რომელსაც ძველი შეტყობინებები უკვე იყენებდა, გვერდ-გვერდ.
კიდევ ერთი ადგილი ვიპოვეთ ამავე ფორმით. როცა ციკლს აჩერებთ, API ბოლო რამდენიმე პასუხს ამოწმებს, იქნებ რომელიმე ცარიელია და გასუფთავებას საჭიროებს. ამას რვა მთლიანი შეტყობინების ჩატვირთვით აკეთებდა. მრავალმეგაბაიტიანი პასუხების ჩატში ეს იგივე პრობლემაა პატარა სახით. ახლა ბაზას ვეკითხებით ორპასუხიან კითხვას — აქვს თუ არა ამ შეტყობინებას რაიმე შიგთავსი? — შიგთავსის თვითონ ჩატვირთვის გარეშე.
როგორ შევამოწმეთ
ჩატის ასლზე, ძველი 384 MiB ლიმიტით:
- პირველი გვერდი 5 მბ-ია. წინათ იგივე მოთხოვნა API-ს ჩამოაგდებდა.
- ხუთი ბრაუზერის ტაბი, რომლებიც ერთდროულად ხსნის ჩატს, მეხსიერების პიკს 154 მბ-ზე აღწევს.
- მთელ ჩატში სქროლვისას ყველა 190 შეტყობინება ბრუნდება, თითოეული ერთხელ, რიგის მიხედვით.
გვერდების შერჩევის ლოგიკას აქვს ერთეულური ტესტები, მათ შორის ერთი, რომელიც იმ ჩატის უახლესი შეტყობინებების რეალური ზომებით არის აგებული. პირველი გვერდი ერთ შეტყობინებას აბრუნებს, ამბობს, რომ კიდევ არის, და შემდეგი გვერდი იწყება იმით, რომელიც მის შემდეგ მოდის.
რა ავიღეთ ამ ამბიდან
- რაოდენობა ზომის ლიმიტი არ არის. ნებისმიერ endpoint-ს, რომელიც „უახლეს N-ს“ აბრუნებს იმისგან, რაც შეუზღუდავად შეიძლება გაიზარდოს, ბაიტების ბიუჯეტიც სჭირდება. მონაცემები საბოლოოდ იპოვის უფსკრულს.
- აშკარა ეჭვმიტანილი ყოველთვის არ არის მიზეზი. დეპლოი ნამდვილად იყო სამუშაოს დამატებითი მიზეზი, და მეტი მეხსიერება ღირდა. მაგრამ ავარია დეპლოის გარეშეც განმეორდა, და მის განმეორების გარეშე მეხსიერების გაზრდაზე გავჩერდებოდით.
- ორი სერვერი ერთად ეცემა, თუ ერთსა და იმავე მოთხოვნას იღებენ. რეზერვირება ერთი სერვერის ჩამოვარდნისგან იცავს. ის არ იცავს მოთხოვნისგან, რომელიც ნებისმიერ სერვერს, რომელსაც მიაღწევს, ჩამოაგდებს — განსაკუთრებით მოთხოვნისგან, რომელსაც მომხმარებელი გვერდის გადატვირთვით თავისთავად გაიმეორებს.
- შეასწორეთ კონფიგურაციაში და არა მხოლოდ სერვერზე. მეხსიერების გაზრდა იმავე დღეს დეპლოის კონფიგურაციაში შევიდა, ასე რომ შემდეგმა დეპლოიმ შეინარჩუნა.
ეს ჩვენი აგენტის გამოტანასთან დაკავშირებული მეორე ბოლოდროინდელი მეხსიერების ამბავია. პირველი იყო სტატუსის label, რომელმაც 690 მბ სტრინგი დააყენა რიგში. თუ Mythex-ზე ქვე-აგენტებს იყენებთ, იმაზე, როგორ მუშაობენ, დოკუმენტაციაში წაიკითხავთ.