PgBouncer थ्रूपुट को 4 गुना तक स्केल कैसे किया गया
(clickhouse.com)- ClickHouse Managed Postgres में single-threaded PgBouncer के एक CPU core तक सीमित हो जाने की सीमा से बचने के लिए, core की संख्या के अनुपात में multi-process fleet को default configuration के रूप में चलाया जाता है
- सभी processes
so_reuseportके जरिए उसी port से bind होते हैं और kernel नए connections को distribute करता है, इसलिए clients को यह single endpoint जैसा दिखता है - अलग connection के जरिए आने वाले Postgres cancel requests उस process तक पहुंच सकते हैं जिसके पास session नहीं है, इसलिए process peering के जरिए उन्हें वास्तविक session owner तक forward किया जाता है
- 16-vCPU
c7i.4xlargeपर single process लगभग 87,000 TPS पर peak हुआ, जबकि 16-process fleet लगभग 336,000 TPS तक पहुंची, यानी करीब 4 गुना throughput दर्ज किया - Connections कम हों तो single process समान या थोड़ा तेज हो सकता है, लेकिन high concurrency में एक core bottleneck बन जाता है, इसलिए connection budget को पूरी fleet में बांटकर Postgres में excessive connections के बिना throughput और connection limit को scale करना चाहिए
Single process से बनने वाला CPU bottleneck
- PgBouncer single-threaded है, इसलिए system में CPU की संख्या चाहे जितनी हो, एक process केवल एक core ही इस्तेमाल करता है
- 16-vCPU server पर भी connection pooling का काम एक core पर केंद्रित हो जाता है और बाकी 15 cores idle रहते हैं
- इस वजह से Postgres की processing capacity खत्म होने से पहले ही pooler overall throughput को limit कर देता है
- ClickHouse Managed Postgres उपलब्ध cores की संख्या के अनुपात में PgBouncer processes की संख्या तय करने वाली process fleet चलाता है
so_reuseport के जरिए connection distribution
- Fleet के सभी processes
so_reuseportenable करते हैं और same port पर bind होते हैं - Kernel incoming connections को हर process में distribute करता है, इसलिए client एक endpoint से connect करता है और उसे यह जानने की जरूरत नहीं होती कि पीछे कई PgBouncer चल रहे हैं
- PgBouncer documentation भी single-threaded processes के कई instances से multiple cores का उपयोग करने के तरीके के रूप में
so_reuseportबताती है
Query cancellation सुनिश्चित करने वाली process peering
- Postgres के cancel requests running query connection से अलग एक नए connection के जरिए cancel key के साथ आते हैं
so_reuseportइस्तेमाल करने पर kernel नया cancel connection उस process के बजाय किसी दूसरे process को दे सकता है जिसके पास वह session है- अगर request पाने वाला process उस query को नहीं जानता, तो cancellation execute नहीं होती
- PgBouncer processes के बीच peering करने पर गलत process पर पहुंचे request को वास्तविक session-owning process तक forward किया जा सकता है
- इससे कोई भी process request पाए, पूरी fleet में query cancellation सही ढंग से काम करती है
Transaction pooling और connection budget
- Pooling transaction mode में काम करती है, और transaction commit होते ही server connection pool में लौट जाता है
max_client_connऔरmax_db_connectionsको processes की संख्या से बांटकर हर process को allocate किया जाता है- Connection budget को split करने से fleet के कुल connections को Postgres की safe limit के भीतर रखते हुए aggregate connection limit बढ़ाई जा सकती है
- Single process अपने
max_client_connसे अधिक नए clients को नीचे दिए error के साथ reject करता हैFATAL: no more connections allowed (max_client_conn)
समान hardware benchmark
- दोनों configurations को समान AWS EC2 environment में मापा गया
- Pooler server: 16-vCPU
c7i.4xlarge - Postgres: अलग server
- Load generator: तीसरे server से
pgbenchका उपयोग - Workload: read-only(select-only), transaction pooling mode
- Pooler server: 16-vCPU
- तुलना PgBouncer single process और 16-process fleet के बीच है; instance type, Postgres और workload समान हैं
- Client connections को 8 से 256 तक बढ़ाते हुए throughput और 16-core server का CPU usage मापा गया
| Clients | Single process TPS | Single process server CPU | Fleet TPS | Fleet server CPU |
|---|---|---|---|---|
| 8 | 8,910 | 0.8% | 6,450 | 2.9% |
| 32 | 54,203 | 5.2% | 64,244 | 12.3% |
| 64 | 86,570 | 8.3% | 219,439 | 31.9% |
| 128 | 83,463 | 8.1% | 320,547 | 45.9% |
| 256 | 76,893 | 7.7% | 336,469 | 48.9% |
Throughput और CPU उपयोग के नतीजे
- Single process लगभग 87,000 TPS पर peak होने के बाद load बढ़ने पर performance घट गई, और 256 clients पर लगभग 77,000 TPS दर्ज किया
- 16-process fleet ने ज्यादा cores का उपयोग करते हुए लगभग 336,000 TPS तक वृद्धि की, जो single process की तुलना में करीब 4 गुना throughput है
pidstatmeasurement में single PgBouncer process ने load के दौरान लगभग 97% CPU के साथ एक core पूरी तरह इस्तेमाल किया, लेकिन 16-vCPU server का overall usage 10% से कम रहा- Fleet ने काम को पूरे server में distribute करके लगभग 8 cores इस्तेमाल किए, और Postgres व load generator के limit पर पहुंचने के बाद भी अतिरिक्त headroom बची रही
- 256 clients बनाए रखकर किए गए measurement में single-process server ने लगभग 9%, और fleet server ने लगभग 52% CPU लगातार इस्तेमाल किया
- EC2 CloudWatch की external measurement में single-process instance का average CPUUtilization लगभग 16% और fleet का लगभग 60% था
- CloudWatch numbers guest-internal measurements से थोड़े अधिक हैं, लेकिन यह अंतर समान रहता है कि single PgBouncer 16-vCPU के अधिकांश हिस्से का उपयोग नहीं कर पाता
Concurrency के अनुसार configuration चुनना
- Connection count कम होने पर parallelize करने के लिए काम नहीं होता और fleet के connections कई processes में distribute हो जाते हैं, इसलिए single process पर्याप्त या थोड़ा तेज हो सकता है
- 8 clients पर single process ने 8,910 TPS और fleet ने 6,450 TPS दर्ज किया
- Concurrency बढ़ने पर single process द्वारा कब्जा किया गया एक core throughput की दीवार बन जाता है, और fleet के साथ performance gap बढ़ता है
- जिन environments में PgBouncer से पहले Postgres limit पर पहुंचता है, वहां single process भी उपयुक्त default है
- जब pooler throughput limit करना शुरू करे, तो bottleneck दूर करने के लिए core count के अनुरूप process fleet,
so_reuseportआधारित port sharing, और process peering को साथ में इस्तेमाल करना चाहिए - सभी ClickHouse Managed Postgres servers में यह configuration default रूप से मिलता है
1 टिप्पणियां
Hacker News टिप्पणियाँ
बस https://github.com/yandex/odyssey इस्तेमाल करें। Odyssey एक scalable PgBouncer है
हमारे कई ग्राहक PostgreSQL कनेक्शनों को 10,000 से अधिक तक scale कर चुके हैं, और आगे चलकर Odyssey या pgdog जैसे विकल्पों पर भी विचार करने की योजना है। हालांकि व्यक्तिगत रूप से मुझे PostgreSQL में 10,000 से ज़्यादा कनेक्शन रखना पसंद नहीं है, और मेरा मानना है कि कुछ सौ कनेक्शनों से भी पर्याप्त scale किया जा सकता है
Kubernetes में PgBouncer चलाने पर एक मशीन पर कई processes चलाना भी आसान होता है, और कई मशीनों में distribute करना भी सरल होता है
Azure VM maintenance के दौरान पूरे server fleet में क्रमिक failures पैदा कर देता है, इसलिए multi-machine setup खास तौर पर उपयोगी है
मैं यहाँ peering की अवधारणा समझता हूँ, लेकिन PostgreSQL में इसे कभी इस्तेमाल नहीं किया
A) क्या PostgreSQL में इसे आसानी से configure करने के लिए कोई mode या setting है? मैं ऐसी संरचना की कल्पना कर रहा हूँ जहाँ cancel requests को peers तक क्रमवार भेजा जाए जब तक error न मिले, या cancel request में ऐसा metadata हो जिससे गलत request पाने वाला process सही process की पहचान कर सके
B) अगर सभी PostgreSQL processes
so_reuseportके साथ client requests ले रहे हों, तो peers के बीच communication के लिए अलग inter-process communication (IPC) mechanism चाहिए होगा; व्यवहार में कौन-सा mechanism इस्तेमाल होता है?अगर PgBouncer process ID को cancel key में शामिल कर दिया जाए, तो इसे काफ़ी आसानी से implement किया जा सकता है
26वीं slide से देख सकते हैं: https://www.pgevents.ca/events/pgconfdev2024/sessions/sessio...
PgBouncer maintainer Jelte की talk video भी है: https://www.youtube.com/watch?v=X-nCHcZ6vQU
मैं pgdog इस्तेमाल कर रहा हूँ, और यह हमारी ज़रूरत के हिसाब से बहुत अच्छी तरह फिट बैठता है
मैं जानना चाहता हूँ कि PgBouncer को cancel requests तक की चिंता क्यों करनी पड़ती है। क्या इसे बस PostgreSQL तक पास नहीं कर देना चाहिए, और अगर canceled query पर PostgreSQL सामान्य response की जगह error लौटा दे, तो उसे वह PgBouncer संभाल ले जो उस connection को चला रहा है?
PgBouncer client को नकली लेकिन traceable cancel PID और secret value देता है, और जब cancel request वापस आती है तो वह असली server/process का PID और secret value खोजकर आगे भेजता है। साथ ही ऐसी स्थिति भी हो सकती है जहाँ client को लगता है कि query अभी चल रही है, लेकिन PgBouncer जानता है कि वह पहले ही खत्म हो चुकी है और connection reuse हो चुका है, इसलिए यह भी जाँचना पड़ता है कि वह connection और PID अभी भी उसी query को चला रहे हैं या नहीं
क्या यह microservices में connection pool के ज़रिए PostgreSQL server तक पहुँच को नियंत्रित करने के लिए है? अगर monolithic backend हो, तो इसकी ज़रूरत नहीं लगती
अधिकांश अच्छे backend frameworks में connection pooling built-in होता है, और उन 98% use cases के लिए जहाँ microservices की न तो ज़रूरत होती है और न ही सिफारिश, वही काफ़ी है
PgBouncer बेहतरीन software है। मैं इसका काफी इस्तेमाल करता हूँ और यह database operations को काफी सरल बना देता है
मुझे अभी
so_reuseportके बारे में पता चला, और यह दिलचस्प है। लगता है setup की कुंजी यही और peering हैं; क्या peering PgBouncer में built-in है और क्या इसे configure करना आसान है?क्या Kubernetes में भी peering इस्तेमाल किया जा सकता है? इस environment में ports reuse करने की ज़रूरत नहीं होगी, तो क्या हर Pod का अपना अलग connection pool होगा और वह स्वतंत्र रूप से काम करेगा?
मेरी उम्र अब 46 है, और मुझे याद है कि 23 साल की उम्र में PostgreSQL का heavy connection model देखकर मैं चौंक गया था। क्या उसके बाद भी इसमें सुधार नहीं हुआ?
लेकिन connection pool न होने पर PostgreSQL को process fork करना पड़ता है, इसलिए नया connection बनाने में हमेशा कई दर्जन milliseconds या उससे अधिक की लागत आती है। connection pool के बिना लिखे गए applications आदर्श नहीं हैं, फिर भी आम तौर पर मिल जाते हैं
application frameworks भी बदल गए हैं, और serverless architectures ऐसे दसियों हज़ार connections बना सकते हैं जहाँ PostgreSQL को दिक्कत शुरू होने लगती है। व्यक्तिगत रूप से मुझे कुछ सौ से ज़्यादा connections पसंद नहीं हैं, लेकिन आजकल यह पूरी तरह यथार्थवादी स्थिति है
Java compatibility हासिल करने की प्रक्रिया में कई कंपनियों को concurrency handle करने के अपने तरीकों में बड़े बदलाव करने पड़े। PostgreSQL और SQLite भी लगभग उसी दौर में डिजाइन हुए थे, लेकिन इन्हें ऐसे अनुभवी उद्योग विशेषज्ञों ने बनाया था जो इन threading बहसों से पहले से ही high-load systems deploy कर रहे थे और पुराने hardware उपयोगकर्ताओं तक को support कर रहे थे