কি হবে যদি আপনার ব্যাকএন্ডে রাতারাতি ১০,০০০ কনকারেন্ট ইউজার রিকোয়েস্ট হিট করে?
যেমন ধরুন—ইভেন্টের টিকিট বুকিং, ফ্ল্যাশ সেল কিংবা কোনো ভাইরাল ক্যাম্পেইনে প্রতি সেকেন্ডে হাজার হাজার ইউজার একযোগে পারচেজ করতে গেল। সিস্টেম প্রোপারলি আর্কিটেক্ট করা না থাকলে সার্ভার মেমোরি শেষ হয়ে ক্র্যাশ করবে এবং সাইট সম্পূর্ণ ডাউন হয়ে যাবে।
সাধারণত এই পরিস্থিতিতে আমরা সিপিইউ-র্যাম বাড়াই, লোড ব্যালেন্সার দিয়ে ইনস্ট্যান্স বাড়াই, কিংবা মেসেজ কিউ (Queue) ব্যবহার করি।
বাট আমরা যদি একটু গভীরে দেখি—আসল প্রবলেমটা কোথায়? কনকারেন্ট ট্রাফিক বাড়লে ডেটাবেসে আসলে কি ঘটে?
১. আসল সমস্যা: ডেটাবেস কানেকশন প্রেশার (The Actual Problem)
Node.js বা Go ব্যাকএন্ডে সাধারণ ডিবি ড্রাইভার একটি নির্দিষ্ট কানেকশন পুল (যেমন: MaxOpenConns = 20) মেইনটেইন করে।
হঠাৎ 10,000 রিকোয়েস্ট চলে আসলে:
- প্রথম 20 টি রিকোয়েস্ট পুল কানেকশন দখল করে নেবে।
- বাকি 9,980 টি রিকোয়েস্ট লোকাল অ্যাপ মেমোরিতে অপেক্ষারত (Queued) থাকবে।
- ডেটাবেস অপারেশন সামান্য হলেই অপেক্ষারত রিকোয়েস্টগুলো টাইমআউট হয়ে ক্লায়েন্টদের 504 Gateway Timeout বা 500 Internal Server Error দেখাতে শুরু করবে।
২. অ্যাপ্লিকেশনে পুল সাইজ বাড়ানো কেন সমাধান নয়? (Why Increasing DB Pool Fails)
সো, আপনি বলতে পারেন—"আমি অ্যাপ্লিকেশনে পুল সাইজ 20 থেকে বাড়িয়ে 1,000 বা 3,000 করে দেবো!" 😀
বাট বাস্তবে এটি ডেটাবেসকে আরও দ্রুত ক্র্যাশ করাবে:
১০,০০০ ইনকামিং রিকোয়েস্ট
↓
অ্যাপ পুল সাইজ = ১,০০০
↓
১,০০০টি সরাসরি PostgreSQL কানেকশন
↓
মারাত্মক মেমোরি নষ্ট (১০ GB+ RAM) + CPU Context Switching
↓
ডেটাবেস ক্র্যাশ (FATAL: too many clients already)
কারণ PostgreSQL প্রতিটি কানেকশনের জন্য আলাদা OS Process (Fork) তৈরি করে এবং প্রতি কানেকশনে প্রায় 4–10 MB RAM নেয়। ফলে 1,000+ সরাসরি কানেকশন হ্যান্ডেল করতেই ডেটাবেসের নিজস্ব মেমোরি ও সিপিইউ থ্র্যাশ করে সার্ভার ডাউন হয়ে যায়।
৩. PgBouncer যেভাবে আর্কিটেকচার বদলে দেয় (How PgBouncer Changes Architecture)
এই জায়গাতেই কাজ করে PgBouncer। এটি ব্যাকএন্ড অ্যাপ্লিকেশন এবং মেইন Postgres ডেটাবেসের মাঝখানে একটি সুপার লাইটওয়েট প্রক্সি লেয়ার হিসেবে বসে:
[ ১০,০০০+ অ্যাপ রিকোয়েস্ট ]
│
▼ (Lightweight sockets via libevent - ~2 KB RAM each)
[ PgBouncer (MAX_DB_CONNECTIONS = 45) ]
│
▼ (Transaction Pooling: ৩০–৪৫টি রিয়াল Postgres কানেকশন)
[ PostgreSQL Database Server (max_connections = 75) ]- Postgres সরাসরি কানেকশনে যেখানে প্রতিটি ক্লায়েন্টে আস্ত প্রসেস খোলে, PgBouncer সেখানে মাত্র ~২ KB RAM দিয়ে হাজার হাজার কানেকশন হোল্ড করে।
- ডেটাবেসের ওপর কোনো অতিরিক্ত প্রসেস বা মেমোরি প্রেশার পড়ে না।
৪. ট্রানজ্যাকশন পুলিং ও মাল্টিপ্লেক্সিং (Transaction Pooling Explained)
অনেকের মনে প্রশ্ন আসতে পারে—মাত্র ৩০টি কানেকশন দিয়ে ১০,০০০ ইউজার কিভাবে হ্যান্ডেল হয়?
ম্যাজিকটা হলো Connection Multiplexing: একটি ৫০ms-এর ওয়েব রিকোয়েস্টে ডেটাবেসে আসল INSERT/SELECT কুয়েরি চলতে সময় নেয় মাত্র ০.২ms থেকে ০.৪ms। বাকি সময়টা অ্যাপ্লিকেশনের নিজস্ব লজিকে ব্যয় হয়।
সরাসরি কানেকশন থাকলে অ্যাপ্লিকেশনকে দীর্ঘ সময় একটি ডেটাবেস কানেকশন ধরে রাখতে হতে পারে। কিন্তু PgBouncer-এর transaction pooling ব্যবহারের ফলে transaction শেষ হওয়া মাত্রই PostgreSQL-এর কানেকশনটি pool-এ ফিরে যায় এবং পরবর্তী রিকোয়েস্ট সেটি ব্যবহার করতে পারে।
এর পেছনে PgBouncer-এর C-ভিত্তিক lightweight connection management-ও গুরুত্বপূর্ণ। এটি প্রতিটি client connection-এর জন্য আলাদা heavyweight application process বা PostgreSQL backend process তৈরি করে না; বরং event-drivenভাবে হাজার হাজার client connection পরিচালনা করে এবং অল্প overhead-এ তাদের PostgreSQL server connection-এর সঙ্গে multiplex করে।
ফলে ৩০টি physical PostgreSQL connection একসাথে হাজার হাজার request serve করতে পারে—একটি transaction শেষ হলে একই connection পরবর্তী waiting request-এর কাছে চলে যায়।
Request 1 (0.3ms INSERT) ──▶ কানেকশন রিলিজ ──▶ Request 2 পেল (0.3ms) ──▶ Request 3...মাত্র ৩০টি ফিজিক্যাল কানেকশন প্রতি সেকেন্ডে হাজার হাজার বার একের পর এক রিকোয়েস্টের মাঝে সাইকেল (Multiplex) হয়ে বিশাল থ্রুপুট ডেলিভার করে।
৫. আমাদের প্রোডাকশন কনফিগারেশন (Actual Configuration)
pgbouncer:
image: edoburu/pgbouncer:latest
environment:
POOL_MODE: transaction # ট্রানজ্যাকশন শেষ হওয়া মাত্রই কানেকশন রিসাইকেল হয়
MAX_CLIENT_CONN: 30000 # একসাথে ৩০,০০০ অ্যাপ কানেকশন কিউ করতে পারে
DEFAULT_POOL_SIZE: 30 # অ্যাপ কুয়েরির জন্য নিয়মিত ৩০টি আসল কানেকশন রাখে
MIN_POOL_SIZE: 10 # অন্তত ১০টি কানেকশন সবসময় প্রি-ওয়ার্মড রাখে
RESERVE_POOL_SIZE: 10 # ট্রাফিক স্পাইক আসলে ১০টি ইমার্জেন্সি কানেকশন ওপেন করে
MAX_DB_CONNECTIONS: 45 # PgBouncer সর্বোচ্চ ৪৫টি কানেকশন ব্যবহার করতে পারবে💡 নোট (৪৫ বনাম ৭৫ কানেকশন): PostgreSQL সার্ভারে max_connections = 75 রাখা হলেও PgBouncer-কে সর্বোচ্চ 45 বেঁধে দেওয়া হয়েছে। কারণ বাকি ৩০টি কানেকশন DBA অ্যাডমিন লগইন (psql), ব্যাকগ্রাউন্ড মাইগ্রেশন, রেপ্লিকেশন ও ডেটাবেস মনিটরিং টুলের জন্য সেফটি হেডরুম (Headroom) হিসেবে রাখা Production বেস্ট-প্র্যাকটিস।
৬. প্র্যাক্টিক্যাল লোড টেস্ট (The Benchmark Proof)
চলুন এবার সরাসরি আমাদের Go (Gin Gonic) সার্ভারে চালানো লোড টেস্টের ফলাফল দেখা যাক।
টেস্ট ১: সিঙ্গেল ইনস্ট্যান্স (Single Go App @ 10,000 Concurrent Users)
A) Direct PostgreSQL (Port 5432):
ফলাফল: সরাসরি ডেটাবেসে কানেকশন থ্রটলিংয়ের কারণে ২,৫৫৩টি রিকোয়েস্ট ফেইল (৪.২৩% Error Rate) করেছে।
B) PgBouncer Transaction Pool (Port 6432):
ফলাফল: PgBouncer ব্যবহার করায় এই লোড টেস্টে ৬৩,১৩০টি অর্ডার সফলভাবে প্রসেস হয়েছে এবং connection-related failure ০% ছিল।
টেস্ট ২: মাইক্রোসার্ভিস স্কেলিং (4 Go Instances Cluster @ 5,000 Concurrent Users)
এখানে ৪টি Go সার্ভার ইনস্ট্যান্স লোড ব্যালেন্সারের পেছনে ছিল। প্রতিটি ইনস্ট্যান্স ৫০টি করে পুল ওপেন করতে চায় (4×50=2004×50=200 কানেকশন), যেখানে Postgres লিমিট মাত্র ৭৫।
A) 4 Instances on Direct PostgreSQL (Port 5432):
ফলাফল: সরাসরি Postgres-এ কানেক্ট করায় অতিরিক্ত কানেকশনের চাপে সার্ভার ওভারলোড হয়ে ৬,৫০৩টি রিকোয়েস্ট ফেইল (১৯.২২% Error Rate) করেছে এবং থ্রুপুট অর্ধেকের নিচে নেমে গেছে!
B) 4 Instances on PgBouncer Transaction Pool (Port 6432):
ফলাফল: ৪টি ইনস্ট্যান্সের ট্রাফিক PgBouncer দিয়ে পাঠানোয় ৪৯,৯০৪টি রিকোয়েস্ট সফলভাবে প্রসেস হয়েছে এবং এই টেস্টে কোনো কানেকশন ড্রপ বা ফেইলর হয়নি (০.০০% error rate)।
৭. রেজাল্ট সামারি টেবিল (Comparison Table)
৮. PgBouncer-এর সীমাবদ্ধতা ও আর্কিটেকচারাল ট্রেড-অফ (Limitations & Cons)
PgBouncer কোনো সব সমস্যার সমাধান নয়। আর্কিটেকচারে এটি যোগ করার আগে এই ট্রেড-অফগুলো জানা জরুরি:
- নতুন Single Point of Failure (SPOF): একটি মাত্র PgBouncer ডাউন হলে পুরো সিস্টেম ডাউন। প্রোডাকশনে K8s Sidecar Pattern অথবা NLB/HAProxy দিয়ে একাধিক PgBouncer ইনস্ট্যান্স চালাতে হয়।
- Single-Threaded CPU Core Saturation: PgBouncer সিঙ্গেল-থ্রেডেড (১টি কোর ব্যবহার করে)। এক্সট্রিম হাই ট্রাফিকে কোর স্যাচুরেট এড়াতে লিনাক্সে SO_REUSEPORT দিয়ে একাধিক প্রসেস চালানো হয়।
- Transaction Pooling Caveats (সবচেয়ে বড় সীমাবদ্ধতা): কানেকশন প্রতিনিয়ত হাতবদল হওয়ায় সেশন-নির্ভর ফিচারগুলো ব্রেক করে:Prepared Statements ও ORM: কানেকশন বদলে গেলে স্টেটমেন্ট ক্র্যাশ করে। তাই ড্রাইভার/ORM-এ প্রিপেয়ার্ড স্টেটমেন্ট বন্ধ বা সিম্পল প্রোটোকল কনফিগার করতে হয় (যেমন: Prisma-তে ?pgbouncer=true, Go pgx-এ QueryExecModeSimpleProtocol, Rails/SQLAlchemy-তে prepared_statements: false)।Session State & Variables: SET timezone অন্য ক্লায়েন্টে লিক হতে পারে; তাই SET LOCAL এবং PgBouncer-এ server_reset_query = DISCARD ALL ব্যবহার করতে হয়।Advisory Locks & Pub/Sub: সেশন লকের বদলে pg_advisory_xact_lock() ব্যবহার করতে হবে এবং LISTEN/NOTIFY-এর জন্য সরাসরি ডেডিকেটেড Postgres কানেকশন লাগবে।Temp Tables: সেশন-স্কোপড CREATE TEMP TABLE পরবর্তী ট্রানজ্যাকশনে খুঁজে পাওয়া যায় না।
- ডেটাবেসের অভ্যন্তরীণ সমস্যা দূর করে না: স্লো কুয়েরি (Missing Index), টেবিল/রো লক এবং ডিস্ক/WAL রাইট বটলেনেক PgBouncer দিয়ে ফিক্স হবে না—সেগুলোর জন্য কুয়েরি ও স্কিমা টিউনিংই সমাধান।
৯. প্রডাকশন টেক-অ্যাওয়ে (Production Takeaways)
- হরাইজন্টাল অটো-স্কেলিংয়ে PgBouncer বাধ্যতামূলক: আপনি যত মাইক্রোসার্ভিস পড বাড়াবেন, প্রতিটি ইনস্ট্যান্স নিজস্ব পুল ওপেন করবে। PgBouncer সেগুলোকে সেন্ট্রালি ছোট একটি সেটে মাল্টিপ্লেক্স করে ডেটাবেস ক্র্যাশ রোধ করে।
- PostgreSQL সরাসরি এক্সপোজ করবেন না: অ্যাপ্লিকেশনের ডাটাবেস ট্রাফিক সবসময় PgBouncer পোর্ট (6432)-এর মধ্য দিয়ে রাউট করুন।
- কুয়েরি টিউনিংই আসল শক্তি: খুব ছোট ও এফিশিয়েন্ট ট্রানজ্যাকশন হলে কম সংখ্যক ডেটাবেস কানেকশন দিয়েই সর্বোচ্চ থ্রুপুট পাওয়া সম্ভব—তবে আসল থ্রুপুট আপনার ওয়ার্কলোড ও হার্ডওয়্যারের ওপর নির্ভর করবে।
আপনার সিস্টেমে কি হাই লোডে কানেকশন টাইমআউট এরর আসছে?
PgBouncer ইন্টিগ্রেট করে কানেকশন পুল টিউনিং করে নিন—আপনার ডেটাবেসের স্ট্যাবিলিটি ও থ্রুপুট বদলে যাবে!
#PostgreSQL #PgBouncer #BackendEngineering #SystemDesign #Scalability #HighConcurrency #Performance #SoftwareArchitecture