পর্ব ১: NovaCommerce-এর জন্য high availability আসলে কী ছিল
কাল্পনিক নামে একটা পরীক্ষা-platform, কিন্তু সংখ্যাগুলো বাস্তব: এক frontend সার্ভার, দুটো ফিক্সড backend, একটাই database node আর দুটো বন্ধ করে রাখা standby। এই পর্বে আছে high availability-র সংজ্ঞা আর বারবার সরে যাওয়া bottleneck।
২৫ সেপ্টেম্বর, পরীক্ষা চলার সময় শিক্ষার্থীরা HTTP 429, অর্থাৎ "too many requests" দেখতে শুরু করল। কারণ সার্ভারের ঘাটতি ছিল না। কারণ ছিল একটা নিয়ম, যেটা ভুল জিনিস গুনছিল: rate limiter গুনছিল IP address ধরে, আর একটা স্কুলের শত শত শিক্ষার্থী একই address-এর পেছনে বসে। limiter-এর চোখে পুরো একটা ভবন যেন একজনই ভীষণ অধৈর্য মানুষ।
এগারো দিন পর, ৬ অক্টোবর রাত ২টা ২৮ মিনিটে একটা backend load test-এ ২,৮৫৪টা application error ফিরে এল। প্রতিটার কথা এক: Valkey-র সঙ্গে connection timeout হয়েছে। স্তর আলাদা, কারণ আলাদা, সমাধানও আলাদা। তারপর এল নিছক CPU-র সমস্যা, যেটা একমাত্র সমস্যা যা সার্ভার বাড়ালে সত্যিই মেটে।
এই প্রকল্পের আসল গল্প এই ক্রমটাই: bottleneck সরেই চলল। সার্ভার বাড়ানো architecture নয়। সেটা কয়েকটা চালের একটা, আর সবচেয়ে শেষের চাল।
২০২৬ সালের সেপ্টেম্বরের শেষ থেকে ৭ অক্টোবরের মধ্যে আমরা DigitalOcean-এর ওপর চলা একটা Laravel ও Next.js platform-কে এক frontend সার্ভার আর দুটো ফিক্সড backend থেকে এমন এক সেটআপে নিয়ে গেছি, যা নির্ধারিত পরীক্ষার চাপের জন্য তৈরি। এই প্রথম পর্বে আছে শুরুর অবস্থা: ব্যবসার সমস্যা, আমরা যা পেয়েছিলাম, কেন সেটা যথেষ্ট ছিল না, "high availability" বলতে আমরা কী বুঝেছিলাম, আর প্রথম test-গুলো আমাদের কী শেখাল।
শুরুর আগে একটা কথা: NovaCommerce একটা কাল্পনিক নাম, এই case study-র জন্য ব্যবহার করা। কোম্পানি, তার সার্ভার বা শিক্ষার্থীদের শনাক্ত করা যায়, এমন সবকিছু আমি বাদ দিয়েছি।
এটি "From One Server to Exam-Day Ready" নামের পাঁচ পর্বের case study-র প্রথম পর্ব। NovaCommerce একটি কাল্পনিক নাম; architecture, সংখ্যা আর ভুলগুলো সবই বাস্তব।
যে platform-এর traffic আসে সময় ধরে
NovaCommerce একটি অনলাইন platform, যেখানে শিক্ষার্থীদের জন্য কোর্স বিক্রি হয় আর সময় ধরা অনলাইন পরীক্ষা নেওয়া হয়। মানুষ সাইন আপ করে, টাকা দেয়, live পরীক্ষা দেয়, ফল দেখে আর AI স্টাডি-সহায়তা ব্যবহার করে।
বেশিরভাগ ওয়েব traffic একটা টিলার মতো: সকালে ধীরে ওঠে, রাতে ধীরে নামে। পরীক্ষার traffic একটা হঠাৎ লাফ। কনসার্ট হলের দরজার কথা ভাবুন। ঘণ্টার পর ঘণ্টা কিছুই ঘটে না, তারপর দরজা খুলতেই সবাই একসঙ্গে ঢোকে। নির্ধারিত পরীক্ষা শুরু হলে হাজার হাজার শিক্ষার্থী একই মিনিটে platform-এ ঢুকে পড়ে।
সুখবর হলো, এই লাফের একটা সময়সূচি আছে। কখন আসবে, আমরা জানি। এই সুবিধার কথা সাধারণভাবে লিখেছি ট্রাফিক স্পাইকে autoscaling নিয়ে গাইডে। এই সিরিজ তার অন্য অর্ধেক: একটা বাস্তব platform, বাস্তব সংখ্যা।
ব্যবসা আমাদের চারটা চাহিদা দিয়েছিল, সহজ ভাষায়।
পরীক্ষার স্পাইক নির্ধারিত এবং তীক্ষ্ণ। হাজার হাজার শিক্ষার্থী একই মিনিটে আসে।
জমা দেওয়া উত্তর কখনো হারানো চলবে না। ধীর পেজ বিরক্তিকর। হারিয়ে যাওয়া পরীক্ষার submission মানে একজন শিক্ষার্থীর পরিশ্রম গায়েব।
Deploy হতে হবে অদৃশ্য। release পাঠানোর সময়ও সাইট চালু থাকতে হবে।
খরচ থাকতে হবে সুস্থ সীমায়। যে capacity কারও কাজে লাগে না, সেটাও একটা ত্রুটি।
এই সিরিজের প্রতিটা প্রযুক্তিগত সিদ্ধান্ত ওই চার লাইনের কোনো একটায় গিয়ে ঠেকে।
শুরুর অবস্থা: আমরা যা পেয়েছিলাম
৫ অক্টোবর ২০২৬-এর আগে সেটআপটা আমরা যেমন পেয়েছিলাম, তা এই রকম: একটা DigitalOcean region, একটা private network।
দুটো single point of failure (frontend আর database), দুটো ফিক্সড backend-এর একটা তালিকা, আর দুটো standby সার্ভার, যেগুলো বন্ধ থাকলেও বিল হচ্ছিল।
Frontend: একটা সার্ভার, আট CPU, একজন ক্যাশিয়ার
Frontend ছিল একটাই droplet: ৮ vCPU আর ১৬ GB memory, তার ওপর একটা Next.js container। domain সরাসরি ওটার দিকেই তাক করা, TLS আসত droplet-এর ওপরেই certbot থেকে। সামনে কোনো load balancer ছিল না। ওই সার্ভার মারা গেলে সাইট বন্ধ।
এতে টাকাও নষ্ট হচ্ছিল। একটা Node.js process প্রায় একটা core-এ render করে, তাই আটটা vCPU-র বেশিরভাগটাই বসে থাকত: আটটা কাউন্টারের দোকানে ক্যাশিয়ার একজন।
Backend: দুটো সার্ভার, যাকে load balancer বাড়াতে পারত না
একটা DigitalOcean load balancer-এর পেছনে ছিল দুটো ফিক্সড backend droplet, প্রতিটায় ৪ vCPU আর ৮ GB। অ্যাপ্লিকেশনটা Laravel (PHP-FPM), nginx-এর পেছনে, Docker-এ: প্রতি node-এ দুটো container, nginx-এর জন্য web আর php-fpm-এর জন্য app। queue (Horizon), scheduler আর Reverb, অর্থাৎ live update-এর WebSocket সার্ভার, চলত অন্য একটা worker-এ।
Load balancer backend দুটোকে target করত আলাদা আলাদা ID ধরে, tag ধরে নয়। এটা হলো নাম লেখা অতিথি-তালিকা আর "যার গলায় স্টাফ ব্যাজ আছে সে-ই ঢুকবে" নিয়মের পার্থক্য। নাম ধরে রাখলে নতুন সার্ভার নিজে থেকে কখনো ঢুকতে পারে না। কাউকে হাতে তালিকা বদলাতে হয়।
এখানে একটা সুখবরও ছিল, আর সেটাই সবচেয়ে বড়। backend web node-গুলো আগে থেকেই stateless ছিল। session, cache আর queue থাকত managed Valkey-তে, আর log যেত stderr-এ। যে সার্ভার মুছে ফেললে কিছু হারায় না, তাকে বহুগুণ করা যায়। এই বৈশিষ্ট্য না থাকলে সিরিজের বাকি কিছুই সম্ভব হতো না।
Database আর বন্ধ করে রাখা standby
Database ছিল একটাই Managed MySQL "Advanced" node, ৮ vCPU আর ৩২ GB: একটা node, কোনো standby নেই।
তারপর ছিল দুটো "standby" droplet, ব্যাকআপ হিসেবে রাখা। দুটোই power off। বন্ধ থাকা droplet-ও টাকা খায়, আর চালু করতে লাগে কয়েক মিনিট। এটা শহরের অন্য প্রান্তে তালাবন্ধ গ্যারেজে রাখা একটা বাড়তি টায়ার: আছে, তার দাম দিচ্ছেন, কিন্তু রাস্তার মাঝে আটকে গেলে কাজে আসে না। এটা high availability নয়।
কোণায় আরও দুটো ছোট বিষয় ছিল। load balancer-এর TLS certificate হাতে upload করা, মেয়াদ শেষ হওয়ার তারিখসহ, তাই নবায়ন ছিল বারবার ফিরে আসা একটা কাজ, যেটা কাউকে মনে রাখতে হতো। আর account-এর droplet limit ছিল ২৫, যেটা গুরুত্বপূর্ণ হয়ে উঠবে যখন দুটো pool বাড়বে আর node বদলাবে (পর্ব ২)।
কেন এটা যথেষ্ট ছিল না
সেটআপের অংশ
কেমন করে বানানো
চাপে বা বিকল হলে কী ঘটে
Frontend
একটা ৮ vCPU / ১৬ GB droplet, একটা Next.js container, load balancer নেই
মারা গেলে সাইট বন্ধ; বেশিরভাগ core অলস, কারণ একটা Node process প্রায় একটা core-ই ব্যবহার করে
Backend
load balancer-এর পেছনে দুটো ফিক্সড ৪ vCPU / ৮ GB droplet, ID ধরে target করা
নিজে থেকে বাড়ে না; নতুন সার্ভার নিজে কখনো যোগ দিতে পারে না
Database
একটা Managed MySQL node, ৮ vCPU / ৩২ GB, standby নেই
node বিকল হলে দায়িত্ব নেওয়ার মতো দ্বিতীয় node তৈরি নেই
Standby
দুটো droplet, power off
বন্ধ থাকলেও বিল হয়; চালু করতে কয়েক মিনিট
টেবিলে কী নেই, খেয়াল করুন: কোনো bug। কিছুই ভাঙা ছিল না। সেটআপ যা করার জন্য বানানো, তা-ই করছিল। সমস্যা ছিল অমিল: নির্ধারিত হঠাৎ লাফ আর এমন এক সেটআপ, যা একটা সার্ভার হারাতে পারে না, নিজে একটা যোগ করতে পারে না, আর বাড়তি capacity বন্ধ করে রাখে। অমিল patch করা যায় না। পুরো সিস্টেমের আকারটাই বদলাতে হয়, আর architecture মানে ঠিক সেটাই।
এখানে "high availability" আসলে কী ছিল
"High availability" এমন একটা শব্দ, যাতে সবাই মাথা নাড়ে অথচ সংজ্ঞা কেউ দেয় না। কিছু বদলানোর আগেই আমরা লিখে ফেলেছিলাম, এই platform-এর জন্য এর মানে কী। কথাটা দাঁড়াল চার লাইনে।
এমন কোনো একক সার্ভার নেই, যেটা হারালে সাইট বন্ধ হয়ে যায়। একটা node হারালেও database টিকে থাকে। Deploy আর scale-in শিক্ষার্থীদের কাছে অদৃশ্য। পরীক্ষা শুরুর আগেই capacity তৈরি, শুরুর পাঁচ মিনিট পরে নয়।
আমি এমন সংজ্ঞা পছন্দ করি, যাকে একটা প্রশ্ন দিয়ে যাচাই করা যায়। যেকোনো একটা সার্ভারের প্লাগ খুলে নিলেও কি আমরা সেবা দিতে পারি? একটা database node বিকল হলেও কি পরীক্ষা চলে? কোনো শিক্ষার্থীর টের না পাওয়ার মতো করে কি release পাঠানো যায়? প্রথম শিক্ষার্থী "start" চাপার সময় capacity কি আগে থেকেই তৈরি?
এই সংজ্ঞার ওপরে বসে আছে দুটো ব্যবসায়িক নিয়ম: জমা দেওয়া উত্তর কখনো হারাবে না, আর খরচ থাকবে সুস্থ সীমায়। ছবিটা প্রতিটা চাহিদাকে সেই স্তরের সঙ্গে মেলায়, যাকে তা পূরণ করতে হবে, এবং সিরিজের যে পর্বে সেই স্তর আসবে, তার সঙ্গেও।
প্রতিটা চাহিদার একজন মালিক আছে: একটা স্তর, যাকে তা পূরণ করতে হবে, আর এই সিরিজের একটা পর্ব, যেখানে দেখানো হয়েছে কীভাবে।
সংজ্ঞার শেষ লাইন, "শুরুর পাঁচ মিনিট পরে নয়", সবচেয়ে বেশি লেগেছিল। আমাদের test-এ autoscaling প্রথম বাড়তি node যোগ করেছিল CPU ৯৯%-এ পৌঁছানোর প্রায় সাত মিনিট পরে। পরীক্ষা এক মিনিট পরেই শুরু হলে সাত মিনিট অনেক লম্বা সময়। লাফের আগে capacity লাগে, পরে নয়।
প্রথম test: bottleneck সরেই চলে
সংজ্ঞা লেখা হলে আমরা সেটাই করলাম, যা আমাদের পদ্ধতি চায়: আগে মাপা। সেপ্টেম্বরের শেষ থেকে ৬ অক্টোবরের মধ্যে আমরা production-এর আচরণ দেখলাম আর load test চালালাম, আর পরপর তিনটা সমস্যা পেলাম। দেখতে সম্পর্কহীন, আর ঠিক সেটাই আসল কথা।
পরপর তিনটা bottleneck, প্রতিটার সমাধান ভিন্ন ধরনের। শুধু তৃতীয়টা সার্ভার বাড়ালে মেটে।
১. Limiter: কোডের সমস্যা (২৫ সেপ্টেম্বর)
Production-এ মাপা: পরীক্ষার সময় শিক্ষার্থীরা HTTP 429 পাচ্ছিল। global API rate limiter চলছিল IP ধরে, কারণ token-ভিত্তিক শিক্ষার্থীদের জন্য default auth guard ছিল ফাঁকা, তাই limiter বুঝতে পারত না শিক্ষার্থীটি কে। একটা স্কুল বা একটা মোবাইল ক্যারিয়ার শত শত শিক্ষার্থীকে একটাই NAT address-এর পেছনে বসায়, আর পুরো ভবন ভাগ করে নিচ্ছিল একটাই বালতি।
Status: Implemented। আমরা API limiter-কে ঠিক করলাম শিক্ষার্থী বা instructor অ্যাকাউন্ট ধরে, আর IP ধরে শুধু guest-দের জন্য। সার্ভার বাড়ালে কাজ হতো না; "না" বলছিল নিয়মটা, hardware নয়। এই bug-এর এক নিকট আত্মীয় ৭ অক্টোবর আবার ফিরে এসেছিল route-level throttle-এ, সে গল্প পর্ব ৪-এ।
২. Valkey connection: data-tier সাইজের সমস্যা (৬ অক্টোবর)
৬ অক্টোবর রাত ২টা ২৮ মিনিটে একটা backend load test-এ ২,৮৫৪টা application 500 error এল। প্রতিটাই connect করার সময় RedisException: Operation timed out, connect timeout ৫ সেকেন্ড। Valkey (৪ GB, primary আর standby) চাপের মুখে যথেষ্ট দ্রুত connection নিতে পারছিল না।
অ্যাপ কীভাবে ওটা ব্যবহার করছিল, তাতেও চাপ বাড়ছিল। প্রতিটা request Valkey-র সঙ্গে নতুন TLS connection খুলত (request প্রতি প্রায় ৫.৪ ms CPU) আর MySQL-এর সঙ্গে আরেকটা (প্রায় ৩.৩ ms)। এমন একটা দোকান ভাবুন, যেখানে প্রতিটা খদ্দেরকে প্রতিবার দরজায় বাজিয়ে ঢুকিয়ে পরিচয় যাচাই করতে হয়। ভিড়ের মধ্যে দরজাটাই হয়ে যায় লাইন।
Status: Implemented, তারপর Tested। আমরা Valkey-র আকার বদলালাম জায়গায় থেকেই: ৮ GB, দুটো node (primary ও standby), data অক্ষত, host অপরিবর্তিত। তারপর দুটো backend node দিয়ে শুরু করে, load-এর সাথে pool-কে বাড়তে দিয়ে, সেকেন্ডে ২৫ থেকে ৫০০ request পর্যন্ত ধীরে ধীরে বাড়িয়ে আবার test করলাম: application error ০টা, backend 5xx ০টা। load balancer-এ ১৪৩টা error এসেছিল, কিন্তু শুধু তখনই, যখন node-গুলো CPU-তে ভরপুর। ওটা প্রত্যাশিত "capacity ফুরিয়েছে" সংকেত, bug নয়। Data tier পাবে পর্ব ৩, আর Valkey ফিরবে পর্ব ৪-এ। সাধারণ তত্ত্বের জন্য দেখুন চাপের মুখে data tier।
৩. Backend CPU: capacity-র সমস্যা
প্রথম দুটো ঠিক হওয়ার পর যা বাকি রইল, তা নিছক অঙ্ক। একটা ৪ vCPU / ৮ GB backend node পুরো CPU-তে বাস্তব API mix-এর প্রায় ৬৫ থেকে ৭০ request/s সামলায়, request প্রতি প্রায় ৫৯ ms CPU খরচ করে। পরবর্তী প্রতিটা capacity সিদ্ধান্ত এই সংখ্যার ওপর দাঁড়ানো।
তিনটার মধ্যে এটাই একমাত্র bottleneck, যেটা সার্ভার বাড়ালে সত্যিই মেটে, আর এখানেও সময়টাই ফাঁদ: প্রথম বাড়তি node এসেছিল CPU ৯৯%-এ পৌঁছানোর প্রায় সাত মিনিট পরে। capacity কীভাবে আগে পৌঁছানো গেল, সেটা পর্ব ২-এর বিষয়।
তাহলে ক্রমটা দাঁড়াল: limiter (কোড), তারপর Valkey connection (data-tier সাইজ), তারপর backend CPU (capacity)। শুরুতেই সার্ভার বাড়াতে গেলে limiter তখনো 429 বলত, আর Valkey তখনো timeout করত।
পুরোনো সেটআপের খরচ
নতুন আকারের কথায় যাওয়ার আগে জানা দরকার, পুরোনোটার খরচ কত ছিল। এগুলো পুরোনো web tier-এর মাসিক DigitalOcean list price।
আইটেম
খরচ
কী পাওয়া যেত
পুরোনো web tier: frontend, দুটো backend, worker, একটা load balancer, দুটো standby
প্রায় ৪১৬ ডলার/মাস
একটা চালু সাইট, তবে একাধিক single point of failure
দুটো power off standby droplet
১১২ ডলার/মাস
কোনো availability নয়: বন্ধ থাকলেও বিল, চালু করতে কয়েক মিনিট
তুলনার জন্য: একটা বাড়তি backend node
ঘণ্টায় প্রায় ০.০৮ ডলার
যে capacity সত্যিই request সামলায়
১১২ ডলার, যা web tier-এর এক-চতুর্থাংশের সামান্য বেশি, এই সংখ্যাটাই আমার মনে গেঁথে আছে। এতে কেনা হয়েছিল এমন দুটো সার্ভার, যারা একটা request-ও সামলাতে পারত না। Pre-scaling-এ একটা বাড়তি backend node-এর খরচ ঘণ্টায় প্রায় ০.০৮ ডলার, তাই এক সন্ধ্যার জন্য capacity বাড়ানোর খরচ কয়েক সেন্ট। বন্ধ করে রাখা capacity-র জন্য পুরো মাস টাকা দেওয়া নিরাপদ বোধ করার সবচেয়ে দামি উপায়।
একটা সৎ কথা: ৪১৬ ডলার শুধু web tier, তাতে database, Valkey বা storage নেই, তাই এটাকে পুরো বিলের সঙ্গে তুলনা করা যাবে না। পর্ব ৫-এ আমি সমান জিনিসের সঙ্গে সমান জিনিস মেলাব।
পদ্ধতি, আর সিরিজের পরিকল্পনা
পুরো প্রকল্পে আমরা যে ছকটা বারবার ধরেছি, সেটাই আপনি এইমাত্র দেখলেন: মাপো, bottleneck খোঁজো, কারণ বোঝো, architecture বদলাও, আবার test করো, আবার মাপো। সার্ভার বাড়ানো architecture নয়। Architecture মানে ঠিক করা, কোন স্তর বদলাবে, আর কেন।
পাঁচটা পর্ব কীভাবে জোড়া লাগে, নিচে দেখুন।
পর্ব
স্তর
যা দেখবেন
১ (এই পর্ব)
শুরুর অবস্থা
ব্যবসার সমস্যা, পুরোনো সেটআপ, high availability-র সংজ্ঞা, প্রথম bottleneck আর পুরোনো খরচ
৭ অক্টোবরের আসল পরীক্ষার সন্ধ্যা, load ও soak test, আর চূড়ান্ত architecture
যে ধারণাগুলো আমরা ভেবেছিলাম অথচ বানাইনি, তারও প্রতিটার গায়ে লেবেল দেব। দুই tier-এর জন্য একটাই load balancer ছিল Considered, তারপর Rejected। "কয়েক সেকেন্ডে তৈরি warm standby" ছিল Considered, কিন্তু autoscale pool-এ warm pool নেই, তাই আমরা নিয়েছি আগে থেকেই সেবা দেওয়া বাড়তি capacity আর pre-scaling। Kubernetes ছিল পরের জন্য Considered, বানানো হয়নি।
সবকিছু শেষ হয়নি, আর সে কথা আমি বলব। একক worker সার্ভার একটা পরিচিত single point of failure; এটা ঠিক করা Planned, এখনো হয়নি (পর্ব ৪)। বহু ঘণ্টার একটা আনুষ্ঠানিক soak test-ও Planned (পর্ব ৫)।
আমরা কী অর্জন করতে চেয়েছিলাম
লক্ষ্যটা একটা চেকলিস্ট আকারে, প্রতিটা লাইন কোন পর্বে বানানো বা test করা হয়েছে সেটাসহ।