০০ যে প্রশ্নগুলোর উত্তর খুঁজবো
এখন পর্যন্ত আমরা একটা application কেই বড় করেছি। তার আরো copy চালিয়েছি, database কে replica আর shard এ ভাগ করেছি, সামনে cache আর queue বসিয়েছি। কিন্তু application এর code টা সবসময় একটাই ছিলো।
এই সেশনে প্রশ্নটা অন্য রকম। Code টাকেই কয়েকটা আলাদা program এ ভাগ করা উচিত কিনা, আর উচিত হলে কখন। এর নাম microservices। এটা নিয়ে অনেক উৎসাহ আছে, আবার অনেক দুঃখের গল্পও আছে। শেষে আপনি বুঝবেন, কোন সমস্যা দেখলে ভাগ করা ঠিক, আর কোন সমস্যা দেখলে ভাগ করলে বিপদ আরো বাড়ে।
- ১ Monolith কী, আর শুরুতে এটা কেন ভালো পছন্দ? (monolith)
- ২ ৯টা team একটা codebase এ কাজ করলে deploy বারবার কেন ভাঙে?
- ৩ ভাগ করার আগে ভেতরটা গুছিয়ে নিলে কেমন হয়? (modular monolith)
- ৪ কখন সত্যিই microservice এ ভাগ করা উচিত?
- ৫ Service এর সীমানা কোথায় টানবো? (bounded context, database per service)
- ৬ ভাগ করার পরও সব একসাথে ভাঙে কেন? (distributed monolith)
- ৭ Team এর গঠন কীভাবে architecture ঠিক করে দেয়? (Conway's law)
- ৮ চালু system কে বন্ধ না করে কীভাবে ভাঙবো? (strangler fig)
- ৯ Microservices এর cost কী? (network call, debugging, data consistency, operations)
০১ Monolith কী, আর শুরুতে এটা কেন ভালো পছন্দ?
বৃহস্পতিবার, রাত পৌনে এগারোটা। নাদিয়া বাসায় ভাত খেতে বসেছে, ঠিক তখন ফোনটা বেজে উঠলো। Support team জানালো, checkout page এ সব order এর total দেখাচ্ছে ৳০। নাদিয়া “বাজারঘর” নামের একটা online দোকানের tech lead। রাত ৯টায় সপ্তাহের বড় deploy হয়েছে, তার পর থেকেই এই অবস্থা। প্লেট সরিয়ে রেখে সে laptop খুললো। এই মাসে এটা তৃতীয় বৃহস্পতিবার, যেদিন deploy এর পর কিছু একটা ভাঙলো।
গল্পটা অবশ্য শুরু হয়েছিলো অনেক শান্তভাবে। ২০২১ সালে বাজারঘরে engineer ছিলো মোটে চারজন। পুরো website ছিলো একটা program এ: product দেখানো, cart, payment, search, সব একসাথে। Code ছিলো একটা repository তে, database ছিলো একটাই। এই ধরনের application কে বলে monolith। অর্থাৎ পুরো application একটা codebase, একসাথে build হয়, একসাথে deploy হয়।
Monolith নামটা শুনে অনেকে ভাবেন এটা পুরোনো আর খারাপ কিছু। আসলে শুরুর জন্য এটাই সবচেয়ে ভালো পছন্দ, আর তার পেছনে শক্ত কারণ আছে:
- সব কিছু এক জায়গায়: নতুন engineer একটা repository clone করে পুরো system চালাতে পারে। কোন feature কোথায়, খুঁজতে এক ঘণ্টাও লাগে না।
- Function call দ্রুত আর নিরাপদ: Cart যখন Payment এর কাছে কিছু জানতে চায়, সেটা একটা সাধারণ function call। একই process এর ভেতরে এটা এক microsecond এরও কম সময় নেয়। Network না থাকায় মাঝপথে হারিয়ে যাওয়ার ভয়ও নেই।
- একটা transaction এ সব কাজ: Order save করা আর stock কমানো একই database এ হয়। তাই একটা transaction (সেশন ০৫) দিয়েই “হয় দুটোই হবে, নয়তো কোনোটাই না” নিশ্চিত করা যায়।
- Debugging সহজ: কিছু ভাঙলে একটা log file, একটা stack trace। কোথায় ভুল হলো, সাথে সাথে দেখা যায়।
- Scale ও করা যায়: Server stateless হলে (সেশন ০২) load balancer এর পেছনে একই program এর আরো copy চালালেই হয়। Database এর চাপ কমাতে replica, cache আর shard তো আছেই।
Monolith মানে খারাপ design নয়। চারজন মানুষ আর একটা নতুন ব্যবসার জন্য এটাই সবচেয়ে দ্রুত আর সস্তা পথ। Shopify এর মতো বিশাল company ও আজও মূলত একটা বড় monolith এ চলে। সমস্যা monolith এ নয়, সমস্যা আসে যখন অনেক মানুষ একসাথে একই monolith বদলাতে থাকে।
০২ ৯টা team একটা codebase এ কাজ করলে deploy বারবার কেন ভাঙে?
চার বছরে বাজারঘর অনেক বড় হয়েছে। এখন engineer ৬০ জন, তারা ৯টা team এ ভাগ হয়ে কাজ করে: Catalog, Search, Cart, Checkout, Payment, Stock, Delivery, Seller আর Notification। কিন্তু code এখনো সেই একটাই monolith। প্রতি বৃহস্পতিবার রাতে সবার সপ্তাহের কাজ একসাথে deploy হয়।
আজ রাতে কী হয়েছিলো, নাদিয়া খুঁজে বের করলো। Search team দাম দেখানোর একটা shared function বদলেছিলো, যাতে search result এ discount দেখা যায়। ঐ একই function checkout page ও ব্যবহার করে, সেটা তারা জানতো না। একটা ছোট বদল, আর ভেঙে গেলো সম্পূর্ণ আলাদা একটা team এর page।
এবার হিসাবটা দেখুন। ধরুন প্রতিটা change এ bug থাকার সম্ভাবনা মাত্র ১%। Change কম থাকলে এটা প্রায় চোখেই পড়ে না। কিন্তু একসাথে অনেক change গেলে ঝুঁকি গুণ হয়ে বাড়ে:
ধরুন, প্রতিটা change এ bug থাকার সম্ভাবনা 1% (মানে 99% change ঠিক থাকে)
2021: 4 জন engineer, এক deploy এ 5টা change
সব ঠিক থাকার সম্ভাবনা = 0.99^5 ≈ 0.95
কিছু একটা ভাঙবে = 1 - 0.95 ≈ 5% (প্রায় 20 সপ্তাহে একবার)
2025: 60 জন engineer, এক deploy এ 45টা change
সব ঠিক থাকার সম্ভাবনা = 0.99^45 ≈ 0.64
কিছু একটা ভাঙবে = 1 - 0.64 ≈ 36% (প্রায় 3 সপ্তাহে একবার)
চারজনের সময় প্রায় পাঁচ মাসে একবার কিছু ভাঙতো। এখন প্রায় প্রতি তিন সপ্তাহে একবার। কেউ খারাপ কাজ করছে না, শুধু মানুষ বেড়েছে। এর সাথে আরো কয়েকটা কষ্ট যোগ হয়েছে:
- একজনের ভুলে সবার কাজ ফেরত যায়: Monolith একটাই program, তাই শুধু Search এর অংশটা ফেরত নেওয়া যায় না। পুরো deploy rollback হয়, আর বাকি ৪৪টা ভালো change ও আটকে যায়।
- সবাই সবার জন্য অপেক্ষা করে: Payment team একটা জরুরি fix আজই দিতে চায়। কিন্তু deploy সপ্তাহে একবার, তাই বৃহস্পতিবার পর্যন্ত অপেক্ষা করতে হয়।
- Test শেষ হতে ৪০ মিনিট: একটা লাইন বদলালেও পুরো application এর সব test চালাতে হয়।
- Code এ কেউ নিশ্চিন্তে হাত দিতে পারে না: কোন function কে কে ব্যবহার করে, সেটা আর কারো মাথায় নেই। তাই সবাই ভয়ে ভয়ে code বদলায়।
বাজারঘরের traffic এর জন্য monolith এখনো যথেষ্ট। Server গুলো ঠিকই চলছে। সমস্যা হলো মানুষের: ৯টা team একটা জিনিস বদলাচ্ছে, আর একজনের কাজ অন্যজনের কাজে আটকে যাচ্ছে। এটা technical এর চেয়ে বেশি একটা team এর সমস্যা।
০৩ ভাগ করার আগে ভেতরটা গুছিয়ে নিলে কেমন হয়?
পরদিন সকালে CTO বললেন, “চলুন সব microservice বানিয়ে ফেলি। Netflix ও তো তাই করে।” নাদিয়া একটু থামলো। তার মনে হলো, code এর ভেতরে কোন অংশ কোথায় শেষ হয়, সেটাই এখন কেউ জানে না। এই অবস্থায় কেটে ভাগ করলে জট টা শুধু network এর দুই পাশে ছড়িয়ে যাবে।
তাই সে আগে একটা সহজ কাজ করলো। Program একটাই থাকলো, কিন্তু ভেতরটা গুছিয়ে নিলো। একে বলে modular monolith: deploy হয় একটা program হিসেবেই, কিন্তু ভেতরে প্রতিটা অংশ (module) পরিষ্কারভাবে আলাদা।
গোছানোর নিয়ম তিনটা:
- প্রতিটা module এর নিজের table: Cart এর table এ শুধু Cart module লেখে আর পড়ে। অন্য কেউ সেখানে সরাসরি SQL চালায় না।
- কথা হয় শুধু public function দিয়ে: Cart এর দাম জানা দরকার হলে সে Catalog এর
getPrice()call করে। Catalog এর ভেতরের কোনো helper function সে ছোঁয় না। - প্রতিটা module এর একজন মালিক team: Catalog module বদলাতে হলে Catalog team এর review লাগে।
এতে আগের বৃহস্পতিবার রাতের মতো ঘটনা অনেক কমে যায়। Search team এখন আর checkout এর function এ হাত দিতে পারে না, কারণ ঐ function টা Checkout module এর ভেতরের, বাইরে থেকে দেখাই যায় না। তবে একটা জিনিস বদলায়নি। Deploy এখনো একটাই, সপ্তাহে একবার।
Modular monolith নিজেই অনেক company এর জন্য শেষ গন্তব্য। আর যাদের পরে সত্যিই ভাগ করতে হয়, তাদের জন্য এটা সবচেয়ে ভালো প্রস্তুতি। যে module এর সীমানা ইতিমধ্যে পরিষ্কার, তাকে আলাদা service বানানো অনেক সহজ। আগে ঘর গোছান, তারপর ঘর ভাগ করুন।
০৪ কখন সত্যিই microservice এ ভাগ করা উচিত?
প্রথমে নামটা পরিষ্কার করে নিই। Microservice হলো একটা ছোট, আলাদা program, যেটা ব্যবসার একটা নির্দিষ্ট কাজ সামলায়। এর নিজের code আছে, নিজের database আছে, আর নিজের সময়ে আলাদাভাবে deploy করা যায়। বাকিরা তার সাথে network এর উপর দিয়ে কথা বলে। “Micro” শব্দটা নিয়ে ভুল বোঝার সুযোগ আছে। এখানে ছোট মানে code এর লাইন কম নয়, মানে দায়িত্ব একটাই।
শুধু “বড় company রা করে” বলে ভাগ করা কোনো কারণ নয়। ভাগ করার পেছনে চারটা আসল কারণ থাকতে পারে।
কারণ ১: Team রা একে অপরকে আটকাচ্ছে
এটা আগের section এর গল্প। একটা service এর মালিক যখন একটা team, তখন সেই team নিজের সময়ে deploy করতে পারে। Payment team মঙ্গলবার দুপুরে fix দিতে চাইলে কারো অনুমতি লাগে না। Search team এর bug এ Payment এর deploy আর আটকায় না।
কারণ ২: আলাদা আলাদা অংশে আলাদা চাপ
গত ঈদের sale এর রাতে বাজারঘরে search এর traffic স্বাভাবিকের ১০ গুণ হয়েছিলো, প্রতি second এ ৩০০ থেকে ৩,০০০ request। কিন্তু Cart আর Payment এ চাপ বেড়েছিলো সামান্য। Admin panel এ বাড়েইনি। স্বাভাবিক দিনে monolith এর ৪টা copy চলে, আর মোট request এর প্রায় অর্ধেকই আসে search এ। তাই search ১০ গুণ হলে মোট চাপ প্রায় ৫ গুণ হয়, আর তখন ২০টা copy লাগে।
Monolith এ শুধু search এর অংশটা বড় করার উপায় নেই। তাই পুরো program এর ২০টা copy চালাতে হয়েছিলো। Search আলাদা service হলে শুধু তারই copy বাড়ে। কিছু অংশের ধরনও আলাদা হয়। যেমন ছবি resize করতে CPU বেশি লাগে, আর search index রাখতে RAM বেশি লাগে। আলাদা service হলে প্রত্যেককে তার উপযুক্ত machine এ চালানো যায়।
কারণ ৩: একটার failure যেন বাকিদের না থামায়
একবার “আপনার জন্য সুপারিশ” (recommendation) অংশের একটা bug এ memory শেষ হয়ে গিয়েছিলো। Monolith এর process টাই crash করলো, আর তার সাথে checkout ও বন্ধ হয়ে গেলো। সুপারিশ না দেখালে কেউ রাগ করে না, কিন্তু checkout বন্ধ মানে বিক্রি বন্ধ। আলাদা service হলে recommendation crash করলেও checkout চলতে থাকে। শুধু সুপারিশের জায়গাটা খালি দেখায়। একে বলে fault isolation: একটা অংশের failure কে তার নিজের সীমানার ভেতরে আটকে রাখা।
কারণ ৪: আলাদা নিয়ম বা technology লাগে
Payment এর data নিয়ে কঠোর security নিয়ম মানতে হয়। পুরো monolith কে সেই নিয়মের আওতায় আনার চেয়ে শুধু Payment কে আলাদা করে সুরক্ষিত রাখা সহজ। আবার কোনো অংশ হয়তো অন্য programming language এ লিখলে অনেক ভালো চলে।
নাদিয়া এই কারণগুলো দিয়ে একটা ছোট decision chart বানালো। নতুন কোনো অংশ আলাদা করার প্রস্তাব এলে সে এই চারটা প্রশ্ন করে:
কোনো নির্দিষ্ট সংখ্যা নেই, তবে অভিজ্ঞতা থেকে একটা আন্দাজ পাওয়া যায়। দশ-পনেরো জনের একটা team এর জন্য সাধারণত monolith ই সবচেয়ে ভালো। ত্রিশ-চল্লিশ জন পেরোলে, আর team রা একে অপরের deploy আটকাতে শুরু করলে, ভাগ করার কথা ভাবার সময় আসে। মাপকাঠি হলো ব্যথা, ফ্যাশন নয়।
০৫ Service এর সীমানা কোথায় টানবো?
ভাগ করবো ঠিক হলো। এবার সবচেয়ে কঠিন প্রশ্ন: কোথায় কাটবো? ভুল জায়গায় কাটলে দুইটা service কে সারাক্ষণ একে অপরকে call করতে হয়। তখন আলাদা করে কোনো লাভ হয় না।
একই শব্দ, তিন রকম মানে
বাজারঘরের সবচেয়ে বেশি বিক্রি হওয়া জিনিসটা ধরুন, product #8841, একটা নীল পাঞ্জাবি। তিনটা team এর কাছে এই একই পাঞ্জাবি তিন রকম জিনিস:
Catalog team এর কাছে পাঞ্জাবি মানে ছবি, বর্ণনা আর size chart। Stock team এর কাছে মানে মিরপুরের গুদামে কয়টা আছে। Order team এর কাছে মানে “রহিম ভাই গত বুধবার কত টাকায় দুইটা কিনেছিলেন”। পরে পাঞ্জাবির দাম বাড়লেও ঐ পুরোনো order এর দাম বদলায় না।
এই প্রতিটা ঘরকে বলে bounded context। একটা bounded context এর ভেতরে প্রতিটা শব্দের একটাই পরিষ্কার মানে থাকে, আর এর নিজের data আর নিয়ম থাকে। ঘরের বাইরে গেলে একই শব্দের মানে বদলে যেতে পারে। Service এর সীমানা টানার সবচেয়ে ভালো জায়গা এই ঘরগুলোর দেয়াল। কারণ ঘরের ভেতরের কাজগুলো একে অপরের সাথে ঘন ঘন কথা বলে, কিন্তু অন্য ঘরের সাথে কথা হয় কম।
Database per service
সীমানা টানার পর আসে data এর প্রশ্ন। নাদিয়ার team একটা নিয়ম ঠিক করলো, যেটা পরে সবচেয়ে কাজে লেগেছে: database per service। অর্থাৎ প্রতিটা service এর নিজের database থাকে, আর সেই data এর একমাত্র মালিক সে নিজেই। অন্য কোনো service সেই database এ সরাসরি query চালাতে পারে না। Data লাগলে মালিকের API তে জিজ্ঞেস করতে হয়।
নিয়মটা কড়া শোনায়, কিন্তু এর কারণ সহজ। Database ভাগ করে রাখলে Stock team তাদের table এর গঠন যখন খুশি বদলাতে পারে। শুধু API টা আগের মতো রাখলেই চলে। Shared database থাকলে তাদের একটা column এর নাম বদলালেই অন্য team এর query ভেঙে যায়। তখন সবাইকে আবার একসাথে deploy করতে হয়, আর আমরা ফিরে যাই বৃহস্পতিবার রাতের সেই সমস্যায়।
সেশন ০৫ এ data কে কয়েকটা shard এ ভাগ করার সময় দেখেছিলেন, দুই shard জুড়ে একটা transaction চালানো কঠিন। Database per service এও ঠিক একই সমস্যা আসে। Order আর Payment এখন দুইটা আলাদা database এ, তাই একটা transaction দিয়ে দুটোকে একসাথে আপডেট করা যায় না। এর সমাধান (saga) আছে সেশন ১৭ তে।
০৬ ভাগ করার পরও সব একসাথে ভাঙে কেন?
নাদিয়ার team এর আগে একবার একটা চেষ্টা হয়েছিলো, আর সেটার গল্পটা শিক্ষণীয়। দুই বছর আগে একজন senior engineer তাড়াহুড়া করে Order, Payment, Stock আর Notification কে চারটা আলাদা service বানিয়েছিলেন। Code আলাদা repository তে গেলো, আলাদা server এ চললো। কিন্তু database টা থাকলো আগের সেই একটাই।
ফলাফল হলো উল্টো। Order table এ একটা নতুন column যোগ হলে চারটা service এরই code বদলাতে হতো। তাই চারটাকে একই দিনে, একই ক্রমে deploy করতে হতো। একটা order দিতে Order কে Payment এর জন্য অপেক্ষা করতে হতো, Payment কে Stock এর জন্য, Stock কে Notification এর জন্য। Notification একটু ধীর হলেই পুরো checkout ধীর হয়ে যেতো। ছয় মাস পর সবাই হাল ছেড়ে দিলো, আর চারটা service কে আবার monolith এ ফিরিয়ে আনা হলো। তাই নাদিয়া এবার এত সাবধান।
এই অবস্থার নাম distributed monolith। দেখতে microservices, কিন্তু আচরণে monolith। এটা দুই জগতের সবচেয়ে খারাপ অংশ একসাথে নিয়ে আসে। Monolith এর মতোই সবাইকে একসাথে deploy করতে হয়, আর microservices এর মতো network এর ধীরগতি আর failure ও সহ্য করতে হয়।
কয়েকটা লক্ষণ দেখলে বুঝবেন আপনি এই ফাঁদে পড়েছেন:
- Shared database: কয়েকটা service একই table পড়ে বা লেখে।
- একসাথে deploy: একটা service বদলালে আরো দুই-তিনটাকে একই দিনে deploy করতে হয়।
- লম্বা sync call এর শিকল: একটা request পূর্ণ করতে চার-পাঁচটা service কে পরপর অপেক্ষা করতে হয়।
- Shared library এর বাঁধন: একটা common library আপডেট হলে সব service কে একসাথে আপডেট করতে হয়।
নিজেকে জিজ্ঞেস করুন: “আমি কি এই service টা মঙ্গলবার দুপুরে, অন্য কোনো team কে না জানিয়ে deploy করতে পারি?” উত্তর “না” হলে আপনার কাছে microservices নেই। আছে একটা monolith, যেটা এখন network দিয়ে জোড়া লাগানো।
০৭ Team এর গঠন কীভাবে architecture ঠিক করে দেয়?
নাদিয়া একদিন বাজারঘরের পুরোনো system diagram আর team এর তালিকা পাশাপাশি রাখলো। সে অবাক হয়ে দেখলো, দুটো প্রায় একই রকম দেখতে। যে দুই team রোজ একসাথে বসে, তাদের code ও সবচেয়ে বেশি জড়ানো। যে team সবার থেকে আলাদা বসে (Seller panel), তাদের code ও সবচেয়ে আলাদা।
এটা কাকতালীয় নয়। ১৯৬৮ সালে Melvin Conway নামের একজন programmer লক্ষ্য করেছিলেন: একটা প্রতিষ্ঠান যে system বানায়, তার গঠন সেই প্রতিষ্ঠানের মানুষদের যোগাযোগের গঠনের মতো হয়। এই পর্যবেক্ষণের নাম Conway’s law। সহজ কথায়, চারটা team মিলে একটা compiler বানালে সেটা প্রায় নিশ্চিতভাবে চার ধাপের compiler হবে।
এর কারণ খুব মানবিক। যাদের সাথে আপনি রোজ কথা বলেন, তাদের code এর সাথে নিজের code মেশানো সহজ। যাদের সাথে কথা হয় না, তাদের সাথে একটা পরিষ্কার চুক্তি (API) ছাড়া কাজ চলে না।
তাই নাদিয়া সিদ্ধান্ত নিলো, আগে team সাজাবে, তারপর code। প্রতিটা bounded context এর জন্য একটা team, আর সেই team তার service এর পুরো মালিক: code, database, deploy, এমনকি রাতে কিছু ভাঙলে ফোনটাও তাদের কাছেই যাবে। এই উল্টো পথে হাঁটাকে বলে inverse Conway maneuver: যে architecture চান, আগে সেই আকারে team সাজান।
Amazon এর একটা পরিচিত নিয়ম আছে, যার নাম “two-pizza team”: একটা team এ এমন কয়জন থাকবে, যাতে দুইটা pizza তেই সবার পেট ভরে যায়। মোটামুটি ছয় থেকে দশ জন। এর চেয়ে বড় হলে team এর ভেতরেই যোগাযোগ কঠিন হয়ে পড়ে। একটা service এর মালিক এমন একটা ছোট team হলে সবচেয়ে ভালো চলে।
০৮ চালু system কে বন্ধ না করে কীভাবে ভাঙবো?
CTO এর প্রথম প্রস্তাব ছিলো: “ছয় মাস নতুন feature বন্ধ রেখে পুরো system নতুন করে microservices এ লিখি।” একে বলে big bang rewrite, আর এর ইতিহাস খুব একটা সুখের নয়। ছয় মাস প্রায় সবসময় আঠারো মাস হয়ে যায়। এর মধ্যে পুরোনো system এ নতুন bug আসে, ব্যবসার চাহিদা বদলায়। আর শেষে একদিন সব traffic একবারে নতুন system এ সরাতে হয়, যেদিন ভুল হলে পুরো দোকান বন্ধ।
নাদিয়া অন্য পথ বেছে নিলো। পুরান ঢাকায় তার নানাবাড়ির পুরোনো দালানের দেয়ালে একটা অশ্বত্থ গাছ গজিয়েছিলো। প্রথমে ছোট একটা চারা। তারপর শিকড় দেয়াল বেয়ে নিচে নামলো, বছরের পর বছর মোটা হলো। একসময় দেখা গেলো দেয়ালের অনেকটা অংশ গাছের শিকড়েই ধরে আছে। বট আর অশ্বত্থ এই দলেরই গাছ, ইংরেজিতে যাদের বলে strangler fig। এরা অন্য গাছ বা দেয়ালের গায়ে জন্মায়, আর ধীরে ধীরে তার জায়গা নিয়ে নেয়।
২০০৪ সালে Martin Fowler এই গাছের নামেই একটা migration কৌশলের নাম দেন: strangler fig pattern। পুরোনো system চালু রেখে তার চারপাশে নতুন service বানানো হয়, একটা একটা feature করে। পুরোনো অংশ ধীরে ধীরে ছোট হয়, একসময় পুরোপুরি বন্ধ করে দেওয়া যায়।
বাজারঘরে কাজটা হলো এভাবে:
- সামনে একটা router বসানো: Monolith এর সামনে একটা router বসলো, যেটা request এর URL দেখে ঠিক করে কোথায় পাঠাবে। শুরুতে সে সব request monolith এই পাঠায়। User কোনো পার্থক্য টের পায় না। (এই router পরে API gateway তে পরিণত হয়, সেটা সেশন ১৩ তে দেখবো।)
- প্রথম feature বেছে নেওয়া: নাদিয়া প্রথমে Search বেছে নিলো। এর সীমানা পরিষ্কার, ঈদে এর চাপ আলাদা, আর এটা বেশিরভাগ data শুধু পড়ে। এমন অংশ দিয়ে শুরু করলে ঝুঁকি কম।
- অল্প অল্প traffic সরানো: নতুন Search service তৈরি হলে router প্রথমে মাত্র ৫% search request সেখানে পাঠালো। সব ঠিক থাকায় এক সপ্তাহে ধীরে ধীরে ১০০% এ নেওয়া হলো। সমস্যা হলে router এর একটা setting বদলালেই আবার monolith এ ফেরা যেতো।
- পুরোনো code মুছে ফেলা: Search এর সব traffic নতুন service এ চলে যাওয়ার পর monolith থেকে পুরোনো search code ডিলিট করা হলো।
- পরের feature এ যাওয়া: তারপর Cart, তারপর Payment। প্রতিবার একই ধাপ।
প্রতিটা ধাপ ছোট, আর প্রতিটা ধাপ থেকে ফেরা যায়। কোনোদিন পুরো system একবারে বদলায় না। মাঝপথে থেমে গেলেও ক্ষতি নেই: যতটুকু সরানো হয়েছে, সেটা কাজ করছে, আর বাকিটা monolith এ আগের মতোই চলছে।
০৯ Microservices এর cost কী?
কয়েক মাসে Search এর পর Cart, Payment আর Stock ও আলাদা service হয়ে গেলো। তার কিছুদিন পর এক সন্ধ্যায় কয়েকজন customer অভিযোগ করলো, তাদের order “processing” এ আটকে আছে। আগে এমন হলে নাদিয়া একটা log file খুলে দশ মিনিটে কারণ বের করতো। এবার একটা order চার জায়গা ঘুরে আসে: monolith, Cart, Payment আর Stock service। চার জায়গার log মিলিয়ে কারণ খুঁজে পেতে লাগলো তিন ঘণ্টা। কারণটা ছিলো, Stock service এর একটা call মাঝে মাঝে timeout হচ্ছিলো।
সেদিন নাদিয়া বুঝলো, microservices বিনা পয়সায় আসে না। প্রথম আর সবচেয়ে বড় cost হলো network:
একটা call এ কয়েক millisecond হয়তো বড় কিছু না। কিন্তু একটা checkout এ কয়েকটা service কে পরপর call করতে হয়। তখন সময় আর ঝুঁকি দুটোই যোগ হতে থাকে:
Checkout এ 4টা service কে পরপর call (Cart, Stock, Payment, Delivery):
Monolith: 4 × 0.001 ms ≈ 0.004 ms
Microservices: 4 × 3 ms ≈ 12 ms
প্রতিটা service 99.9% সময় ঠিকঠাক উত্তর দিলে:
চারটাই ঠিক থাকার সম্ভাবনা = 0.999^4 ≈ 99.6%
অর্থাৎ প্রতি 1,000 checkout এ প্রায় 4টা কোথাও না কোথাও আটকায়
প্রতি হাজারে চারটা checkout আটকানো শুনতে ছোট লাগে। কিন্তু ঈদের রাতে এক লাখ checkout হলে সংখ্যাটা চারশো। Monolith এ এই সমস্যাটা ছিলোই না, কারণ function call মাঝপথে হারায় না। এই ধরনের ব্যর্থতাকে বলে partial failure: system এর একটা অংশ fail করেছে, বাকিটা ঠিক আছে। Service গুলো কীভাবে কথা বলবে আর timeout কীভাবে সামলাবে, সেটা সেশন ১২ এর বিষয়।
বাকি cost গুলো একসাথে দেখে নিন:
Microservices এর downside গুলো
- Network call: আগে যেটা function call ছিলো, এখন সেটা network এর উপর দিয়ে যায়। প্রতিটা call এ কয়েক millisecond লাগে, আর যেকোনো call fail করতে পারে।
- Debugging কঠিন: একটা order এখন ছয়টা service ঘুরে আসে। ভুল কোথায় হলো, সেটা খুঁজতে ছয় জায়গার log মেলাতে হয়।
- Data consistency: Order, Payment আর Stock এর data এখন তিনটা আলাদা database এ। একটা transaction দিয়ে তিনটাকে একসাথে আপডেট করার উপায় নেই।
- Operations এর বোঝা: একটার বদলে পনেরোটা service এর deploy pipeline, monitoring আর alert চালাতে হয়। রাতে কোন team এর ফোন বাজবে, সেটাও ঠিক করে রাখতে হয়।
- Test করা কঠিন: নিজের service একা ঠিক চললেই হয় না। বাকিদের সাথে মিলে ঠিক চলছে কিনা, সেটাও দেখতে হয়।
এগুলোর প্রতিটার জন্য আলাদা সমাধান আছে, আর পরের সেশনগুলোতে আমরা সেগুলো শিখবো। চার service জুড়ে একটা request কে খুঁজে বের করার কৌশল (distributed tracing) আছে সেশন ১৯ এ। আলাদা database জুড়ে consistency রাখার কৌশল আছে সেশন ১৭ তে। কিন্তু প্রতিটা সমাধান আবার নতুন কিছু শেখার আর চালানোর দায়িত্ব যোগ করে।
Microservices একটা technical সমস্যার চেয়ে বেশি একটা মানুষের সমস্যার সমাধান। অনেক team কে আলাদাভাবে, নিজের গতিতে কাজ করতে দেয়, আর আলাদা অংশকে আলাদাভাবে scale করতে দেয়। বিনিময়ে আপনি নেন network এর ধীরগতি, কঠিন debugging, ভাগ হওয়া data আর অনেক বেশি operations এর কাজ। Team এর ব্যথা এই cost এর চেয়ে বড় হলে তবেই ভাগ করুন।
ছয় মাস পরের এক বৃহস্পতিবার রাত। Payment team সেদিন বিকেলেই নিজেদের fix deploy করে বাড়ি চলে গেছে। Search team এর একটা bug এ search কিছুক্ষণ ধীর ছিলো, কিন্তু checkout একবারও থামেনি। নাদিয়া সেদিন রাতে ভাত খেতে বসলো, আর ফোনটা একবারও বাজলো না।
১০ মগজে প্রেশার দিন
আগে নিজে ভাবুন। তারপর উত্তর খুলে দেখুন: বেশিরভাগ মানুষ কীভাবে ভাবে, সেই চিন্তায় কী বাদ পড়ে, আর আসল কথাটা কোথায়।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“হ্যাঁ, শুরু থেকেই microservices। পরে monolith ভাঙা অনেক কষ্টের। এখনই ভাগ করে রাখলে পরে scale করতে সুবিধা হবে।”
কথাটা শুনতে দূরদর্শী লাগে, কিন্তু দুটো জিনিস বাদ পড়েছে।
যে জায়গাটা যোগ করা দরকার
প্রথমত, শুরুতে কেউ জানে না ব্যবসার সীমানাগুলো কোথায়। Product টাই হয়তো ছয় মাসে বদলে যাবে। ভুল জায়গায় ভাগ করলে পরে সেই সীমানা বদলানো monolith ভাঙার চেয়েও কঠিন, কারণ এখন সেটা network আর আলাদা database জুড়ে ছড়ানো। দ্বিতীয়ত, তিনজন মানুষের মধ্যে কেউ কাউকে আটকায় না। তাই ভাগ করার প্রধান লাভটাই এখানে নেই, কিন্তু network, debugging আর operations এর সব cost পুরোপুরি আছে।
ভালো উত্তর হলো: একটা modular monolith দিয়ে শুরু করবো। Module এর সীমানা পরিষ্কার রাখবো, প্রতিটা module এর নিজের table রাখবো। পরে team বড় হলে বা কোনো অংশের চাপ আলাদা হলে সেই module টা strangler fig দিয়ে বের করে আনবো।
Monolith first, কিন্তু ভেতরে modular। ভাগ করার সিদ্ধান্ত নিন কোনো নির্দিষ্ট ব্যথা দেখে, যেমন team রা একে অপরের deploy আটকাচ্ছে বা একটা অংশের scaling এর চাহিদা আলাদা।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“Deploy process টা ধীর। একটা ভালো CI/CD pipeline (code থেকে নিজে নিজে build, test আর deploy করার ব্যবস্থা) বানালেই ঠিক হয়ে যাবে।”
Pipeline দ্রুত হলে কষ্ট একটু কমে, কিন্তু রোগটা থেকে যায়।
যে জায়গাটা যোগ করা দরকার
সব service একসাথে deploy করতে হচ্ছে মানে তারা একে অপরের উপর শক্তভাবে নির্ভরশীল। এটা distributed monolith এর লক্ষণ। কারণ খুঁজতে হবে তিন জায়গায়: তারা কি একই database এর table ছোঁয়? একজনের API বদলালে কি অন্যদের code ও বদলাতে হয়? একটা request কি অনেকগুলো service এর লম্বা sync শিকলের উপর নির্ভর করে?
সমাধান pipeline এ নয়, সীমানায়। প্রতিটা service কে নিজের data এর মালিক বানাতে হবে (database per service)। API বদলাতে হলে পুরোনো version কিছুদিন চালু রাখতে হবে, যাতে অন্যরা নিজের সময়ে বদলাতে পারে। দরকার হলে ভুল জায়গায় কাটা কয়েকটা service কে আবার জোড়া লাগাতে হবে। Segment নামের একটা company ২০১৮ সালে ঠিক এটাই করেছিলো: শতাধিক ছোট service কে আবার একটা service এ ফিরিয়ে এনেছিলো, আর তাতে তাদের কাজ অনেক সহজ হয়েছিলো।
Microservices এর মাপকাঠি service এর সংখ্যা নয়। মাপকাঠি হলো প্রতিটা service একা, অন্য কাউকে না জানিয়ে deploy করা যায় কিনা।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“API call মানে network, মানে কয়েক millisecond বেশি। Table টা পড়ার অনুমতি দিলেই সবচেয়ে দ্রুত হয়। শুধু পড়ছি, লিখছি তো না, তাই ক্ষতি কী?”
দ্রুততার হিসাব ঠিক আছে। কিন্তু এখানে দ্রুততার চেয়ে বড় একটা জিনিস হারাচ্ছে।
যে জায়গাটা যোগ করা দরকার
Order service সরাসরি table পড়া শুরু করলে Stock এর table এর গঠন আর Stock team এর একার থাকে না। Stock team একটা column এর নাম বদলালে বা table টা দুই ভাগ করলে Order এর query ভেঙে যাবে। তাই কোনো বদলের আগে Order team এর সাথে বসতে হবে, আর দুই service একসাথে deploy করতে হবে। শুধু পড়লেও এই বাঁধন তৈরি হয়। এভাবে কয়েকটা service একই table পড়া শুরু করলে আমরা ফিরে যাই distributed monolith এ।
API হলো একটা চুক্তি: “এই প্রশ্ন করলে এই আকারে উত্তর পাবেন।” চুক্তির পেছনে Stock team যা খুশি বদলাতে পারে। আর ধীরগতির ভয় থাকলে Order service উত্তরটা কিছুক্ষণের জন্য cache করে রাখতে পারে (সেশন ০৪)।
অন্য service এর database মানে অন্যের ঘর। দরজায় নক করুন (API), জানালা দিয়ে ঢুকবেন না (সরাসরি SQL)।
১১ যে টার্মগুলো শিখলাম
| টার্ম | মানে |
|---|---|
| Monolith | পুরো application একটা codebase, একসাথে build হয় আর একসাথে deploy হয়। |
| Modular monolith | Deploy হয় একটা program হিসেবে, কিন্তু ভেতরে প্রতিটা module এর নিজের table আর public function আছে। |
| Microservice | ব্যবসার একটা কাজ সামলানো ছোট, আলাদা program। নিজের code, নিজের database, আর আলাদাভাবে deploy করা যায়। |
| Fault isolation | একটা অংশের failure কে তার নিজের সীমানার ভেতরে আটকে রাখা, যাতে বাকিরা চলতে থাকে। |
| Bounded context | ব্যবসার এমন একটা অংশ, যার ভেতরে প্রতিটা শব্দের একটাই মানে আর যার নিজের data ও নিয়ম আছে। |
| Database per service | প্রতিটা service এর নিজের database। অন্যরা সেই data চাইলে API দিয়ে চায়, সরাসরি SQL দিয়ে নয়। |
| Distributed monolith | দেখতে আলাদা service, কিন্তু shared database বা শক্ত নির্ভরতার কারণে সবাইকে একসাথে deploy করতে হয়। |
| Conway’s law | একটা প্রতিষ্ঠান যে system বানায়, তার গঠন প্রতিষ্ঠানের মানুষদের যোগাযোগের গঠনের মতো হয়। |
| Inverse Conway maneuver | যে architecture চান, আগে সেই আকারে team সাজানো। |
| Strangler fig | পুরোনো system চালু রেখে একটা একটা feature নতুন service এ সরানো, যতক্ষণ না পুরোনোটা বন্ধ করা যায়। |
| Big bang rewrite | পুরো system একবারে নতুন করে লিখে একদিনে বদলে ফেলা। ঝুঁকিপূর্ণ পথ। |
| Partial failure | System এর একটা অংশ fail করেছে, কিন্তু বাকি অংশ চলছে। Network এর উপর চলা system এ এটা স্বাভাবিক ঘটনা। |
১২ শর্ট সামারি
- Monolith শুরুর জন্য সবচেয়ে সহজ আর দ্রুত পথ, আর stateless server হলে এটাও scale করে। এর আসল সমস্যা দেখা দেয় তখন, যখন অনেকগুলো team একই codebase বদলায় আর একে অপরের deploy আটকায়।
- ভাগ করার আগে modular monolith দিয়ে ভেতরটা গোছান। Team রা আটকালে, কোনো অংশের scaling এর চাহিদা আলাদা হলে, বা একটার failure বাকিদের থামালে তবেই সেই অংশটা আলাদা service বানান।
- Service এর সীমানা টানুন bounded context ধরে, আর প্রতিটা service কে নিজের database এর মালিক বানান। Shared database বা একসাথে deploy থাকলে সেটা distributed monolith, যেখানে দুই দিকেরই খারাপটা পাওয়া যায়।
- Conway’s law মনে রেখে আগে team সাজান, তারপর strangler fig দিয়ে ধাপে ধাপে ভাগ করুন। আর মনে রাখুন, network call, কঠিন debugging, ভাগ হওয়া data আর বাড়তি operations হলো এই পথের নিয়মিত cost।
১৩ মনে রাখুন
- Monolith দিয়ে শুরু করুন, কিন্তু ভেতরটা modular রাখুন। পরে ভাগ করতে হলে পরিষ্কার module সবচেয়ে বড় সাহায্য।
- ভাগ করার কারণ হতে হবে একটা নির্দিষ্ট ব্যথা। Team এর আটকে যাওয়া, আলাদা scaling, বা failure ছড়িয়ে পড়া; শুধু ফ্যাশন নয়।
- সীমানা টানুন bounded context ধরে। যে কাজগুলো ঘন ঘন একসাথে কথা বলে, তাদের এক service এ রাখুন।
- অন্য service এর database এ সরাসরি হাত দেবেন না। Data লাগলে তার API তে চান।
- একা deploy করা না গেলে সেটা microservice নয়। একসাথে deploy করতে হলে আপনি distributed monolith বানিয়েছেন।
- আগে team সাজান, তারপর code। Conway’s law অনুযায়ী system শেষ পর্যন্ত team এর আকারই নেয়।
- Big bang rewrite নয়, strangler fig। ছোট ছোট ধাপে সরান, যাতে প্রতিটা ধাপ থেকে ফেরা যায়।