RDS კავშირებზე უარს ამბობდა 10% CPU-ზე: ნაგულისხმევი პულები, გამრავლებული რეპლიკებზე
დატვირთვის ტესტები ~420 კავშირის ბრტყელ პლატოზე ჩერდებოდა და „უფრო დიდ ბაზას" ითხოვდნენ. ნამდვილი ჭერი იყო max_connections Npgsql-ის ნაგულისხმევი პულების წინააღმდეგ.
Gambchamp-ის სტეიჯ გარემოზე დატვირთვის ტესტებისას .NET სერვისებმა ბაზასთან კავშირების გახსნა ვეღარ შეძლეს. ჩემამდე მარტივი თხოვნა მოვიდა: „ბევრად დიდი ბაზა გვჭირდება”. RDS ინსტანსის CPU იმავე ფანჯრებში 97%-მდე ადიოდა, ამიტომ თხოვნა გონივრული ჩანდა.
გრაფიკის ფორმა სხვას ამბობდა.
პლატო და არა მრუდი
CloudWatch DatabaseConnections ათეულობით წუთის განმავლობაში 412–424-ზე ბრტყელ ხაზს აჩვენებდა. შვიდდღიანი საშუალო — დაახლოებით 7. ტევადობის პრობლემები მრუდს ღუნავს; ბრტყელი მწვერვალი ლიმიტში შეჯახებაა.
ინსტანსი იყო db.t4g.medium ნაგულისხმევი max_connections ფორმულით — ეს 399-ია. გამოკლებული superuser_reserved_connections=3 და reserved_connections=2 — ჩვეულებრივ როლებს 394 სლოტი რჩება.
საიდან 400 კავშირი
არც ერთ კავშირის სტრიქონში Max Pool Size არ იყო, ამიტომ Npgsql ნაგულისხმევს იღებდა: 100 კავშირი პროცესზე, კავშირის სტრიქონზე. არც PgBouncer, არც RDS Proxy; ყოველი პოდი პირდაპირ უერთდებოდა.
PostgreSQL-ის ლოგში პიკის ფანჯარაში ერთ ბაზაზე სამი კლიენტური IP იყო — ინტეგრაციული სერვისის სამი რეპლიკა — და კიდევ ერთი სხვა ბაზაზე. სამჯერ 100 პლუს 100 — დაახლოებით 400: აპლიკაციების ჭერი თითქმის ზუსტად.
მტკიცებულება — კონკრეტული FATAL ხაზი, ერთ ტესტში 18-ჯერ და შემდეგში 47-ჯერ:
remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role
ორი მაჩვენებელი, რომელიც გზას გიბნევს
- CloudWatch პიკში
max_connections-ზე მაღლა იყო (424 > 399). მეტრიკა ითვლის იმ ბექენდებსაც, რომლებიც დაფორკდნენ და მერე FATAL მიიღეს, ამიტომ შტორმში აჭარბებს. too many clients alreadyარასდროს გამოჩენილა. ეს შეტყობინება ნიშნავს, რომ დარეზერვებული სლოტებიც კი ამოიწურა. „Remaining connection slots are reserved” უფრო ადრე ჩნდება, როცა მხოლოდ ჩვეულებრივი სლოტები მთავრდება — ზუსტად ეს მოხდა.
პრაქტიკულად ამის დანახვა მხოლოდ პიკის ფანჯრით შეზღუდული Logs Insights-ით იყო შესაძლებელი: ჩვეულებრივი ფილტრი უსასრულო შენახვის ლოგ-ჯგუფზე უბრალოდ არ მთავრდებოდა.
დიდი ინსტანსი მხოლოდ კედელს წევს
კოლეგამ ინსტანსი db.m7g.large-მდე გაზარდა — ლიმიტი 832-მდე გაიზარდა (827 აპლიკაციებისთვის). ეს მარაგია, მაგრამ კედლის მოხსნა არა: ბაზის კავშირის სტრიქონებს 17 პოდი იყენებს, ანუ თეორიული მოთხოვნა მინიმუმ 17 × 100 = 1 700-ია.
ნამდვილი შეკეთება აპლიკაციების მხარესაა: თითოეული სერვისისთვის მკაფიო Max Pool Size, რომ „რეპლიკები × პულის ზომა” ჯამი აპლიკაციების სლოტების ~80%-ის ფარგლებში დარჩეს. ციფრებით გადავეცი სერვისების მფლობელებს. თუ რეპლიკების რაოდენობა HPA-ით დინამიკური გახდება, შემდეგი ნაბიჯი RDS Proxy ან PgBouncer-ია. ასევე ნოდების დაშბორდი დავამატე, რომ შემდეგმა ტესტმა ნოდების სატურაციაც აჩვენოს: იმ namespace-ის 20 დეპლოიმენტიდან 19-ს რესურსების ლიმიტი არ ჰქონდა.
დასკვნები
- კავშირების ბრტყელი პლატო ლიმიტია და არა ტევადობის მრუდი. სანამ რამეს გაზრდი, FATAL-ის ტექსტი შეამოწმე.
- Npgsql-ის პულები პროცესზე და კავშირის სტრიქონზეა: პოდს, რომელიც სამ ბაზასთან დადის, შეუძლია 300 კავშირი დაიკავოს.
- კავშირები მკაფიოდ დაგეგმე: Σ(რეპლიკები × პულის ზომა) ≤ ~80%
max_connections-ს გამოკლებული დარეზერვებული სლოტები. - CloudWatch
DatabaseConnectionsუარების შტორმში შეიძლებაmax_connections-ს აჭარბებდეს. ეს ლიმიტის ბაგი არ არის.