পাঠ ২৫ · ৫১-এর মধ্যে · মডিউল ৭
Home / Courses / System Design / মনোলিথ বনাম মাইক্রোসার্ভিস

মনোলিথ বনাম মাইক্রোসার্ভিস

Monolith vs microservices
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • মনোলিথ ও মাইক্রোসার্ভিস আর্কিটেকচারের মৌলিক পার্থক্য
  • মনোলিথের সুবিধা (সরলতা) ও অসুবিধা (রিডিপ্লয়মেন্ট স্কোপ, স্বাধীন স্কেলিং না থাকা)
  • মাইক্রোসার্ভিসের সুবিধা (স্বাধীন স্কেলিং/ডিপ্লয়মেন্ট) ও অসুবিধা (নেটওয়ার্ক জটিলতা, ডিস্ট্রিবিউটেড ট্রানজেকশন)
  • "Monolith first" কৌশল কেন অনেক বাস্তব দলের জন্য যুক্তিসঙ্গত সিদ্ধান্ত
  • Python দিয়ে দুটো আর্কিটেকচারে ডিপ্লয়মেন্ট স্কোপের পার্থক্য দেখা

১ · মনোলিথ আর্কিটেকচার

একটি মনোলিথে সম্পূর্ণ অ্যাপ্লিকেশনের সব ফিচার (ইউজার ম্যানেজমেন্ট, অর্ডার, পেমেন্ট, নোটিফিকেশন) একই কোডবেসে, একই প্রসেসে চলে, এবং একটি একক ইউনিট হিসেবে ডিপ্লয় হয়। শুরুতে এটি খুবই সহজ — একটি রিপো, একটি ডিপ্লয়মেন্ট পাইপলাইন, ট্রানজেকশন ম্যানেজমেন্ট সহজ (সব একই ডেটাবেসে, একই প্রসেসে)।

কিন্তু অ্যাপ বড় হওয়ার সাথে সাথে সমস্যা দেখা দেয় — বিল্ড/টেস্ট সময় বেড়ে যায়, একটি ছোট বাগ ফিক্স করতেও পুরো অ্যাপ রিডিপ্লয় করতে হয় (এবং তার সাথে জড়িত ঝুঁকি নিতে হয়), একটি অংশে (যেমন পেমেন্ট) বেশি ট্রাফিক এলেও পুরো অ্যাপ স্কেল করতে হয় (এমনকি যেসব অংশে দরকার নেই), এবং একটি নির্দিষ্ট প্রযুক্তিতে (ভাষা/ফ্রেমওয়ার্ক) পুরো টিম লক-ইন হয়ে যায়।

২ · মাইক্রোসার্ভিস আর্কিটেকচার

মাইক্রোসার্ভিসে অ্যাপ্লিকেশন ছোট ছোট, স্বাধীনভাবে ডিপ্লয়যোগ্য সার্ভিসে ভাঙা হয় — প্রতিটির নিজস্ব ডেটা ও কোডবেস থাকে, এবং তারা নেটওয়ার্কের মাধ্যমে (REST/gRPC — L06, L07, বা ইভেন্ট-ড্রিভেন — L22, L23) যোগাযোগ করে।

Monolith
একটি ডিপ্লয়েবল ইউনিট। শুরুতে সহজ, ট্রানজেকশন সহজ, কিন্তু বড় হলে বিল্ড/ডিপ্লয় ধীর, স্বাধীন স্কেলিং নেই, টেক লক-ইন।
Microservices
ছোট, স্বাধীন সার্ভিস। স্বাধীন স্কেলিং/ডিপ্লয়মেন্ট/প্রযুক্তি-পছন্দ, কিন্তু নেটওয়ার্ক কল ধীর/কম নির্ভরযোগ্য, ডিস্ট্রিবিউটেড ট্রানজেকশন কঠিন (L18, L28), সার্ভিস ডিসকভারি (L26) ও ক্রস-সার্ভিস মনিটরিং (L41) দরকার।
"Monolith First" — একটি বাস্তবসম্মত কৌশল

অনেক সফল কোম্পানি (এমনকি বড় বড় মাইক্রোসার্ভিস-ভিত্তিক কোম্পানিও) শুরুতে একটি মনোলিথ দিয়ে যাত্রা শুরু করে। কারণ, প্রথম দিকে আপনার টিম ছোট, ফিচারের সীমানা এখনও পরিষ্কার নয়, এবং মাইক্রোসার্ভিসের অপারেশনাল জটিলতা (সার্ভিস ডিসকভারি, ডিস্ট্রিবিউটেড ট্রেসিং, নেটওয়ার্ক ব্যর্থতা সামলানো) অহেতুক বোঝা হয়ে দাঁড়ায়। শুধু যখন একটি নির্দিষ্ট অংশে স্কেলিং চাপ পড়ে (যেমন পেমেন্ট সার্ভিস বাকি সবার চেয়ে ১০০ গুণ বেশি ট্রাফিক পাচ্ছে) অথবা টিম-বাউন্ডারি সমস্যা দেখা দেয় (একাধিক টিম একই কোডবেসে কাজ করতে গিয়ে বারবার সংঘর্ষ করছে), তখনই সেই নির্দিষ্ট অংশটুকু আলাদা সার্ভিস হিসেবে ভেঙে ফেলা যুক্তিসঙ্গত।

Monolith — one change, redeploy ALL: একটি অ্যাপ্লিকেশন (Users + Orders + Payments + Notifications) সব মডিউল একই প্রসেসে — payments বদলালেও সব রিডিপ্লয় হয় Microservices — one change, redeploy only that service: Users unchanged Orders unchanged Payments redeployed Notifications unchanged
মনোলিথে একটি পরিবর্তনেও পুরো ব্লক রিডিপ্লয় হয়; মাইক্রোসার্ভিসে শুধু প্রাসঙ্গিক সার্ভিসটি বদলায়।

৩ · Python-এ ডিপ্লয়মেন্ট স্কোপের পার্থক্য

নিচের কোডটি একটি হালকা, ইলাস্ট্রেটিভ ডেমো — বাস্তব ডিপ্লয়মেন্ট সিস্টেম নয়, বরং ধারণাটি দেখানোর জন্য। মনোলিথ ফাংশন একটি পরিবর্তনের জন্য সব মডিউল "রিডিপ্লয়" (রিসেট) করে, মাইক্রোসার্ভিস ফাংশন শুধু প্রাসঙ্গিক সার্ভিসটুকু আপডেট করে।

Python
# মনোলিথ — একটি shared state, যেকোনো পরিবর্তনে পুরো অ্যাপ রিডিপ্লয় হয়
monolith_state = {"users": "v1", "orders": "v1", "payments": "v1", "notifications": "v1"}

def monolith_redeploy(changed_module, new_version):
    print(f"মনোলিথ: '{changed_module}' মডিউলে পরিবর্তন -> পুরো অ্যাপ রিডিপ্লয় হচ্ছে...")
    for module in monolith_state:
        if module == changed_module:
            monolith_state[module] = new_version
        # প্রতিটি মডিউলই রিস্টার্ট/রিডিপ্লয় হয়, কোড না পাল্টালেও
        print(f"  redeploying {module} (version={monolith_state[module]})")
    print("  পুরো অ্যাপ ডাউনটাইমসহ রিস্টার্ট সম্পন্ন\n")

# মাইক্রোসার্ভিস — প্রতিটি সার্ভিস আলাদা, শুধু প্রাসঙ্গিক সার্ভিস আপডেট হয়
microservices_state = {"users": "v1", "orders": "v1", "payments": "v1", "notifications": "v1"}

def microservice_update(changed_service, new_version):
    print(f"মাইক্রোসার্ভিস: শুধু '{changed_service}' সার্ভিস আপডেট হচ্ছে...")
    microservices_state[changed_service] = new_version
    print(f"  redeploying {changed_service} (version={microservices_state[changed_service]})")
    print("  বাকি সব সার্ভিস অপরিবর্তিত ও চালু আছে\n")

monolith_redeploy("payments", "v2")
microservice_update("payments", "v2")

print(f"মনোলিথ চূড়ান্ত অবস্থা: {monolith_state}")
print(f"মাইক্রোসার্ভিস চূড়ান্ত অবস্থা: {microservices_state}")

    
দুই ক্ষেত্রেই চূড়ান্ত ডেটা একই (payments → v2, বাকি সব v1) — পার্থক্যটা প্রক্রিয়ায়়: মনোলিথে প্রতিটি মডিউল "ছোঁয়া" লেগেছে (৪টি redeploy লগ লাইন), মাইক্রোসার্ভিসে শুধু ১টি সার্ভিস ছোঁয়া লেগেছে। বাস্তব সিস্টেমে এই পার্থক্যটাই ডিপ্লয়মেন্ট রিস্ক, ডাউনটাইম উইন্ডো ও রিলিজ ফ্রিকোয়েন্সিতে বিশাল প্রভাব ফেলে।

৪ · সঠিক সিদ্ধান্ত নেওয়া

মাইক্রোসার্ভিস "বেশি আধুনিক" মানেই "সবসময় ভালো" নয়। প্রতিটি নতুন সার্ভিস একটি নতুন নেটওয়ার্ক নির্ভরতা, একটি নতুন ব্যর্থতার বিন্দু, এবং অপারেশনাল ওভারহেড (মনিটরিং, ডিপ্লয়মেন্ট পাইপলাইন, সার্ভিস ডিসকভারি) যোগ করে। সঠিক প্রশ্ন হলো — এই মুহূর্তে মনোলিথের ব্যথাটা (ডিপ্লয়মেন্ট গতি, স্কেলিং সীমাবদ্ধতা, টিম-সংঘর্ষ) কি মাইক্রোসার্ভিসের অতিরিক্ত জটিলতার চেয়ে বেশি ব্যয়বহুল? উত্তর "হ্যাঁ" হলেই ভাঙা উচিত, নাহলে নয়।

মূল কথা · Key takeaway

মনোলিথ ও মাইক্রোসার্ভিস একটি স্পেকট্রামের দুই প্রান্ত, দুটোর কোনোটাই সর্বজনীন "সঠিক উত্তর" নয়। এই মডিউলের (M7) বাকি পাঠগুলো (API গেটওয়ে/সার্ভিস ডিসকভারি L26, সার্কিট ব্রেকার L27, SAGA L28, ইভেন্ট সোর্সিং/CQRS L29) মূলত মাইক্রোসার্ভিসে গেলে যে নতুন সমস্যাগুলো তৈরি হয় সেগুলোর সমাধান — অর্থাৎ এই ট্রেড-অফটা মেনে নেওয়ার পর কীভাবে এর জটিলতা সামলাতে হয়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ একটি ছোট স্টার্টআপ (৫ জন ইঞ্জিনিয়ার) কেন প্রথম দিনেই মাইক্রোসার্ভিস আর্কিটেকচার দিয়ে শুরু না করে মনোলিথ দিয়ে শুরু করবে?

৫ জনের টিম একটি মনোলিথ সহজে বুঝতে, ডিবাগ করতে ও ডিপ্লয় করতে পারে — কোড একই জায়গায়, ট্রানজেকশন স্বাভাবিকভাবেই সামঞ্জস্যপূর্ণ, এবং কোনো সার্ভিস ডিসকভারি/নেটওয়ার্ক ব্যর্থতা সামলানোর দরকার নেই। মাইক্রোসার্ভিস দিয়ে শুরু করলে এই ছোট টিমকে অতিরিক্ত অপারেশনাল কাজে (একাধিক ডিপ্লয়মেন্ট পাইপলাইন, সার্ভিস-টু-সার্ভিস অথেন্টিকেশন, ডিস্ট্রিবিউটেড ডিবাগিং) সময় ব্যয় করতে হবে, অথচ এই মুহূর্তে তাদের কোনো স্কেলিং বা টিম-বাউন্ডারি সমস্যাই নেই যা সমাধান করার প্রয়োজন। ফিচার বানানোর গতিই এই পর্যায়ে সবচেয়ে গুরুত্বপূর্ণ, এবং মনোলিথ সেটাই দেয়।

প্র ০২ মাইক্রোসার্ভিসে গেলে ডিস্ট্রিবিউটেড ট্রানজেকশন কেন হঠাৎ একটি বড় সমস্যা হয়ে দাঁড়ায়, যেখানে মনোলিথে এটি প্রায় বিনামূল্যে পাওয়া যেত?

মনোলিথে সব ডেটা সাধারণত একই ডেটাবেসে থাকে, তাই একটি ট্রানজেকশন (যেমন "স্টক কমাও এবং অর্ডার তৈরি করো") একটি স্বাভাবিক ডেটাবেস ট্রানজেকশনে (BEGIN/COMMIT) atomic ভাবে করা যায়। মাইক্রোসার্ভিসে "স্টক" ও "অর্ডার" আলাদা সার্ভিসের আলাদা ডেটাবেসে থাকে — একটি নেটওয়ার্ক কল ব্যর্থ হলে দুটো ডেটাবেস অসামঞ্জস্যপূর্ণ (inconsistent) অবস্থায় থেকে যেতে পারে। এই সমস্যার সমাধানে 2PC (L18, যা ব্লকিং ও মাইক্রোসার্ভিসের সাথে ভালো খাপ খায় না) বা SAGA প্যাটার্ন (L28, কম্পেনসেটিং ট্রানজেকশন দিয়ে) ব্যবহার করা হয়।

প্র ০৩ "শুধু স্কেলিং চাপ পড়া অংশটুকু মাইক্রোসার্ভিসে ভাঙা উচিত" — এই নীতি অনুযায়ী একটি ই-কমার্স মনোলিথে সবচেয়ে আগে কোন অংশটি ভাঙা যুক্তিসঙ্গত হতে পারে, এবং কেন?

সাধারণত Payments বা Search/Catalog সার্ভিস — কারণ এদের ট্রাফিক প্যাটার্ন ও নন-ফাংশনাল রিকোয়ারমেন্ট বাকি সিস্টেম থেকে স্পষ্টভাবে আলাদা (পেমেন্টে কড়া কনসিস্টেন্সি ও সিকিউরিটি দরকার, সার্চে ভিন্ন ধরনের ইনডেক্সিং/স্কেলিং দরকার — L49-এর অটোকমপ্লিটের মতো)। যখন একটি নির্দিষ্ট মডিউলের রিসোর্স চাহিদা, স্কেলিং প্যাটার্ন বা পরিবর্তনের হার বাকি সিস্টেম থেকে স্পষ্টভাবে ভিন্ন হয়ে যায়, সেটাই সেই মডিউলকে আলাদা সার্ভিস হিসেবে ভাঙার সবচেয়ে শক্তিশালী সংকেত।

অনুশীলন

  1. চিন্তা করুন: আপনার পরিচিত একটি বড় অ্যাপ (যেমন bKash, Daraz) কল্পনা করুন এবং সেটিকে মাইক্রোসার্ভিসে ভাঙলে কমপক্ষে ৪টি সম্ভাব্য সার্ভিস (যেমন User, Payment, Notification, Catalog) চিহ্নিত করুন এবং প্রতিটির জন্য কেন সেটি আলাদা সার্ভিস হওয়া যুক্তিসঙ্গত তা লিখুন।

    উদাহরণ (Daraz-এর মতো একটি ই-কমার্স অ্যাপ): User Service — লগইন/প্রোফাইল, তুলনামূলক স্থিতিশীল ট্রাফিক। Catalog Service — প্রোডাক্ট সার্চ/ব্রাউজ, সবচেয়ে বেশি রিড ট্রাফিক, স্বাধীন ক্যাশিং (L19) দরকার। Order Service — চেকআউট ফ্লো, কড়া কনসিস্টেন্সি দরকার। Payment Service — সর্বোচ্চ নিরাপত্তা ও কমপ্লায়েন্স প্রয়োজন, আলাদা টিম/অ্যাক্সেস কন্ট্রোল যুক্তিসঙ্গত। প্রতিটির ভিন্ন স্কেলিং প্যাটার্ন ও রিস্ক প্রোফাইল থাকায় আলাদা সার্ভিস করা যুক্তিসঙ্গত।

  2. কোড পরিবর্তন করুন: উপরের কোড সেলে monolith_redeploy()-এ একটি কাউন্টার যোগ করুন যা গোনে মোট কতগুলো মডিউল "redeploy" হলো, এবং microservice_update()-এও একইভাবে গোনে। দুটি ফাংশন একবার করে কল করার পর দুই কাউন্টারের পার্থক্য প্রিন্ট করুন।

    সমাধানের কাঠামো: প্রতিটি ফাংশনে একটি লোকাল কাউন্টার রেখে প্রতিটি "redeploying" লাইনে ১ যোগ করে রিটার্ন করা যায়। monolith_redeploy() ৪টি মডিউল নিয়ে কাজ করলে কাউন্ট হবে ৪, আর microservice_update()-এ কাউন্ট হবে ১। পার্থক্য = ৩ — অর্থাৎ একই পরিবর্তনের জন্য মনোলিথে ৩টি অতিরিক্ত, সম্পূর্ণ অপ্রয়োজনীয় মডিউল "ছোঁয়া" লেগেছে যা মাইক্রোসার্ভিসে এড়ানো গেছে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
ব্যাচ বনাম স্ট্রিম প্রসেসিং