মনোলিথ বনাম মাইক্রোসার্ভিস ডিপ্লয়মেন্ট স্ট্র্যাটেজি
এই পাঠে যা শিখবেন
- মনোলিথ ডিপ্লয়মেন্টের atomic (all-or-nothing) বৈশিষ্ট্য কেন এবং কীভাবে গঠনগতভাবে (structurally) নিশ্চিত হয়
- মাইক্রোসার্ভিস ডিপ্লয়মেন্টে কেন আংশিক-ব্যর্থতা (এক সার্ভিস ব্যর্থ, বাকিরা সফল) সম্ভব
- একটি "মিশ্র-ভার্সন" অবস্থা বাস্তবে সিমুলেট করে দেখা, এবং সেটি মনোলিথে কেন কখনো ঘটতে পারে না তা যাচাই করা
- এই দুই স্ট্র্যাটেজির মধ্যে সিদ্ধান্ত নেওয়ার সময় বিবেচ্য বিষয়
১ · মনোলিথ ডিপ্লয়মেন্ট — একক, atomic ইউনিট
একটি মনোলিথ অ্যাপ্লিকেশনের সব অংশ (ফ্রন্ট-এন্ড সার্ভিং, API, ব্যবসায়িক লজিক) একটি একক প্রসেস/আর্টিফ্যাক্ট হিসেবে প্যাক করা ও ডিপ্লয় করা হয়। ডিপ্লয়মেন্ট মানে একটাই কল — পুরো অ্যাপ্লিকেশনকে একসাথে নতুন ভার্সনে সুইচ করা। এর ফলে ডিপ্লয়মেন্ট স্বভাবতই atomic হয় — হয় পুরো অপারেশন সফল, নাহলে পুরোটাই ব্যর্থ হয়ে আগের অবস্থায় ফিরে যায় (rollback)। যেহেতু ডিপ্লয় করার মতো ইউনিট মাত্র একটি, "কিছু অংশ নতুন, কিছু অংশ পুরনো" — এমন মিশ্র অবস্থা তৈরি হওয়ার কোনো উপায় নেই।
২ · মাইক্রোসার্ভিস ডিপ্লয়মেন্ট — একাধিক স্বাধীন ইউনিট
মাইক্রোসার্ভিস আর্কিটেকচারে অ্যাপ্লিকেশন একাধিক ছোট, স্বাধীন সার্ভিসে ভাগ করা থাকে (যেমন auth-service, user-service, payment-service) — প্রতিটির নিজস্ব ডিপ্লয়মেন্ট চক্র আছে। এর মানে প্রতিটি সার্ভিস আলাদাভাবে ডিপ্লয় করা হয় — একটি সার্ভিসের ডিপ্লয়মেন্ট ব্যর্থ হলেও অন্য সার্ভিসগুলোর ডিপ্লয়মেন্ট প্রভাবিত হয় না। এটি নমনীয়তা দেয় (একটি সার্ভিস স্বাধীনভাবে আপডেট/স্কেল করা যায়), কিন্তু এর মূল্য হলো সিস্টেমটি সাময়িকভাবে মিশ্র-ভার্সন অবস্থায় থাকতে পারে — কিছু সার্ভিস নতুন ভার্সনে, কিছু এখনো পুরনো ভার্সনে।
১টি ডিপ্লয় কল → সব-অথবা-কিছুই। সরল, কিন্তু ছোট পরিবর্তনের জন্যও পুরো অ্যাপ পুনরায় ডিপ্লয় করতে হয়।
N-টি স্বাধীন ডিপ্লয় কল → আংশিক-সাফল্য সম্ভব। নমনীয়, কিন্তু ভার্সন সামঞ্জস্যতা নিজে থেকে সামলাতে হয় (ব্যাকওয়ার্ড-কম্প্যাটিবল API ডিজাইন প্রয়োজন)।
৩ · সিমুলেশন — মনোলিথ কখনো মিশ্রিত হয় না, মাইক্রোসার্ভিস হতে পারে
নিচের কোড সেলে দুটো অংশ আছে। প্রথমে deploy_monolith() — একটি একক ফাংশন কল যা "কম্পোনেন্ট"-দের
একটি ডিকশনারি নিয়ে হয় সবগুলোকে একসাথে নতুন ভার্সনে পাঠায়, নাহলে ব্যর্থ হলে সবগুলোকে অপরিবর্তিত (রোলব্যাক)
রাখে — একটি সফল ও একটি ব্যর্থ রান, দুটোতেই ফলাফলের ভার্সন-সেটের আকার (len(set(...))) হিসাব করে
দেখানো হয়েছে এটি সবসময় 1 — অর্থাৎ মিশ্র অবস্থা কখনো ঘটে না। এরপর deploy_service()
— প্রতিটি সার্ভিসের জন্য আলাদাভাবে কল হয়; ৪টি সার্ভিসের মধ্যে ৩টি সফল হয় ও একটি (payment-service)
ইচ্ছাকৃতভাবে ব্যর্থ হয় — চূড়ান্ত অবস্থায় সত্যিই মিশ্র ভার্সন (কিছু নতুন, কিছু পুরনো) দেখা যায়।
# ---------- মনোলিথ: একক, atomic ডিপ্লয়মেন্ট ----------
def deploy_monolith(components, new_version, fail=False):
"""components: {কম্পোনেন্ট_নাম: বর্তমান_ভার্সন} -- পুরো অ্যাপ একটি একক
ডিপ্লয় কলে ডিপ্লয় হয়। সফল হলে সব কম্পোনেন্ট একসাথে new_version-এ যায়;
ব্যর্থ হলে সবগুলো অপরিবর্তিত (রোলব্যাক) থাকে -- কখনো কিছু কম্পোনেন্ট নতুন,
কিছু পুরনো থাকতে পারে না, কারণ এখানে প্রতিটি কম্পোনেন্টের জন্য আলাদা
ডিপ্লয় কল নেই -- একটিমাত্র কল সবকিছু ঠিক করে।"""
if fail:
return dict(components), "ROLLED_BACK" # সব কম্পোনেন্ট অপরিবর্তিত
return {name: new_version for name in components}, "DEPLOYED"
components = {"web": "v1.0.0", "api": "v1.0.0", "worker": "v1.0.0"}
print("=== মনোলিথ -- সফল ডিপ্লয়মেন্ট ===")
state_ok, status_ok = deploy_monolith(components, "v1.1.0", fail=False)
print(f" {state_ok} | status={status_ok}")
print(f" আলাদা ভার্সনের সংখ্যা: {len(set(state_ok.values()))}")
print("\n=== মনোলিথ -- ব্যর্থ ডিপ্লয়মেন্ট ===")
state_fail, status_fail = deploy_monolith(components, "v1.1.0", fail=True)
print(f" {state_fail} | status={status_fail}")
print(f" আলাদা ভার্সনের সংখ্যা: {len(set(state_fail.values()))}")
print(" (দুটো রানেই আলাদা ভার্সনের সংখ্যা 1 -- মিশ্র অবস্থা structurally অসম্ভব)")
# ---------- মাইক্রোসার্ভিস: একাধিক স্বাধীন ডিপ্লয়মেন্ট ----------
def deploy_service(name, current_version, new_version, fail=False):
"""একটি একক সার্ভিসকে স্বাধীনভাবে ডিপ্লয় করে -- অন্য কোনো সার্ভিসের
সাফল্য/ব্যর্থতার সাথে এর কোনো সম্পর্ক নেই।"""
if fail:
return current_version, "FAILED"
return new_version, "DEPLOYED"
services = {
"auth-service": {"current": "v2.3.0", "new": "v2.4.0", "fail": False},
"user-service": {"current": "v1.8.0", "new": "v1.9.0", "fail": False},
"payment-service": {"current": "v3.1.0", "new": "v3.2.0", "fail": True}, # ইচ্ছাকৃতভাবে ব্যর্থ
"notify-service": {"current": "v1.0.4", "new": "v1.0.5", "fail": False},
}
print("\n=== মাইক্রোসার্ভিস -- আংশিক-ব্যর্থতা পরিস্থিতি ===")
final_state = {}
for name, info in services.items():
version, status = deploy_service(name, info["current"], info["new"], fail=info["fail"])
final_state[name] = version
print(f" {name:18s} -> {version:8s} | {status}")
new_versions = {info["new"] for info in services.values()}
old_versions = {info["current"] for info in services.values()}
any_on_new = any(v in new_versions for v in final_state.values())
any_on_old = any(v in old_versions for v in final_state.values())
mixed = any_on_new and any_on_old
print(f"\nচূড়ান্ত অবস্থায় কেউ নতুন ভার্সনে আছে?: {any_on_new}")
print(f"চূড়ান্ত অবস্থায় কেউ পুরনো ভার্সনে আছে?: {any_on_old}")
print(f"মিশ্র-ভার্সন অবস্থা ঘটেছে?: {mixed}")
print("(payment-service পুরনো ভার্সনে রয়ে গেছে, বাকি তিনটি নতুন ভার্সনে চলে গেছে --")
print(" এই মিশ্র অবস্থা মাইক্রোসার্ভিসে সম্ভব, কিন্তু উপরের মনোলিথ সিমুলেশনে কখনোই ঘটেনি)")
components ডিকশনারির সব কম্পোনেন্ট শুরুতে একই ভার্সন ("v1.0.0")
থেকে শুরু করে বলে len(set(...)) হিসাব সরাসরি বোঝায় — সফল রানে সবাই
"v1.1.0"-এ যায় (সেট সাইজ ১), ব্যর্থ রানে সবাই "v1.0.0"-এ থেকে যায় (সেট সাইজ ১) —
কোনো রানেই সেট সাইজ ১-এর বেশি হয় না। মাইক্রোসার্ভিস সিমুলেশনে payment-service-এর
fail=True থাকায় সে তার current_version ("v3.1.0") ধরে রাখে, যেখানে
বাকি তিনটি সার্ভিস তাদের নিজ নিজ new ভার্সনে চলে যায় — তাই mixed
True হয়, যা মনোলিথে কখনো সম্ভব নয়।
মনোলিথ ডিপ্লয়মেন্ট একটি একক ডিপ্লয় কলের মধ্য দিয়ে যায় বলে গঠনগতভাবেই atomic — সব-অথবা-কিছুই, মিশ্র
অবস্থা অসম্ভব। মাইক্রোসার্ভিস ডিপ্লয়মেন্ট একাধিক স্বাধীন কলের মধ্য দিয়ে যায় বলে নমনীয় (একটি সার্ভিস
বাকিদের প্রভাবিত না করেই ব্যর্থ/সফল হতে পারে) কিন্তু এর দাম হলো সাময়িক মিশ্র-ভার্সন অবস্থা সামলানোর
দায়িত্ব — যেমন ব্যাকওয়ার্ড-কম্প্যাটিবল API ডিজাইন। ব্যাপক distributed-systems স্কেল আলোচনা
../system-design/ কোর্সে রয়েছে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
উপরের কোডে deploy_monolith()-এ যদি প্রতিটি কম্পোনেন্টের জন্য আলাদা
if/for লুপে আলাদা সাফল্য/ব্যর্থতা নির্ধারণ করা হতো, তাহলে এটি কি এখনো "মনোলিথ" ডিপ্লয়মেন্ট
বলা যেত?
না — যদি প্রতিটি কম্পোনেন্টের নিজস্ব স্বাধীন সাফল্য/ব্যর্থতা থাকতে পারত, তাহলে সেটি আসলে মাইক্রোসার্ভিস
প্যাটার্নের অনুকরণ হয়ে যেত, নাম যাই হোক না কেন। মনোলিথের সংজ্ঞাগত বৈশিষ্ট্যই হলো একটি একক ডিপ্লয়
সিদ্ধান্ত সব কম্পোনেন্টের জন্য প্রযোজ্য — উপরের কোডে fail একটি একক প্যারামিটার যা পুরো কলকে
নিয়ন্ত্রণ করে, প্রতিটি কম্পোনেন্টকে আলাদাভাবে নয় — এটাই এর atomicity নিশ্চিত করে।
প্র ০২
মাইক্রোসার্ভিস সিমুলেশনে payment-service ব্যর্থ হলেও auth-service,
user-service, notify-service কেন প্রভাবিত হয়নি?
কারণ প্রতিটি সার্ভিসের জন্য deploy_service() স্বতন্ত্রভাবে, নিজস্ব প্যারামিটার
(current_version, new_version, fail) নিয়ে কল হয়েছে — একটি
কলের রিটার্ন মান অন্য কলকে কোনোভাবেই প্রভাবিত করে না। লুপে প্রতিটি সার্ভিস তার নিজের
info["fail"] মান অনুযায়ী স্বাধীনভাবে সফল বা ব্যর্থ হয়েছে — এটাই মাইক্রোসার্ভিসের মূল
বৈশিষ্ট্য যা মনোলিথে সম্ভব নয়।
প্র ০৩
mixed = any_on_new and any_on_old — এই বুলিয়ান এক্সপ্রেশনটি "মিশ্র অবস্থা" সঠিকভাবে
শনাক্ত করে কেন? যদি সবগুলো সার্ভিস ব্যর্থ হতো, তাহলে mixed-এর মান কী হতো?
mixed তখনই True হয় যখন চূড়ান্ত অবস্থায় অন্তত একটি সার্ভিস নতুন ভার্সনে
এবং অন্তত একটি সার্ভিস পুরনো ভার্সনে থাকে — ঠিক যেটাকে আমরা "মিশ্র" বলি। যদি সবগুলো সার্ভিস
ব্যর্থ হতো, তাহলে any_on_new হতো False (কেউই নতুন ভার্সনে যায়নি), তাই
mixed হতো False — এটি সঠিক, কারণ "সবাই পুরনো ভার্সনে" একটি সুসংগত
(uniform, যদিও ব্যর্থ) অবস্থা, মিশ্র অবস্থা নয়।
অনুশীলন
-
চিন্তা করুন: একটি ছোট টিমের, কম ট্রাফিকের একটি প্রজেক্টের জন্য মনোলিথ ভালো নাকি
মাইক্রোসার্ভিস — কোন বিষয়গুলো এই সিদ্ধান্তে প্রভাব ফেলে?
ছোট টিম, কম ট্রাফিকের প্রজেক্টের জন্য সাধারণত মনোলিথ ভালো পছন্দ — কারণ একাধিক সার্ভিস পরিচালনা করার অপারেশনাল জটিলতা (প্রতিটির আলাদা ডিপ্লয়মেন্ট, নেটওয়ার্ক কল, ভার্সন সামঞ্জস্যতা) ছোট টিমের জন্য অতিরিক্ত বোঝা হয়ে দাঁড়ায়, অথচ এত কম ট্রাফিকে মাইক্রোসার্ভিসের স্বাধীন-স্কেলিং সুবিধা কার্যত অব্যবহৃত থেকে যায়। মাইক্রোসার্ভিসের সুবিধা তখনই স্পষ্টভাবে প্রকাশ পায় যখন টিমের আকার ও ট্রাফিকের স্কেল বড় হয় এবং বিভিন্ন অংশ ভিন্ন হারে পরিবর্তন/স্কেল করার প্রয়োজন হয়।
-
পরীক্ষা করুন: উপরের কোড সেলে
servicesডিকশনারিতে"user-service"-এর"fail"মানওTrueকরে দিন (এখন দুটো সার্ভিস ব্যর্থ), Run চেপে দেখুনfinal_state-এ কতগুলো সার্ভিস পুরনো ভার্সনে থাকে এবংmixed-এর মান কী হয়।এখন
payment-serviceওuser-service— দুটোই তাদেরcurrent_version-এ থেকে যাবে, আরauth-service,notify-serviceনতুন ভার্সনে যাবে।mixedএখনওTrueথাকবে, কারণ এখনও অন্তত একটি সার্ভিস নতুন ভার্সনে এবং অন্তত একটি পুরনো ভার্সনে আছে — শুধু "কতগুলো" সার্ভিস মিশ্রণের কোন পাশে আছে তা বদলেছে, মিশ্র-অবস্থা থাকা/না-থাকার সিদ্ধান্ত বদলায়নি। এটি দেখায় মাইক্রোসার্ভিসে একাধিক স্বাধীন ব্যর্থতা একসাথে ঘটতে পারে, প্রতিটি অন্যগুলো থেকে সম্পূর্ণ স্বাধীনভাবে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
- System Design কোর্স সহোদর কোর্স মাইক্রোসার্ভিস স্কেল করা, সার্ভিস ডিসকভারি ও ব্যাপক distributed-systems আর্কিটেকচারের বিস্তারিত সেই কোর্সে কভার করা হয়েছে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।