ყველა მონაცემთა ბაზას აქვს ერთდროული კავშირების მაქსიმალური რაოდენობა და ის უფრო დაბალია, ვიდრე უმეტესობა ფიქრობს. მისი გადაჭარბება რბილად არ ანელებს საქმეს. ის შეცდომას წარმოქმნის, და წარმოქმნის დატვირთვისას, ანუ სწორედ მაშინ, როცა ახალი მარცხის რეჟიმი ყველაზე ნაკლებად გჭირდება:
FATAL: sorry, too many clients alreadyეს შეტყობინება სიმძლავრის პრობლემა არ არის და უფრო დიდი გეგმა მას ვერ მოაგვარებს. ეს არითმეტიკაა: კავშირების რაოდენობა, რომლის გახსნის უფლებაც შენს აპლიკაციის პროცესებს აქვთ, გამრავლებული პროცესების რაოდენობაზე, მეტია იმაზე, რასაც ბაზა იღებს. ეს პოსტი ამ არითმეტიკას იმისთვის გაკეთებაზეა, სანამ პროდაქშენი გაგიკეთებს: რა ჯდება კავშირი, რატომ სჯობს პატარა pool დიდს, რომელი რიცხვებიდან დაიწყო, რომელი timeout-ები აქცევს გაჭედვას სუფთა შეცდომად და როგორ იპოვო leak, როცა pool საათების განმავლობაში იცლება.
რა ჯდება სინამდვილეში კავშირი#
PostgreSQL-ში კავშირი ოპერაციული სისტემის პროცესია. postmaster თითოეულისთვის backend-ს ფორკავს და ის ცოცხლობს, სანამ კლიენტი არ გაითიშება. მას საკუთარი პირადი მეხსიერების რამდენიმე მეგაბაიტი უჭირავს, პლუს ქეშისა და buffer-ის სტრუქტურები, რომლებსაც ეხება, პლუს ის, რასაც მისი მოთხოვნა მუშაობისას work_mem-დან იღებს. ასი უმოქმედო კავშირი ასი პროცესია, რომლებიც ბირთვმა უნდა დაგეგმოს, და მეხსიერების რეალური ნაჭერი, რომელიც ერთი მოთხოვნის გაშვებამდე გაქრა.
გახსნაც უფასო არ არის. ახალი კავშირი ნიშნავს fork-ს, ავთენტიფიკაციას, TLS მოლაპარაკებას, თუ იყენებ, და სესიის მომზადებას, რომელსაც შენი framework აკეთებს. ლოკალურ ქსელში ეს რამდენიმე მილიწამია; ინტერნეტით TLS-ით შეიძლება ათეულობით. ვებ მოთხოვნისთვის, რომელსაც 30 ms რეალური სამუშაო სჭირდება, ყოველ ჯერზე კავშირის გასახსნელად 20 ms-ის გადახდა შენს დაყოვნებას ორმაგად ზრდის ტყუილად.
MongoDB-ში კავშირი უფრო იაფია - პროცესის ნაცვლად thread-ია - მაგრამ უფასო არა, და სერვერსაც ჭერი აქვს. იქ რეალურად driver-ის pool-ს აკონტროლებ და იგივე მსჯელობა მასაც ეხება.
pool ორივე პრობლემას წყვეტს. ის ერთხელ ხსნის კავშირების ფიქსირებულ რაოდენობას, გასცემს მათ კოდის იმ ნაწილს, რომელიც ითხოვს, მოთხოვნის ბოლოს იბრუნებს და თბილს ინახავს. ყველა გავრცელებულ framework-ს აქვს. კითხვა არასოდეს არის, გამოვიყენო თუ არა pool; კითხვაა, რა რიცხვი ჩავწერო max-ში.
რატომ ანელებს მეტი კავშირი საქმეს#
ეს არაინტუიციური ნაწილია და სწორედ ამიტომ არის პასუხი ჩვეულებრივ ნაკლები რიცხვი, ვიდრე ხალხი ელოდება.
მონაცემთა ბაზის სერვერს რეალურად შეუძლია შეზღუდული რაოდენობის საქმის ერთდროულად კეთება: დაახლოებით იმდენის, რამდენი CPU ბირთვიც აქვს, პლუს გარკვეული გადაფარვა იმისთვის, რაც დისკს ელოდება. ამის მიღმა ზედმეტი კავშირები მეტ საქმეს არ აკეთებს. ისინი რიგში დგებიან - მაგრამ თავაზიანად შენს pool-ში დგომის ნაცვლად, სადაც ეს არაფერი ჯდება, ბაზის შიგნით დგებიან, სადაც ყოველი მოლოდინის მოთხოვნას მეხსიერება, ლოქები და დაგეგმვის სლოტი უჭირავს და სადაც ასობით backend-ს შორის context switching იმ CPU-ს წვავს, რომელიც მოთხოვნებს შეეძლო გაეშვა.
შედეგი გამტარუნარიანობის მრუდია, რომელიც იზრდება, გასწორდება და მერე ეცემა. ათი კავშირი, რომლებიც დაკავებულები რჩებიან, ასზე უკეთესია, რომლებიც ერთმანეთს ეჯახებიან, და ასს ექნება უარესი tail latency, ისევე როგორც უარესი გამტარუნარიანობა, რადგან ყოველი მოთხოვნა ახლა ოთხმოცდაცხრამეტს ელოდება ცხრის ნაცვლად.
ამის დანახვის მოხერხებული გზა არსებობს. თანადროულობა უდრის გამტარუნარიანობას გამრავლებულს დაყოვნებაზე. თუ აპლიკაცია წამში 500 მოთხოვნას აკეთებს და საშუალო მოთხოვნას 4 ms სჭირდება, მაშინ საშუალოდ ნებისმიერ მომენტში ორი კავშირია დაკავებული. ორი. ორმოცდაათიანი pool, რომელიც ბლოგიდან გადმოიწერე, არაფერს აჩქარებს; ეს დაზღვევაა პიკისგან, რომელსაც რიგი უკეთ გაუმკლავდებოდა.
არითმეტიკა, რომელსაც არავინ აკეთებს#
რიცხვი შენს კონფიგურაციის ფაილში თითო პროცესზეა. თითქმის ყველა ინციდენტი ამის დავიწყებით იწყება.
| კომპონენტი | კავშირები | შენიშვნა |
|---|---|---|
| ვებ აპლიკაცია, 4 პროცესი, pool 20 | 80 | რიცხვი, რომელსაც ხალხი „20“-ად ასახელებს |
| Background worker, 2 პროცესი, pool 5 | 10 | ჩვეულებრივ მთლიანად ავიწყდებათ |
| დაგეგმილი დავალებები, ზოგჯერ გადაფარვით | 5 | პიკზე ადის, როცა ღამის ანგარიში გადის |
| შენი psql სესია debug-ის დროს | 1 | ის, რომელიც ყველაზე მეტად გჭირდება |
| მონიტორინგი ან metrics exporter | 2 | სამუდამოდ ეკითხება, შეცდომისას ხელახლა უკავშირდება |
| სულ ნაგულისხმევ `max_connections = 100`-ის წინააღმდეგ | 98 | ორი რეზერვი |
PostgreSQL-ის ნაგულისხმევი max_connections არის 100 და მათგან სამს superuser_reserved_connections იტოვებს, რომ ადმინისტრატორმა მაინც შეძლოს შესვლა. შენი რეალური ბიუჯეტი 97-ია. 98-მდე შევსება ნიშნავს, რომ შემდეგი deploy, რომელიც მოკლედ ძველ და ახალ პროცესებს ერთად უშვებს, ჩავარდება.
დათვალე ყველა პროცესი, რომელიც უკავშირდება და არა ყველა სერვერი. PHP-ის ან WordPress-ის სტეკში ჩვეულებრივი გაგებით pool საერთოდ არ არის: ყოველ PHP-FPM worker-ს საკუთარი კავშირი უჭირავს, ამიტომ pm.max_children არის შენი pool-ის ზომა და ორმოცი child ორმოცი კავშირია. კონტეინერიზებულ გარემოში ორი კონტეინერიდან ოთხზე გადასვლა ჯამს ორმაგად ზრდის იმის გარეშე, რომ ვინმემ ბაზის პარამეტრს შეხოს. და ყველაფერი, რაც ერთ პროცესს თითო მოთხოვნაზე უშვებს - serverless პლატფორმა, CGI-ს სტილის ჰოსტი - შეზღუდვის გარეშე მრავლდება, რაც ზუსტად ის შემთხვევაა, რისთვისაც PgBouncer არსებობს.
pool-ის ზომა: ფორმულა და საწყისი წერტილი#
ყველაზე ძველი და ისევ საუკეთესო ხელისგულა წესი PostgreSQL-ის community-დან მოდის და HikariCP-ის დოკუმენტაციამ გახადა ცნობილი:
connections = (core_count * 2) + effective_spindle_count2 vCPU სერვერზე NVMe საცავით ეს მთელი აპლიკაციისთვის დაახლოებით 5-ს ან 6-ს გვაძლევს და არა თითო პროცესზე. 4 vCPU-ზე - დაახლოებით 9-ს ან 10-ს. ეს რიცხვები აბსურდულად პატარა ჩანს ყველასთვის, ვინც 50-იან pool-ს უშვებდა და ვერ ამჩნევდა, და ეს არის აზრი: ისინი 50-ს არასოდეს იყენებდნენ, ისინი ბაზის წინ რიგში დგომის ნაცვლად მის შიგნით დგებოდნენ.
პრაქტიკული საწყისი წერტილი პატარა აპლიკაციისთვის:
| სიტუაცია | კავშირები სულ | როგორ გავანაწილოთ |
|---|---|---|
| 1-2 vCPU ბაზა, ერთი აპლიკაციის პროცესი | 5-10 | ერთი pool, max 8 |
| 2 vCPU ბაზა, 4 აპლიკაციის პროცესი | 10-12 | max 3 თითო პროცესზე |
| 4 vCPU ბაზა, 4 აპლიკაციის პროცესი და 2 worker | 16-20 | max 3 თითოეულზე, 2 worker-ებზე |
| პლუს, ყოველთვის | 3-5 | მიგრაციები, მონიტორინგი, შენ |
მერე შეასწორე მტკიცებულებით. თუ pool არასოდეს იწურება და მოთხოვნები სწრაფია, ის საკმარისად დიდია - ზედმეტად დიდი pool არანაირ სიმპტომს არ აჩენს იმ დღემდე, სანამ არ აჩენს. თუ მოთხოვნები კავშირის აღებას ელოდება, სანამ ბაზის CPU უქმია, გაზარდე. თუ მოთხოვნები ელოდება და ბაზის CPU გაჯერებულია, pool პრობლემა არ არის: გაასწორე მოთხოვნები EXPLAIN ANALYZE-ით ან დაამატე ინდექსი, რადგან უფრო დიდი pool იმავე CPU-ს უბრალოდ უფრო თხელად გაანაწილებს.
RE:NODE-ზე მონაცემთა ბაზის ხაზები არის შენი საკუთარი სერვერი და არა გაზიარებული კლასტერის ნაჭერი, ფაილებითა და კონსოლით ხელმისაწვდომი, ამიტომ max_connections არის ის, რასაც ამ სერვერზე postgresql.conf ამბობს და შეგიძლია შეცვალო. ფრთხილად გაზარდე პატარა დონეზე: მეხსიერება შემზღუდველი ფაქტორია, და როცა კონტეინერი აქ მეხსიერების ლიმიტს აღწევს, ის swap-ში დატოვების ნაცვლად ჩერდება და სუფთად ხელახლა ირთვება. ასი backend 1 GB გეგმაზე ნელ შუადღეს გადატვირთვად აქცევს. PostgreSQL-ის მორგება პატარა სერვერებისთვის აღწერს, რომელი რიცხვი გაზარდო პირველად, და ეს იშვიათად არის ეს.
pool-ის პარამეტრები ბიბლიოთეკებში, რომლებსაც იყენებ#
ყველა pool ერთსა და იმავე რამდენიმე იდეას სხვადასხვა სახელით აჩვენებს. ეს ნაგულისხმევებია, რომლებიც ცოდნად ღირს, რადგან რამდენიმე მათგანი სერვერული აპლიკაციისთვის არასწორია.
| ბიბლიოთეკა | ზომის პარამეტრი | ნაგულისხმევი | რის დაყენებაა ღირს |
|---|---|---|---|
node-postgres (pg) | max | 10 | connectionTimeoutMillis, idleTimeoutMillis |
| SQLAlchemy | pool_size | 5 (პლუს max_overflow 10) | pool_pre_ping, pool_recycle |
| HikariCP (Java) | maximumPoolSize | 10 | connectionTimeout, maxLifetime |
| Django | CONN_MAX_AGE | 0, ახალი კავშირი თითო მოთხოვნაზე | 60, ან აშკარა pool |
| PHP-FPM | pm.max_children | განსხვავდება | ჩათვალე pool-ის ზომად |
| MongoDB driver-ები | maxPoolSize | 100 | შეამცირე, პლუს waitQueueTimeoutMS |
ამ ნაგულისხმევებიდან სამს კომენტარი სჭირდება. SQLAlchemy-ის max_overflow ნიშნავს, რომ რეალური ჭერი თითო პროცესზე 15-ია და არა 5, რაც ზემოთ არითმეტიკის მკეთებლებს აკვირვებს. Django ისტორიულად ხსნიდა და ხურავდა კავშირს თითო მოთხოვნაზე, რაც უსაფრთხოა და ნელი; CONN_MAX_AGE რაღაც 60-ზე მას ხელახლა იყენებს, ხოლო ბოლო ვერსიებს psycopg 3-ით ნამდვილი pool-ის გამოყენებაც შეუძლია - შეამოწმე დოკუმენტაცია შენი ვერსიისთვის, რადგან ეს ახლახან შეიცვალა. და MongoDB driver-ების ნაგულისხმევი 100 თითო პროცესზე ბევრად მეტია, ვიდრე ნებისმიერ პატარა აპლიკაციას სჭირდება.
// node-postgres: one pool per process, created onceimport { Pool } from "pg";export const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5, idleTimeoutMillis: 30_000, connectionTimeoutMillis: 5_000, // fail fast instead of hanging});# SQLAlchemy: the real ceiling here is 8, not 5engine = create_engine( os.environ["DATABASE_URL"], pool_size=5, max_overflow=3, pool_timeout=5, # seconds to wait for a connection pool_recycle=1800, # reconnect before anything else drops it pool_pre_ping=True, # check liveness before handing one out)შექმენი pool ერთხელ, მოდულის დონეზე ან აპლიკაციის გაშვებისას, და არასოდეს მოთხოვნის handler-ში. თითო მოთხოვნაზე შექმნილი pool pool არ არის; ეს კავშირია დამატებითი ნაბიჯებით, და ეს პატარა კოდის ბაზებში ამის არასწორად გაკეთების ყველაზე გავრცელებული გზაა.
Timeout-ები: სწრაფი მარცხი დაგროვების ნაცვლად#
pool timeout-ების გარეშე ნელ ბაზას გაჭედილ აპლიკაციად აქცევს. მნიშვნელობა ოთხ timeout-ს აქვს და მათ სხვადასხვა მნიშვნელობა უნდა ჰქონდეს.
- Acquire timeout (
connectionTimeoutMillis,pool_timeout,connectionTimeout). რამდენ ხანს ელოდება მოთხოვნა თავისუფალ კავშირს, სანამ თავს დაანებებს. დააყენე რამდენიმე წამი. მოთხოვნა, რომელმაც უკვე ხუთი წამი იცადა, არის მოთხოვნა, რომელსაც ვერავინ უყურებს, და მისი ჩავარდნა სლოტს იმას უთავისუფლებს, რომელიც მნიშვნელოვანია. - Statement timeout. ბაზაზე ან სესიაზე დაყენებული, ის კლავს მოთხოვნას, რომელიც ძალიან დიდხანს მუშაობს. ვებ ტრაფიკისთვის
SET statement_timeout = '10s', ანგარიშებისთვის მეტი, მიგრაციებისთვის გამორთული. - Idle in transaction timeout.
idle_in_transaction_session_timeoutნაგულისხმევად0-ია, ანუ არასოდეს, და ამ ნაგულისხმევმა უფრო მეტი გათიშვა გამოიწვია, ვიდრე სხვა რამ. კავშირი, რომელმაც ტრანზაქცია გახსნა და მერე წავიდა, თავის ლოქებს იჭერს და vacuum-ს განუსაზღვრელი ვადით ბლოკავს. ოცდაათი წამი გულუხვია. - მაქსიმალური სიცოცხლის ხანგრძლივობა (
maxLifetime,pool_recycle). დახურე და ხელახლა გახსენი კავშირები პერიოდულად, რომ firewall-მა, proxy-მ ან failover-მა არ დაგტოვოს სოკეტებით, რომლებიც მეორე მხარეს უკვე დავიწყებია.
-- Sensible defaults for an application role, set onceALTER ROLE app_user SET statement_timeout = '15s';ALTER ROLE app_user SET idle_in_transaction_session_timeout = '30s';ALTER ROLE app_user SET lock_timeout = '3s';მათი როლზე და არა აპლიკაციის კოდში დაყენება ნიშნავს, რომ ისინი ყველა კავშირზე მოქმედებს, იმასაც ჩათვლით, რომელსაც ვინმე ლეპტოპიდან ღამის ორზე ხსნის. რომელი როლი გამოიყენო და რატომ არ უნდა იყოს ის superuser, ეს არის Postgres-ის როლები და უფლებები.
ოთხივეს აზრი ერთია: შემოუსაზღვრელი ლოდინი გადააქციე სწრაფ, ხილულ შეცდომად, რომელსაც შენი health check-ები და ლოგები დაინახავენ. აპლიკაცია, რომელიც ორ წამში 503-ს აბრუნებს, აღდგენადია; ის, რომელიც ათი ათას მოთხოვნას ღიად ინახავს, სანამ load balancer-ის timeout არ ამოიწურება, - არა. ამ ქცევის მეორე ნახევარს გრაციოზული გამორთვა და health check-ები მოიცავს.
PgBouncer და როდის გჭირდება#
PgBouncer პატარა proxy-ია, რომელიც PostgreSQL-ის წინ დგას. აპლიკაციები მას უკავშირდებიან და ის ბაზასთან რეალური კავშირების ბევრად უფრო პატარა pool-ს ინახავს და საჭიროებისამებრ გასცემს. ის არსებობს, რადგან PostgreSQL-ის კავშირის მოდელი ძვირია და გარკვეული deployment ფორმები კავშირებს ათასობით ქმნის.
[databases]appdb = host=127.0.0.1 port=5432 dbname=appdb[pgbouncer]listen_port = 6432pool_mode = transactionmax_client_conn = 1000default_pool_size = 10pool-ის სამი რეჟიმი, და არჩევანი მთელი გადაწყვეტილებაა:
session- კლიენტი თავის სერვერულ კავშირს ინახავს, სანამ არ გაითიშება. უსაფრთხოა, ყველაფერთან თავსებადია და თითქმის არაფერს გიზოგავს.transaction- სერვერული კავშირი თითოეული ტრანზაქციის ბოლოს ბრუნდება. ეს არის რეჟიმი, რომელიც ღირს, და ის, რომელიც ათას კლიენტს ათ backend-ზე ამრავლებს.statement- ბრუნდება ყოველი statement-ის შემდეგ. ხურავს მრავალ-statement-იან ტრანზაქციებს. იშვიათად შეესაბამება.
transaction რეჟიმს პირობები აქვს, რადგან ყველაფერი, რაც ტრანზაქციაში კი არა, სესიაში ცხოვრობს, საიმედო აღარ არის: SET statement-ები, სესიის დონის advisory ლოქები, LISTEN/NOTIFY და ტრანზაქციის გარეთ ჩარჩენილი cursor-ები. სერვერული prepared statement-ები წლების განმავლობაში პრობლემა იყო; PgBouncer-ის ბოლო ვერსიები მათ პროტოკოლით max_prepared_statements-ის საშუალებით უჭერენ მხარს, მაგრამ დაეყრდნობამდე შეამოწმე შენი ვერსია.
PgBouncer გჭირდება, როცა გაქვს ბევრი მოკლე სიცოცხლის პროცესი, რომელთაგან თითოეულს კავშირი უნდა - PHP სტეკი ასობით worker-ით, პლატფორმა, რომელიც თითო მოთხოვნაზე პროცესს ქმნის, ან რამდენიმე აპლიკაცია, რომლებიც ერთ ბაზას იზიარებენ. არ გჭირდება ერთი Node ან Python აპლიკაციისთვის სწორად ზომადი pool-ით, სადაც ის იქნებოდა მეორე პროცესი გასაშვებად ყოველგვარი სარგებლის გარეშე. მისი „მასშტაბისთვის“ დამატება ოთხი აპლიკაციის პროცესის გაშვებისას კლასიკური შემთხვევაა პრობლემის გადაჭრისა, რომელიც არ გაგაჩნია.
leak-ის პოვნა#
pool, რომელიც საათების განმავლობაში ნელა იწურება და გადატვირთვაზე აღდგება, სიმძლავრეს არ აკლდება. მას აქვს გზა კოდში, რომელიც კავშირს იღებს და არასოდეს აბრუნებს.
ნიმუში ყოველთვის ერთია: ადრეული return, გადაგდებული exception ან ბრენჩი, რომელიც release-ს გამოტოვებს. ყველგან, სადაც ხედავ ხელით connect-ს release-ის გარეშე finally ბლოკში ან context manager-ში, გყავს კანდიდატი.
// The leak: an exception here never releases the clientconst client = await pool.connect();const rows = await client.query(sql); // throwsclient.release();// The fixconst client = await pool.connect();try { return await client.query(sql);} finally { client.release();}უკეთესი ისაა, რომ კავშირები საერთოდ ხელით არ აიღო. node-postgres-ში pool.query(), Python-ში with ბლოკი, context manager ან repository-ს ფენა - ყველა მათგანი release-ს ავტომატურს ხდის და არცერთის დავიწყება არ შეიძლება მოგვიანებით დამატებულ კოდის გზაში.
იმის დასადასტურებლად, რასაც ხედავ, ჰკითხე ბაზას და ნუ იგულისხმევ:
-- Who is connected, and what are they doing?SELECT state, count(*) FROM pg_stat_activity GROUP BY state;-- The dangerous ones: open transactions doing nothingSELECT pid, usename, state, now() - state_change AS idle_for, left(query, 60) AS last_queryFROM pg_stat_activityWHERE state = 'idle in transaction'ORDER BY idle_for DESC;// MongoDB: current, available and total ever createddb.serverStatus().connectionsidle in transaction სესიების გროვა გაჟონილი ტრანზაქციაა და არა გაჟონილი კავშირი, და ეს უარესია: ის ლოქებს იჭერს და vacuum-ს გაწმენდაში უშლის ხელს. უბრალო idle სესიების გროვა, რომელიც შენს კონფიგურირებულ მაქსიმუმს უდრის, დასვენებაში მყოფი ჯანსაღი pool-ია. totalCreated-ის ზრდა MongoDB-ზე, ან კავშირები, რომლებიც ტრაფიკის გასწორების შემდეგაც ადიან, არის ნიშანი იმისა, რომ pool სადღაც ხელახლა იქმნება გამოყენების ნაცვლად.
pool-იდან სამი რიცხვი გაიტანე და უყურე: გამოყენებაში მყოფი კავშირები, მოლოდინში მყოფი მოთხოვნები და კავშირის აღების მოლოდინის 99-ე პერცენტილი. ერთი გამოყენება არაფერს გეუბნება - 100 პროცენტზე მყოფი pool, რომელსაც არავინ ელოდება, სრულყოფილად ზომადია. მოლოდინის დრო არის მეტრიკა, რომელსაც აზრი აქვს, და მონიტორინგი, რომელიც რაღაცას გეუბნება ზოგად არგუმენტს გვაძლევს მეტრიკების ასე არჩევისთვის. თუ მოლოდინის დრო მაღალია, ბაზა დაკავებულია ან მოთხოვნები ნელია, და როდის გაიუმჯობესო გეგმა ამ ორის გარჩევით იწყება.
FAQ#
რას ნიშნავს "sorry, too many clients already"?
რომ PostgreSQL-მა max_connections-ს მიაღწია და ახალი სესია უარყო. დათვალე შენი აპლიკაციის პროცესები გამრავლებული მათი pool-ის ზომაზე, პლუს worker-ები, cron დავალებები და ნებისმიერი კლიენტი, რომელიც ღიად გაქვს. ეს ჯამი ლიმიტზე მეტია და გამოსწორება თითქმის ყოველთვის უფრო პატარა pool-ია და არა უფრო მაღალი ლიმიტი.
უბრალოდ გავზარდო max_connections?
იშვიათად. ყოველი კავშირი საკუთარი მეხსიერების მქონე პროცესია, ამიტომ ლიმიტის გაზრდა მოთხოვნებისთვის საჭირო მეხსიერებას ცვლის კავშირებზე, რომლებიც უქმად იქნებიან. გაზარდე მხოლოდ მაშინ, როცა დარწმუნებული ხარ, რომ ყველა კავშირი სასარგებლო საქმეს აკეთებს, და ჯერ შენი pool შეამცირე.
რა არის კარგი pool-ის ზომა?
დაიწყე დაახლოებით ბაზის CPU ბირთვების ორმაგი რაოდენობით, გადანაწილებული შენი ყველა აპლიკაციის პროცესზე, პლუს რამდენიმე რეზერვი მიგრაციებისა და ადმინისტრირებისთვის. პატარა აპლიკაციისთვის 2 vCPU ბაზაზე ეს სულ დაახლოებით 8 კავშირია - და არა თითო პროცესზე.
მჭირდება PgBouncer?
მხოლოდ თუ გაქვს ბევრი მოკლე სიცოცხლის პროცესი და თითოეულს კავშირი უნდა: PHP სტეკი დიდი pm.max_children-ით, თითო მოთხოვნაზე პროცესის მქონე პლატფორმა ან რამდენიმე აპლიკაცია ერთ ბაზაზე. ერთი კარგად pool-ირებული აპლიკაცია სარგებელს არ იღებს, და transaction pooling შეზღუდავს სესიის შესაძლებლობებს, რომლებსაც შესაძლოა იყენებდე.
რატომ იწურება ჩემი pool ღამით, მაგრამ გადატვირთვის შემდეგ მუშაობს?
ეს leak-ია. კოდის გზა კავშირს იღებს და არასოდეს აბრუნებს, ჩვეულებრივ იმიტომ, რომ exception-მა release გამოტოვა. თითოეული ხელით აღება გახვიე finally-ში ან context manager-ში და ამჯობინე pool-ის საკუთარი query helper, რომ release-ის დავიწყება შეუძლებელი იყოს.
MongoDB-ს იგივე პრობლემა აქვს?
იგივე ფორმა, ნაკლებად მძიმე. მისი კავშირები პროცესების ნაცვლად thread-ებია, მაგრამ driver-ის ნაგულისხმევი maxPoolSize 100 თითო პროცესზე მაინც მრავლდება შენს აპლიკაციაზე. შეამცირე, დააყენე wait queue timeout და ეჭვის შემთხვევაში შეამოწმე db.serverStatus().connections.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.