পাঠ ২৮ · ৫১-এর মধ্যে · মডিউল ৭
Home / Courses / System Design / SAGA প্যাটার্ন

SAGA প্যাটার্ন — ডিস্ট্রিবিউটেড ট্রানজেকশন ম্যানেজমেন্ট

The SAGA pattern
৯ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কেন 2PC মাইক্রোসার্ভিস আর্কিটেকচারে সমস্যাযুক্ত, এবং SAGA কীভাবে ভিন্ন পথে একই সমস্যা সমাধান করে
  • Compensating transaction ধারণা এবং একটি Order Saga-এর সম্পূর্ণ worked example
  • Choreography বনাম Orchestration — কখন কোনটি বেছে নেবেন
  • Python দিয়ে একটি অরকেস্ট্রেশন-স্টাইল সাগা ইমপ্লিমেন্ট করা, যেখানে একটি ধাপ ব্যর্থ হলে আগের ধাপের কম্পেনসেশন স্বয়ংক্রিয়ভাবে চলে

১ · কেন 2PC মাইক্রোসার্ভিসে ভালো মানায় না

L18-এ আমরা দেখেছি Two-Phase Commit (2PC) কীভাবে একটি কোঅর্ডিনেটরের মাধ্যমে সব অংশগ্রহণকারীকে একসাথে কমিট বা অ্যাবোর্ট করায়। কিন্তু এর একটি বড় দুর্বলতা আছে — এটি blocking: Phase 1-এ ভোট দেওয়ার পর একটি অংশগ্রহণকারী তার রিসোর্স লক করে রাখে এবং কোঅর্ডিনেটরের চূড়ান্ত সিদ্ধান্তের জন্য অপেক্ষা করে। কোঅর্ডিনেটর যদি সেই মুহূর্তে ক্র্যাশ করে, লকগুলো অনির্দিষ্টকালের জন্য আটকে থাকতে পারে। মাইক্রোসার্ভিস আর্কিটেকচারে (L25) প্রতিটি সার্ভিসের নিজস্ব ডেটাবেস থাকে এবং সার্ভিসগুলো স্বাধীনভাবে স্কেল/ডিপ্লয় হতে চায় — এমন একটি টাইট-কাপলড, ব্লকিং মেকানিজম এই লক্ষ্যের সরাসরি বিপরীত।

২ · SAGA প্যাটার্ন — Compensating Transaction দিয়ে সমাধান

SAGASAGA Patternএকটি ডিস্ট্রিবিউটেড ট্রানজেকশনকে একাধিক স্থানীয় (local) ট্রানজেকশনের ধারাবাহিকতায় ভাঙা হয়, প্রতিটির একটি নির্দিষ্ট compensating transaction থাকে যা প্রয়োজনে সেই ধাপের প্রভাব বাতিল করে দেয়। প্রতিটি ধাপকে একটি স্বাধীন লোকাল ট্রানজেকশন হিসেবে দেখে, যা নিজের ডেটাবেসেই সাথে সাথে কমিট হয়ে যায় — কোনো লক ধরে রাখা লাগে না। যদি কোনো পরের ধাপ ব্যর্থ হয়, তাহলে ইতিমধ্যে সম্পন্ন ধাপগুলোর compensating transactionCompensating Transactionএকটি নির্দিষ্ট সম্পন্ন ধাপের প্রভাব বাতিল করার জন্য ডিজাইন করা একটি বিপরীত অপারেশন — যেমন "ইনভেন্টরি রিজার্ভ করো"-এর কম্পেনসেশন হলো "রিজার্ভেশন ছেড়ে দাও"। উল্টো ক্রমে চালিয়ে সিস্টেমকে একটি সঙ্গতিপূর্ণ অবস্থায় ফিরিয়ে আনা হয়।

Worked Example — Order Saga

একটি অর্ডার প্রসেসিং সাগা তিনটি ধাপে বিভক্ত: (১) reserve_inventory — পণ্য রিজার্ভ করা, (২) charge_payment — পেমেন্ট কাটা, (৩) confirm_order — অর্ডার নিশ্চিত করা। যদি ধাপ ২ (পেমেন্ট) ব্যর্থ হয় — ধরুন গ্রাহকের কার্ডে পর্যাপ্ত টাকা নেই — তাহলে সাগা ধাপ ৩ চালাবে না, এবং ধাপ ১-এর কম্পেনসেশন release_inventory চালিয়ে রিজার্ভ করা পণ্যটি আবার স্টকে ফিরিয়ে দেবে।

৩ · Choreography বনাম Orchestration

Choreography
কোনো কেন্দ্রীয় কোঅর্ডিনেটর নেই — প্রতিটি সার্ভিস একটি ইভেন্ট শোনে এবং নিজের কাজ শেষে পরের ইভেন্ট emit করে (L23-এর ইভেন্ট-ড্রিভেন আর্কিটেকচারের সাথে সরাসরি সম্পর্কিত)। সরল, কম কাপলিং, কিন্তু পুরো ফ্লো বোঝা কঠিন হতে পারে।
Orchestration
একটি কেন্দ্রীয় Saga Orchestrator স্পষ্টভাবে প্রতিটি ধাপ ও প্রয়োজনে কম্পেনসেশন নির্দেশ দেয়। ফ্লো এক জায়গায় দেখা যায়, ডিবাগ করা সহজ, কিন্তু orchestrator নিজেই একটি অতিরিক্ত কম্পোনেন্ট।
ধাপ ১: reserve_inventory success ধাপ ২: charge_payment FAILED ধাপ ৩: confirm_order skipped কম্পেনসেট ধাপ ১: release_inventory ব্যর্থ! rollback শুরু
ধাপ ২ (charge_payment) ব্যর্থ হলে ধাপ ৩ আর চলে না, এবং ধাপ ১-এর কম্পেনসেশন (release_inventory) স্বয়ংক্রিয়ভাবে চলে সিস্টেমকে সঙ্গতিপূর্ণ অবস্থায় ফেরায়।

৪ · Python দিয়ে অরকেস্ট্রেশন-স্টাইল সাগা ইমপ্লিমেন্ট করা

নিচের কোডে প্রতিটি ধাপ একটি (step_function, compensating_function) জোড়া হিসেবে সংজ্ঞায়িত। অরকেস্ট্রেটর ধাপগুলো ক্রমান্বয়ে চালায়; কোনো ধাপ ব্যর্থ হলে ইতিমধ্যে সম্পন্ন ধাপগুলোর কম্পেনসেশন উল্টো ক্রমে চালানো হয়।

Python
log = []

def reserve_inventory():
    log.append("ধাপ ১: reserve_inventory -> সফল")
    return True

def release_inventory():
    log.append("কম্পেনসেশন ১: release_inventory -> সফল (পণ্য স্টকে ফেরত)")

def charge_payment():
    log.append("ধাপ ২: charge_payment -> ব্যর্থ (অপর্যাপ্ত ব্যালেন্স)")
    return False

def refund_payment():
    log.append("কম্পেনসেশন ২: refund_payment -> সফল")

def confirm_order():
    log.append("ধাপ ৩: confirm_order -> সফল")
    return True

def cancel_order():
    log.append("কম্পেনসেশন ৩: cancel_order -> সফল")


# প্রতিটি এন্ট্রি: (স্টেপ ফাংশন, তার কম্পেনসেটিং ফাংশন)
order_saga = [
    (reserve_inventory, release_inventory),
    (charge_payment,   refund_payment),
    (confirm_order,    cancel_order),
]

def run_saga(steps):
    completed = []
    for step, compensate in steps:
        success = step()
        if not success:
            log.append("-> সাগা ব্যর্থ! ইতিমধ্যে সম্পন্ন ধাপগুলোর কম্পেনসেশন উল্টো ক্রমে চালানো হচ্ছে:")
            for _, comp in reversed(completed):
                comp()
            return False
        completed.append((step, compensate))
    log.append("-> সাগা সফলভাবে সম্পন্ন")
    return True


result = run_saga(order_saga)

print("=== Saga Execution Log ===")
for line in log:
    print(line)
print()
print("চূড়ান্ত ফলাফল:", "SUCCESS" if result else "ROLLED BACK (compensated)")

    
লক্ষ্য করুন — ধাপ ৩ (confirm_order) কখনোই চলে না, কারণ ধাপ ২ ব্যর্থ হওয়ার সাথে সাথেই সাগা থেমে যায়। শুধু ইতিমধ্যে সম্পন্ন ধাপ ১-এর কম্পেনসেশন (release_inventory) চলে — ধাপ ২-এর নিজের কম্পেনসেশন (refund_payment) চলে না, কারণ পেমেন্টটি আসলে কখনো সফল হয়ইনি, তাই বাতিল করার কিছু নেই।

৫ · SAGA-এর ট্রেড-অফ

SAGA 2PC-এর মতো কড়া atomicity ও isolation দেয় না — ধাপ ১ ও ২-এর মাঝামাঝি সময়ে অন্য কোনো প্রক্রিয়া হয়তো "পণ্য রিজার্ভড কিন্তু পেমেন্ট এখনও হয়নি" এমন একটি মধ্যবর্তী অবস্থা দেখতে পারে। বিনিময়ে SAGA কোনো লক ধরে রাখে না, তাই প্রতিটি সার্ভিস স্বাধীনভাবে availability ও scalability বজায় রাখতে পারে। বাস্তব সিস্টেমে compensating transaction ডিজাইন করা কঠিন হতে পারে বিশেষত যখন একটি ধাপের প্রভাব সম্পূর্ণভাবে বাতিলযোগ্য নয় (যেমন একটি ইমেইল পাঠানো ইতিমধ্যে হয়ে গেলে তা "ফেরত" নেওয়া যায় না) — এক্ষেত্রে একটি ক্ষমাপ্রার্থী নোটিফিকেশন পাঠানোই ব্যবহারিক কম্পেনসেশন হতে পারে।

মূল কথা · Key takeaway

SAGA প্যাটার্ন মাইক্রোসার্ভিস জগতে ডিস্ট্রিবিউটেড ট্রানজেকশনের বাস্তবসম্মত সমাধান — এটি 2PC-এর ব্লকিং সমস্যা এড়ায় compensating transaction-এর মাধ্যমে "এগিয়ে যাও অথবা পিছিয়ে এসো" নীতি অনুসরণ করে, কড়া isolation-এর বদলে availability ও loose coupling বেছে নিয়ে।

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

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

প্র ০১ SAGA-তে কেন প্রতিটি ধাপের কম্পেনসেশন নিজে নিজেই ব্যর্থ হতে পারে না, এমনটা ধরে নেওয়া হয় (বা তার জন্যও আলাদা ব্যবস্থা লাগে)?

কম্পেনসেশন ব্যর্থ হলে সিস্টেম একটি অসঙ্গতিপূর্ণ অবস্থায় আটকে যেতে পারে — যেমন পণ্য রিজার্ভড থেকে গেল অথচ অর্ডার বাতিল হয়ে গেছে। বাস্তব সিস্টেমে কম্পেনসেশনগুলোকে সাধারণত নিজেরাই idempotent (L38) এবং রিট্রাই-সেফ ডিজাইন করা হয়, এবং ব্যর্থ হলে একটি ডেড-লেটার কিউতে (L22) পাঠিয়ে ম্যানুয়াল/অটোমেটেড পুনঃচেষ্টার ব্যবস্থা রাখা হয় — সম্পূর্ণ নিশ্চয়তা দেওয়া যায় না, তবে ব্যর্থতার সম্ভাবনা যতটা সম্ভব কমানো হয়।

প্র ০২ Choreography-ভিত্তিক সাগায় সার্ভিসের সংখ্যা বাড়লে কী সমস্যা দেখা দিতে পারে, যা Orchestration এড়ায়?

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

প্র ০৩ SAGA কেন 2PC-এর মতো "সব নয়তো কিছুই না" (all-or-nothing) atomicity গ্যারান্টি দেয় না, অথচ তবুও ব্যবহারযোগ্য?

কারণ SAGA-এর প্রতিটি ধাপ নিজের ডেটাবেসে সাথে সাথে কমিট হয়ে যায় — কোনো গ্লোবাল লক বা "সবাই প্রস্তুত কিনা" যাচাইয়ের অপেক্ষা নেই। তাই মাঝপথে ব্যর্থতা ঘটলে ইতিমধ্যে কমিট হওয়া ধাপগুলো সাময়িকভাবে দৃশ্যমান থাকে, যতক্ষণ না কম্পেনসেশন চলে। এটি ব্যবহারযোগ্য কারণ compensating transaction দিয়ে শেষ পর্যন্ত সিস্টেমকে সঙ্গতিপূর্ণ অবস্থায় ফেরানো যায় — শুধু তাৎক্ষণিক নয়, বরং "এভেন্চুয়াল" সঙ্গতি (L04-এর eventual consistency ধারণার সাথে সংযোগ)। বেশিরভাগ ব্যবসায়িক প্রক্রিয়ায় এই সাময়িক অসঙ্গতি গ্রহণযোগ্য, বিনিময়ে সিস্টেম অনেক বেশি availability পায়।

অনুশীলন

  1. কোড বদলান: উপরের কোডে charge_payment-কে সফল (return True) করে দিন এবং confirm_order-কে ব্যর্থ (return False) করুন। এখন কোন কোন কম্পেনসেশন চলবে বলে মনে করেন? কোডে বদলে চালিয়ে মিলিয়ে দেখুন।

    এবার ধাপ ১ ও ২ দুটোই সফল হবে, তাই completed-এ দুটো এন্ট্রি থাকবে। ধাপ ৩ ব্যর্থ হলে সাগা উল্টো ক্রমে দুটো কম্পেনসেশনই চালাবে — প্রথমে refund_payment (ধাপ ২-এর কম্পেনসেশন, উল্টো ক্রমের কারণে আগে), তারপর release_inventory (ধাপ ১-এর কম্পেনসেশন)। এটি দেখায় কেন কম্পেনসেশন সবসময় সম্পন্ন হওয়ার বিপরীত ক্রমে চালানো হয় — শেষে যা করা হয়েছিল তা আগে বাতিল করা হয়।

  2. ডিজাইন করুন: একটি হোটেল বুকিং সিস্টেমের জন্য একটি SAGA কল্পনা করুন — ধাপগুলো কী কী হতে পারে এবং প্রতিটির কম্পেনসেশন কী হবে?

    উদাহরণ: (১) reserve_room (কম্পেনসেশন: release_room), (২) charge_payment (কম্পেনসেশন: refund_payment), (৩) send_confirmation_email (কম্পেনসেশন: send_cancellation_email — যেহেতু পাঠানো ইমেইল ফেরত নেওয়া যায় না, কম্পেনসেশন হলো একটি সংশোধনী বার্তা পাঠানো)। এটি দেখায় সব কম্পেনসেশন "নিখুঁত বিপরীত" নয় — কখনো কখনো সেরা কম্পেনসেশন হলো সত্যটি জানানো, প্রভাবটি মুছে ফেলা নয়।

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

আগের পাঠ
সার্কিট ব্রেকার ও রিট্রাই প্যাটার্ন