SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-21

4 GB VPS-এ PostgreSQL connection pooling কেন দরকার

PostgreSQL-এর প্রতিটি connection আলাদা process। তাই 4 GB VPS-এ max_connections=100-এর আগেই RAM শেষ হতে পারে। pooler কী ঠিক করে এবং কী ভাঙে জানুন।

4 GB VPS-এ max_connections সীমায় পৌঁছানোর আগেই RAM শেষ হয়ে যায় কেন

VPS-এ PostgreSQL connection pooling গতি বাড়ানোর কৌশল নয়। এটি 4 GB মেশিন সচল রাখার উপায়, কারণ প্রতিটি PostgreSQL connection একটি পৃথক operating system process এবং নিজের private memory ধরে রাখে। একটি pooler অল্প, নির্দিষ্ট সংখ্যক প্রকৃত backend process-এর পেছনে বিপুল সংখ্যক কম-খরচের client connection পরিচালনা করে।

ডিফল্ট max_connections হলো 100। এটি একটি সীমা, কোনো memory budget নয়। PostgreSQL আপনার মেশিনে সত্যিই real query চালানো 100টি backend ধারণ করতে পারবে কি না তা যাচাই করে না। তাই আগে মেশিনের memory শেষ হয়। Kernel-এর out of memory (OOM) killer একটি process নির্বাচন করে। এটি কোনো backend নির্বাচন করলে PostgreSQL পুরো cluster restart করে, যাতে shared memory নিরাপদ থাকে। লগে server process (PID 1234) was terminated by signal 9: Killed, তারপর terminating any other active server processes দেখা যায়। সুস্থ connection-সহ প্রতিটি খোলা connection বন্ধ হয়ে যায়।

প্রতিটি connection একটি process হওয়ায় এবং work_mem প্রতি connection অনুযায়ী নয়, বরং প্রতিটি sort বা hash operation-এর জন্য বরাদ্দ হওয়ায় মেশিনের memory শেষ হয়। এই দুই বিষয়ের প্রভাব পরস্পরকে বাড়িয়ে দেয়।

প্রতিটি connection একটি process, এবং প্রতিটি process-এর জন্য memory লাগে

PostgreSQL প্রতিটি connection-এর জন্য একটি করে process ব্যবহার করে। কোনো client connect করলে postmaster একটি backend fork করে, এবং client disconnect না করা পর্যন্ত সেই backend চালু থাকে। এটি thread নয়। এর নিজস্ব page table, catalog cache এবং cached query plan থাকে। connection আরও বেশি table ব্যবহার করলে এবং আরও বেশি আলাদা query চালালে এই cache-গুলো বড় হয়। তাই ব্যস্ত ORM application-এ দীর্ঘক্ষণ চালু থাকা একটি connection-এর খরচ নতুন connection-এর চেয়ে বেশি।

Shared memory সত্যিই shared। shared_buffers পুরো cluster-এর জন্য একটি allocation, যা প্রতিটি backend-এ map করা থাকে। Private memory shared নয়। তাই এখানে top আপনাকে বিভ্রান্ত করতে পারে: কোনো backend-এর resident set size (RSS)-এর মধ্যে সেই backend ব্যবহার করা shared page-গুলোও থাকে। ফলে 50টি backend-এর RSS যোগ করলে shared_buffers 50 বার গণনা করা হয়।

এর বদলে private অংশ মাপুন। PSS (proportional set size) প্রতিটি shared page-কে সেটি যতটি process map করেছে, সেই সংখ্যা দিয়ে ভাগ করে। USS (unique set size) শুধু সেই process-এর নিজস্ব page-গুলো গণনা করে।

sudo apt update
sudo apt install -y smem
sudo smem -k -P '^postgres'

USS column-এ দেখানো memory ওই backend বন্ধ হলে যে memory মুক্ত হবে। এটিই আপনার প্রকৃত per-connection খরচ। প্রকাশিত পরিসংখ্যান অনুযায়ী, একটি idle backend-এর memory সাধারণত এক অঙ্কের megabyte-এ থাকে। বড় ORM query চালানো একটি backend-এর memory এর কয়েক গুণ হতে পারে। এগুলোকে সাধারণ প্রকাশিত পরিসংখ্যান হিসেবে বিবেচনা করুন, আপনার server-এর নির্দিষ্ট মান হিসেবে নয়। পরিকল্পনার জন্য কেবল আপনার workload-এ আপনার server থেকে পাওয়া মান ব্যবহার করুন।

একটি per-session allocation সহজেই চোখ এড়িয়ে যায়। temp_buffers-এর default মান 8MB। কোনো session প্রথমবার temporary table ব্যবহার করলে এটি ওই session-এর জন্য allocate হয়। session শেষ না হওয়া পর্যন্ত এই memory ফেরত দেওয়া হয় না।

work_mem প্রতি connection-এর জন্য নয়, প্রতি operation-এর জন্য বরাদ্দ হয়

এখানেই হিসাবটি অনেকের নজর এড়িয়ে যায়। work_mem-এর default হলো 4MB, এবং PostgreSQL documentation-এ এর অর্থ সরাসরি বলা হয়েছে: “একটি জটিল query একই সময়ে একাধিক sort ও hash operation চালাতে পারে, এবং প্রতিটি operation সাধারণত এই value যত memory নির্দিষ্ট করে ততটা ব্যবহার করতে পারে, তারপর temporary file-এ data লেখা শুরু করে।” তিনটি sort node থাকা একটি plan একই backend-এর মধ্যে একই সময়ে work_mem-এর তিন গুণ memory ব্যবহার করতে পারে।

Hash operation-এর ক্ষেত্রে ব্যবহার আরও বেশি হয়। hash_mem_multiplier-এর default হলো 2.0। তাই একটি hash join বা hash aggregate work_mem-এর দুই গুণ ব্যবহার করতে পারে, অর্থাৎ stock setting-এ 8MB। Parallel query-তে এই পরিমাণ আবার গুণ হয়, কারণ প্রতিটি parallel worker নিজস্ব allowance-সহ একটি আলাদা process।

4 GB VPS-এর জন্য হিসাবটি করুন। shared_buffers-কে 1 GB নির্ধারণ করুন, work_mem-কে 4MB রাখুন, এবং 100টি connection-কে প্রত্যেককে দুইটি hash node-সহ একটি করে query চালাতে দিন। এর অর্থ 100 গুণ 16MB, অর্থাৎ 1 GB shared buffers-এর অতিরিক্ত 1.6 GB private memory; এর মধ্যে page cache বা সার্ভারের অন্য কোনো memory usage ধরা হয়নি। এখন সার্ভারে অতিরিক্ত RAM আছে বলে work_mem-কে 64MB করুন। একই 100টি connection তখন 100 গুণ 256MB memory ব্যবহারের সম্ভাবনা তৈরি করে। কোনো warning পাবেন না। OOM killer কাজ করার সময় বিষয়টি বুঝতে পারবেন।

অনুমান না করে work_mem খুব ছোট কি না তা পরীক্ষা করতে পারেন। log_temp_files = 0-কে postgresql.conf-এ নির্ধারণ করে reload করুন। এরপর disk-এ প্রতিটি spill হলে file-এর নাম ও size-সহ একটি line লেখা হবে, যেমন temporary file: path "base/pgsql_tmp/pgsql_tmp1234.0", size 20971520। ঘন ঘন spill হলে বেশি work_mem উপকারী হতে পারে। কোনো spill না হলে মান বাড়িয়ে কোনো লাভ নেই; বরং আপনার কাছে না থাকা memory খরচ হবে।

বাস্তবে যে pool-এর হিসাব সমস্যায় ফেলে

কেউ শুরুতেই 240টি connection খোলার পরিকল্পনা করে না। তারা 20টির pool configure করে, তারপর application-টি একাধিক জায়গায় চালায়।

ChartBackends requested per app topology, pool size 20 per worker (arithmetic)
The data behind this chart
[
  {
    "config": "1 worker",
    "backends": 20
  },
  {
    "config": "4 web workers",
    "backends": 80
  },
  {
    "config": "4 web + 2 background",
    "backends": 120
  },
  {
    "config": "2 hosts x 4 workers",
    "backends": 160
  },
  {
    "config": "3 hosts x 4 workers",
    "backends": 240
  }
]

চারটি Gunicorn worker, প্রতিটি 20টির pool ধরে রেখে, 80 backend চায়। দুটি background job worker যোগ করলে সংখ্যাটি হয় 120। সংখ্যা 3 hosts x 4 workers-এ বাড়ালে application একটি max_connections-এর বিপরীতে 240 backend চায়, যার capacity 100। ওই 5টি setup-এর কোনোটিই একক কোনো জায়গায় misconfigured নয়। pool প্রতি process-এর জন্য আলাদা, এবং application-এর কোনো অংশ মোট সংখ্যা দেখতে পারে না।

Library-গুলোর default-ও একই দিকে ঠেলে দেয়। SQLAlchemy-এর QueuePool default হিসেবে pool_size=5, যেখানে max_overflow=10 সেট করা থাকে; অর্থাৎ প্রতি process-এ 15টি connection। HikariCP-এর default 10। Django-তে 5.1-এর আগে built-in pool ছিল না; প্রতি worker process-এ একটি connection থাকত। তাই Django application-গুলো এই সীমায় পরে পৌঁছায়, এবং কেউ CONN_MAX_AGE সেট করলে বা নতুন "pool": True option চালু করলে একসঙ্গে সীমা অতিক্রম করে। আপনি যদি Gunicorn ও nginx-এর পেছনে একটি Django app চালান, তাহলে গুণ করার সংখ্যা server count নয়, Gunicorn worker count।

VPS-এ Postgres connection pooling বাস্তবে কী পরিবর্তন করে

একটি pooler একদিকে আপনার application-এর সঙ্গে PostgreSQL wire protocol ব্যবহার করে এবং অন্যদিকে অল্পসংখ্যক প্রকৃত server connection ধরে রাখে। এটি কোনো query দ্রুত করে না। এটি পরিবর্তন করে connection-এর resource cost কে বহন করবে এবং প্রকৃত backend-এর সংখ্যা কত হবে।

দুটি বিষয় উন্নত হয়। Connection তৈরি করতে আর fork এবং খালি backend cache পূরণের জন্য catalog lookup-এর খরচ হয় না, কারণ pooler নিজেই client-এর connect request-এর উত্তর দেয়। আরও গুরুত্বপূর্ণ হলো, প্রকৃত backend-এর সংখ্যা আর application connection-এর সংখ্যার সঙ্গে তাল মিলিয়ে বাড়ে না। ফলে 500টি client 20টি backend ভাগ করে ব্যবহার করতে পারে।

অপেক্ষা করাই এই ব্যবস্থার মূল সুবিধা, এবং মানুষ সাধারণত এই বিষয়টিই মেনে নিতে চায় না। pooler ছাড়া 500টি concurrent query-ই একটি করে backend পায় এবং দুইটি CPU core-এ একই সময়ে চলে। ফলে প্রতিটি query ধীর হয় এবং একই মুহূর্তে পুরো memory ব্যবহৃত হয়। pooler থাকলে 20টি query চলে এবং বাকিগুলো কয়েক milliseconds অপেক্ষা করে। তাই চলমান প্রতিটি query CPU-এর বাস্তবসম্মত অংশ পায় এবং দ্রুত শেষ হয়। বড় pool-এর সামনে কোনো queue না থাকার চেয়ে ছোট pool-এর সামনে একটি queue বেশি কার্যকর।

pooler মেশিনের অন্য কোনো resource সীমাবদ্ধ করে না। Postgres যদি একই VPS-এ কোনো app server অথবা একই VPS-এ চলা কোনো vector database-এর সঙ্গে resource ভাগ করে, pooler শুধু আপনার application থেকে Postgres-কে সুরক্ষা দেয়; এর বেশি কিছু নয়। প্রতিবেশী service-গুলোর জন্যও কঠোর সীমা নির্ধারণ করুন: আপনি systemd দিয়ে কোনো service-এর memory ও CPU ব্যবহার সীমিত করতে পারেন, যাতে নিয়ন্ত্রণের বাইরে চলে যাওয়া কোনো process database-কে সঙ্গে টেনে নামাতে না পারে। Database নিজেই কোথায় চলছে, তার ওপর এই সীমা নির্ধারণের পদ্ধতি বদলে যায়। এটিই Docker-এ অথবা সরাসরি host-এ Postgres চালানোর ব্যবহারিক পার্থক্য।

সেশন পুলিং বনাম ট্রানজ্যাকশন পুলিং

একটি সেটিং বাকি সবকিছু নির্ধারণ করে, সেটি হলো pool_mode

সেশন পুলিংয়ে একটি server connection client connection-এর পুরো সময়জুড়ে সেই client-এর জন্য বরাদ্দ থাকে এবং client disconnect করলে pool-এ ফিরে যায়। সবকিছু কাজ করে, কারণ pooler এখানে একটি সাধারণ proxy হিসেবে কাজ করে। এতে connection তৈরির খরচ কমে, আর কিছু নয়। application যদি 200টি connection খোলে, আপনার এখনও 200টি backend প্রয়োজন হবে।

ট্রানজ্যাকশন পুলিংয়ে একটি server connection শুধু একটি transaction চলাকালীন client-এর জন্য বরাদ্দ থাকে। COMMIT বা ROLLBACK-এ এটি pool-এ ফিরে যায় এবং পরের অপেক্ষমাণ client connection পায়। এভাবেই 500টি client-কে 20টি backend-এ পরিচালনা করা যায়। একই কারণে কিছু বিষয় কাজ করা বন্ধ করে, এবং এটি নকশা অনুযায়ীই ঘটে: আপনার পরের statement আগের statement-এর চেয়ে ভিন্ন backend-এ চলতে পারে।

PgBouncer-এর default হলো pool_mode = session। এটি install করে কোনো পরিবর্তন না করলে আপনি শুধু কম খরচের অংশটি পাবেন, কার্যকর সুবিধাটি পাবেন না। তৃতীয় mode, statement, প্রতিটি statement-এর পর connection ফিরিয়ে দেয় এবং একাধিক statement-সম্বলিত transaction প্রত্যাখ্যান করে। কেন এটি প্রয়োজন তা সঠিকভাবে না জানা পর্যন্ত এই mode ব্যবহার করবেন না।

কোন কোন transaction mode বৈশিষ্ট্য ব্যর্থ হয় এবং কেন

নিচের সবকিছু একই কারণে ব্যর্থ হয়। এগুলো একটি নির্দিষ্ট backend-এর অভ্যন্তরীণ state-এর ওপর নির্ভর করে। কিন্তু transaction pooling একই backend-এ আপনাকে পরপর দুবার পাঠানোর নিশ্চয়তা দেয় না।

  • Session level-এ SET এবং RESETSET search_path, SET statement_timeout, SET TIME ZONE এবং SET ROLE যে backend ওই statement চালিয়েছে, সেখানেই কার্যকর হয় এবং আপনার পরবর্তী transaction শুরু হওয়ার আগেই হারিয়ে যায়। পরিবর্তে explicit transaction-এর ভেতরে SET LOCAL ব্যবহার করুন। এটি ওই transaction-এর scope-এ থাকে এবং তাই নিরাপদ।
  • LISTEN। Notification delivery সেই backend-এর সঙ্গে যুক্ত থাকে, যেখানে LISTEN চালানো হয়েছে। Transaction শেষ হলেই ওই backend অন্য client-কে দেওয়া হয়। Transaction mode-এ NOTIFY এখনও কাজ করে। তাই এই ব্যর্থতা বিভ্রান্তিকর: পাঠানো সফল হয়, কিন্তু গ্রহণ কখনও হয় না। LISTEN প্রয়োজন হলে port 5432-এ pooler এড়িয়ে সরাসরি আরও একটি connection খুলুন।
  • Session-level advisory lock। pg_advisory_lock() session-এর সঙ্গে যুক্ত থাকে এবং session শেষ হলে release হয়। Transaction pooling-এ আপনার unlock call অন্য backend-এ চলে, তাই lock ধরে রাখা থাকে যতক্ষণ না PgBouncer সেই server connection-টি অব্যবহৃত হিসেবে সরিয়ে দেয়। ডিফল্টভাবে এটি server_lifetime পরে হয়, অর্থাৎ এক ঘণ্টা পরে। পরিবর্তে pg_advisory_xact_lock() ব্যবহার করুন। যে backend lock নিয়েছে, সেটিই transaction শেষ হলে lock-টি release করে।
  • PREPARE এবং DEALLOCATE, অর্থাৎ SQL statement-গুলো। এগুলো transaction mode-এ কখনও পাওয়া যায় না।
  • WITH HOLD cursor এবং transaction শেষ হওয়ার পরও চালু থাকবে বলে প্রত্যাশিত যেকোনো server-side cursor।
  • Commit-এর পরও টিকে থাকার জন্য তৈরি temporary table। CREATE TEMP TABLE ... ON COMMIT PRESERVE ROWS table-টিকে একটি backend-এর temporary schema-তে রাখে। আপনার পরবর্তী transaction সেই backend-এ নাও চলতে পারে।
  • LOAD

Protocol-level prepared statement একমাত্র বিষয়, যার সমর্থন পরে পরিবর্তিত হয়েছে। PgBouncer 1.21.0 transaction mode-এ এগুলোর জন্য support যোগ করে। PgBouncer 1.24.0-এ max_prepared_statements-এর মান 200 সেট করে এটি ডিফল্টভাবে চালু করা হয়। পুরোনো build-গুলোতে এর মান 0 থাকে, অর্থাৎ বৈশিষ্ট্যটি বন্ধ থাকে। Ubuntu 24.04-এর সঙ্গে PgBouncer 1.22.0 দেওয়া হয়। তাই বৈশিষ্ট্যটি আছে, কিন্তু আপনাকে নিজে max_prepared_statements সেট করতে হবে। আপনার build কীভাবে কাজ করছে তা নিশ্চিত না হলে client-এ নিরাপদ setting ব্যবহার করুন: prepare_threshold-এর মান None সেট করলে psycopg 3 server-side prepared statement ব্যবহার বন্ধ করে।

Django এই বিষয়টির নিজস্ব নাম ব্যবহার করে। Documentation-এ বলা হয়েছে, “transaction pooling mode-এ connection pooler ব্যবহার করলে, যেমন PgBouncer, ওই connection-এর জন্য server-side cursor বন্ধ করতে হয়”, কারণ “server-side cursor যে connection-এ তৈরি হয়, শুধু সেই connection-এই ব্যবহার করা যায়”। ওই database entry-তে DISABLE_SERVER_SIDE_CURSORS-এর মান True সেট করুন। তা না হলে প্রতিটি .iterator() call intermittent failure-এ পরিণত হবে, যা শুধু load-এর অধীনে দেখা যায়।

Transaction mode ব্যবহার করা উপযোগী, তবে এটি একটি নির্দিষ্ট contract। তালিকাটি পড়ুন, আপনার ORM এবং background job library-এর সঙ্গে সামঞ্জস্য পরীক্ষা করুন, তারপর এটি চালু করুন।

PgBouncer ইনস্টল করুন এবং অ্যাপ্লিকেশনকে এর মাধ্যমে সংযুক্ত করুন

নিচের configuration-টি আপনার নিজের server-এ চালানোর জন্য দেওয়া হয়েছে: Ubuntu 24.04, যেখানে PostgreSQL ইতিমধ্যে 127.0.0.1 port 5432-এ listening করছে।

sudo apt update
sudo apt install -y pgbouncer
pgbouncer --version

Ubuntu 24.04-এ PgBouncer 1.22.0 package হিসেবে রয়েছে। August 2026 অনুযায়ী upstream version হলো 1.25.2। আপনার কাছে কোন version আছে তা পরীক্ষা করুন, কারণ উপরের prepared statement-এর আচরণ এর ওপর নির্ভর করে।

এমন একটি role তৈরি করুন যার একমাত্র কাজ PgBouncer admin console-এ login করা। এরপর password file তৈরি করুন। PgBouncer-এর pg_authid থেকে SCRAM (salted challenge response authentication mechanism) secret প্রয়োজন। এই table পড়তে পারে শুধু superuser।

sudo -u postgres psql -c "CREATE ROLE pgb_admin LOGIN PASSWORD 'change-this'"
sudo -u postgres psql -At -c \
  'SELECT format($$"%s" "%s"$$, rolname, rolpassword) FROM pg_authid WHERE rolpassword IS NOT NULL' \
  > /tmp/userlist.txt
sudo install -o postgres -g postgres -m 640 /tmp/userlist.txt /etc/pgbouncer/userlist.txt
rm /tmp/userlist.txt

Password আবার টাইপ না করে secret copy করলেই এই পদ্ধতি কাজ করে। PgBouncer PostgreSQL-এ login করার জন্য SCRAM secret পুনর্ব্যবহার করতে পারে কেবল তখনই, যখন client-ও SCRAM দিয়ে authenticated হয়েছে, যখন file-এর secret-টি pg_authid-এর secret-এর byte-for-byte একই (শুধু একই password নয়; একই salt এবং iteration count-ও থাকতে হবে), এবং যখন [databases] line কোনো user= নির্দিষ্ট করে না। ওই line-এ user=appuser যোগ করলে PgBouncer-এর পরিবর্তে plaintext password প্রয়োজন হবে। systemctl show pgbouncer -p User দিয়ে নিশ্চিত করুন যে file-এর owner service যে account হিসেবে চলে, তার সঙ্গে মেলে। PostgreSQL-এ password rotate করলে এই file আবার তৈরি করতে হবে। না হলে পরবর্তী connect-এ password authentication failed ফেরত আসবে।

এখন /etc/pgbouncer/pgbouncer.ini লিখুন।

[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
admin_users = pgb_admin
pool_mode = transaction
max_client_conn = 500
default_pool_size = 20
min_pool_size = 5
max_db_connections = 80
max_prepared_statements = 200
ignore_startup_parameters = extra_float_digits

listen_addr = 127.0.0.1 pooler-কে public internet থেকে দূরে রাখে। এটি গুরুত্বপূর্ণ, কারণ বাইরে থেকে reachable pooler এমন একটি authentication endpoint, যা আপনি প্রকাশ করতে চাননি। max_client_conn হলো PgBouncer কতগুলো application connection গ্রহণ করবে। এটি কম resource ব্যবহার করে, তাই এর মান বড় হতে পারে। default_pool_size হলো একটি database এবং user pair একসঙ্গে কতগুলো প্রকৃত backend connection ধরে রাখতে পারবে। এটিই বেশি resource-নির্ভর সংখ্যা। max_db_connections পুরো database-এর সীমা 80-এ আটকে রাখে। ফলে max_connections-এর মধ্যে psql, backup এবং monitoring-এর জন্য জায়গা থাকে। ignore_startup_parameters = extra_float_digits PgBouncer-কে সেই parameter পাঠানো driver-গুলো প্রত্যাখ্যান করা থেকে বিরত রাখে, যার মধ্যে JDBC driver-ও রয়েছে।

sudo systemctl restart pgbouncer
sudo systemctl status pgbouncer --no-pager
sudo journalctl -u pgbouncer -n 20 --no-pager

সফলভাবে start হলে log-এ PgBouncer 127.0.0.1:6432-এ listening করছে—এমন একটি line দেখা যায়। Password file-এর কারণে start ব্যর্থ হলে log-এ যে path পড়া যায়নি, সেটি উল্লেখ থাকে। এটি প্রায় সব সময় syntax সমস্যার বদলে mode বা ownership সমস্যার কারণে ঘটে। এরপর application-এর connection string-এ port 5432-এর পরিবর্তে port 6432 দিন এবং application restart করুন। Application-এর অন্য কোনো পরিবর্তন প্রয়োজন নেই।

পুল ঠিকভাবে কাজ করছে কি না তা পরীক্ষা করা

PgBouncer-এর একটি admin console আছে। এটি pgbouncer নামের একটি virtual database-এর মাধ্যমে খোলা যায়।

psql -h 127.0.0.1 -p 6432 -U pgb_admin pgbouncer
SHOW POOLS;
SHOW STATS;
SHOW CLIENTS;

SHOW POOLS-এর দিকে নজর রাখুন। cl_active হলো বর্তমানে কোনো server connection-এর সঙ্গে যুক্ত client-এর সংখ্যা। cl_waiting হলো server connection-এর জন্য queue-তে অপেক্ষমাণ client-এর সংখ্যা। sv_active এবং sv_idle হলো বাস্তবে ব্যবহৃত ও অব্যবহৃত backend-এর সংখ্যা। maxwait হলো queue-এর সামনে থাকা client কতক্ষণ অপেক্ষা করছে, তা seconds-এ প্রকাশিত। স্বাভাবিক load-এ সুস্থ অবস্থায় cl_waiting-এর মান 0 এবং maxwait-এর মান 0 থাকে। maxwait এক বা দুই seconds-এর বেশি বাড়তে থাকলে বুঝবেন pool খুব ছোট, অথবা query খুব ধীর। এই দুই সমস্যার সমাধান আলাদা।

default_pool_size বাড়ানোর আগে কোন সমস্যাটি হচ্ছে তা পরীক্ষা করুন।

SELECT state, count(*) FROM pg_stat_activity
  WHERE backend_type = 'client backend' GROUP BY state;

বেশিরভাগ backend যদি idle in transaction অবস্থায় থাকে, তাহলে pool size সমস্যা নয়। Application একটি transaction খুলে তার ভেতরে ধীর কোনো কাজ করছে, যেমন HTTP call। ফলে query না চললেও প্রতিটি backend দখল হয়ে থাকে। idle_in_transaction_session_timeout এই connection-গুলো বন্ধ করে দেবে, কিন্তু প্রকৃত সমাধান application code-এ করতে হবে। অন্যদিকে সব backend যদি active অবস্থায় থাকে, তাহলে pool সত্যিই সম্পূর্ণ ব্যবহৃত হচ্ছে। আরও connection দেওয়ার আগে query-গুলোর জন্য EXPLAIN (ANALYZE, BUFFERS) প্রয়োজন।

Size নির্ধারণের ক্ষেত্রে সবচেয়ে বেশি উদ্ধৃত starting point হলো HikariCP heuristic: core count-এর প্রায় দ্বিগুণের সঙ্গে এক যোগ করা। 2 core-এর VPS-এ এটি 5। এটিকে প্রকাশিত starting figure হিসেবে ধরুন, default_pool_size-কে এর কাছাকাছি নির্ধারণ করুন, এবং maxwait-এর ভিত্তিতে পরে পরিবর্তন করুন। ছোট pool অস্বাভাবিক মনে হতে পারে, কিন্তু সাধারণত পরিমাপে ভালো ফল দেয়। কারণ queue-তে থাকা backend কোনো খরচ করে না, কিন্তু চলমান backend CPU, memory এবং lock contention ব্যবহার করে।

PgBouncer, PgDog এবং Pgpool-II-এর মধ্যে নির্বাচন

সাধারণ ক্ষেত্রে PgBouncer-ই উপযুক্ত: একটি PostgreSQL server, একটি VPS এবং এমন একটি application, যা সার্ভারটি যত connection সামলাতে পারে তার চেয়ে বেশি connection খোলে। এটি একটি কাজই করে, এর configuration একটি মাত্র ini file-এ থাকে, এবং Debian ও Ubuntu এটি package হিসেবে সরবরাহ করে। এটি একটি single thread-এ connection handling চালায়। VPS-সমমানের workload-এর জন্য এটি যথেষ্ট। অনেক বড় machine-এ গিয়ে এটিই সীমাবদ্ধতা হয়ে দাঁড়ায়।

Pooling-এর একই network hop-এ routing decision নিতে হলে PgDog বিবেচনা করা যায়। PgDog নিজেকে PostgreSQL scaling-এর জন্য proxy হিসেবে বর্ণনা করে। এটি Rust-এ লেখা। Query parse করে এটি transaction ও session pooling, read/write splitting, এবং multi-shard routing ও two-phase commit-সহ sharding সমর্থন করে। আপনার একটি primary এবং এক বা একাধিক replica থাকলে, এবং application-কে replica-এর অস্তিত্ব না জানিয়ে read request replica-তে পাঠাতে চাইলে এটি ব্যবহার করুন। এখানে দুটি সতর্কতা আছে। এটি AGPLv3-এর অধীনে প্রকাশিত। তাই network-use clause নিয়ে production-এ ব্যবহারের আগে আপনার প্রতিষ্ঠানে এই সিদ্ধান্তের দায়িত্বে থাকা ব্যক্তির সঙ্গে licence-এর বিষয়টি নির্ধারণ করুন। প্রকল্পটির নিজস্ব অবস্থান হলো, internal use এবং private modification source প্রকাশের বাধ্যবাধকতা তৈরি করে না। এটি এখনো নতুন প্রকল্প। Weekly release হয় এবং version number 0.x পর্যায়ে আছে। তাই main অনুসরণ না করে একটি নির্দিষ্ট release tag pin করুন।

git clone https://github.com/pgdogdev/pgdog
cd pgdog
cargo build --release
./target/release/pgdog --config pgdog.toml --users users.toml

Source থেকে build করতে বর্তমান stable Rust toolchain, CMake এবং একটি C/C++ compiler প্রয়োজন। Releases page-এ prebuilt Linux binary ও Debian package-ও আছে। ghcr.io/pgdogdev/pgdog-এ একটি container image-ও পাওয়া যায়। Configuration দুটি file-এ ভাগ করা। প্রথম file-এ সাধারণ settings এবং প্রতি database-এর জন্য একটি entry থাকে। এখানে এগুলো inline table-এর TOML array হিসেবে লেখা হয়েছে, যাতে দুটি form সহজে আলাদা করে চেনা যায়।

databases = [
  { name = "appdb", host = "127.0.0.1" },
]

[general]
port = 6432
default_pool_size = 10

দ্বিতীয় file-এ একই array form-এ প্রতি user-এর জন্য একটি entry থাকে।

users = [
  { name = "appuser", database = "appdb", password = "change-this" },
]

PgDog defaultভাবে 6432 port-এ listen করে, যা PgBouncer-এর একই port। তাই একই host-এ দুটি service-ই default port নিতে পারে না।

June 2026 অনুযায়ী Pgpool-II-এর version 4.7.2। এটি pooling ও load balancing-এর সঙ্গে automatic failover-এর জন্য watchdog দেয়। অতিরিক্ত feature-এর কারণে অতিরিক্ত failure mode-ও আসে। তাই এটি বেছে নেওয়ার আগে এর pooling model বোঝা জরুরি। Pgpool-II আগে থেকেই num_init_childrenটি child process fork করে। প্রতিটি child সর্বোচ্চ max_poolটি server connection cache করে। ফলে backend-এর সর্বোচ্চ সংখ্যা হয় num_init_children-কে max_pool দিয়ে গুণ করলে যত হয়। প্রতিটি child এক সময়ে একজন client serve করে। তাই আপনি যত client গ্রহণ করতে পারবেন, তার সংখ্যা num_init_children-এর সমান এবং startup-এর সময় নির্ধারিত হয়। Idle client-ও একটি child দখল করে রাখে। num_init_children-এর মান 100 এবং max_pool-এর মান 4 নির্ধারণ করলে 400টি backend অনুমোদিত হয়। Pooler বসানোর মূল সমস্যাটিই এতে তৈরি হয়। Failover ও query routing-এর জন্য Pgpool-II প্রয়োজন হলে এটি বেছে নিন এবং গুণের হিসাবটি সতর্কভাবে করুন। শুধু backend-এর সংখ্যা কমাতে চাইলে এই কাজের জন্য এটি প্রয়োজনের তুলনায় বেশি জটিল।

ম্যানেজড proxy-এর প্রশ্ন এবং self-hosted সমতুল্য

Managed platform-গুলো এটিকে আলাদা product হিসেবে দেয়। AWS, RDS-এর সামনে RDS Proxy রাখে, আর Supabase, Supabase Postgres-এর সামনে নিজস্ব pooler Supavisor রাখে। উভয়ই এখানে বর্ণিত কাজ করে: client connection কম খরচে ধরে রাখে এবং কম সংখ্যক প্রকৃত backend-এর মধ্যে সেগুলো বিতরণ করে। Supavisor open source এবং self-host করা যায়। তাই এই পছন্দটি proprietary বনাম free নয়।

Managed proxy-এর self-hosted সমতুল্য কোনো ভিন্ন ধারণা নয়। config file আপনার নিয়ন্ত্রণে থাকা একই ধারণা এটি: transaction mode-এ PgBouncer, database-এর একই VPS-এ, 127.0.0.1-এ listening করে। দুটি পার্থক্য বাস্তব। Managed proxy একটি network hop দূরে থাকে। তাই এটি latency যোগ করে এবং database এর মধ্যে restart হলেও client connection ধরে রাখে। Database host-এ PgBouncer একটি loopback hop যোগ করে, যার খরচ প্রায় শূন্য, কিন্তু host বন্ধ হলে এটিও বন্ধ হয়ে যায়। Restart টিকে থাকার আচরণ চাইলে failover machinery-ও দরকার। তখন Pgpool-II-এর watchdog বা PgDog-এর health check-এর জটিলতা যুক্তিযুক্ত হতে শুরু করে।

আরও একটি option তালিকায় রাখা উচিত। Connection count যদি deployment জটিল হওয়ার প্রধান কারণ হয়, তবে embedded database-এ pool করার মতো কোনো connection model থাকে না। কারণ এটি কোনো port-এ চলা server নয়; আপনার process-এর ভেতরের একটি library। Modest write volume-সহ একটি single application server-এর ক্ষেত্রে VPS-এ production-এ SQLite চালানো এই সমস্যাটি manage না করে পুরোপুরি সরিয়ে দেয়। যখন সত্যিই একটি real server দরকার হবে, তখন machine-এর size নির্ধারণের আগে pool-এর size নির্ধারণ করুন।

FAQ

PgBouncer দরকার কি, যদি আমার application-এ ইতিমধ্যে connection pool থাকে?

সাধারণত দরকার, কারণ application pool প্রতি process-ভিত্তিক এবং অন্য process-এর pool দেখতে পারে না। চারটি Gunicorn worker-এর প্রতিটিতে 20টি request 80 backend-এর pool থাকলে, আরও দুটি background worker যোগ করার পর মোট সংখ্যা 120 হয়। মোট সংখ্যা দেখতে এবং তার সীমা নির্ধারণ করতে পারে একমাত্র PgBouncer। ভালো বিন্যাস হলো দুটিই রাখা: প্রতিটি worker-এর ভিতরে ছোট pool, যাতে request-কে প্রতিবার TCP connection তৈরি করতে না হয়, এবং তাদের পেছনের প্রকৃত backend-এর সংখ্যা সীমাবদ্ধ করতে transaction mode-এ PgBouncer।

PgBouncer-কে transaction mode-এ বদলালে ঠিক কী ব্যর্থ হয়?

যে কোনো কিছু, যা transaction-এর পরেও একই backend-এ state ধরে রাখে। Session-level SETRESET, LISTEN, WITH HOLD cursor, SQL-এর PREPAREDEALLOCATE statement, session-level advisory lock, commit-এর পরেও থাকতে হবে এমন temporary table এবং LOADNOTIFY কাজ করতে থাকে। তাই ত্রুটিপূর্ণ LISTEN-কে pooling সমস্যার বদলে delivery bug মনে হতে পারে। Django-তে DISABLE_SERVER_SIDE_CURSORS-কে True সেট করুন। psycopg 3-এ হয় prepare_threshold-কে None সেট করুন, নয়তো PgBouncer 1.22 বা নতুন সংস্করণ ব্যবহার করুন এবং max_prepared_statements-কে 0-এর বেশি রাখুন। pg_advisory_lock()-এর পরিবর্তে pg_advisory_xact_lock() ব্যবহার করুন।

2 core VPS-এ default_pool_size কত বড় হওয়া উচিত?

যতটা উপযুক্ত মনে হয়, তার চেয়ে ছোট। বহুল ব্যবহৃত HikariCP heuristic হলো core সংখ্যার প্রায় দ্বিগুণের সঙ্গে এক যোগ করা। তাই দুই core-এ প্রাথমিক মান প্রায় 5 হতে পারে, তবে এটিই চূড়ান্ত উত্তর নয়। মানটি সেট করার পর বাস্তব load-এর অধীনে SHOW POOLS-এ maxwaitcl_waiting পড়ুন। উভয় ক্ষেত্রের মান 0 হলে pool যথেষ্ট বড়। maxwait বাড়তে থাকলে client-রা queue-তে অপেক্ষা করছে। সংখ্যা বাড়ানোর আগে pg_stat_activity পরীক্ষা করুন: idle in transaction-এ আটকে থাকা backend application-এর bug, এবং বেশি connection শুধু সেটি আড়াল করবে।

PgBouncer নাকি PgDog?

একটি VPS-এ একটি PostgreSQL server থাকলে PgBouncer ব্যবহার করুন; অধিকাংশ deployment-এ এটাই প্রযোজ্য। এটি Ubuntu-তে package হিসেবে পাওয়া যায়, এর আচরণ ভালোভাবে নথিভুক্ত, এবং সম্পূর্ণ configuration একটি ini file-এ থাকে। একই hop-এ pooling-এর সঙ্গে replica-গুলোর মধ্যে read/write splitting বা sharding দরকার হলে PgDog ব্যবহার করুন, যাতে application-কে topology জানতে না হয়। PgDog বেছে নেওয়ার আগে আপনার কর্মস্থলে licensing-এর দায়িত্বে থাকা ব্যক্তির সঙ্গে AGPLv3 সংক্রান্ত বিষয়টি নির্ধারণ করুন। একটি নির্দিষ্ট release pin করুন, কারণ project-টি এখনও 0.x version number ব্যবহার করছে এবং প্রতি সপ্তাহে release প্রকাশ করছে।

#postgres#pgbouncer#pgdog#connections#performance