পাঠ ৩৮ · ৫১-এর মধ্যে · মডিউল ৯
Home / Courses / System Design / আইডেম্পোটেন্সি

আইডেম্পোটেন্সি ও এক্সাক্টলি-ওয়ান্স ডেলিভারি

Idempotency & exactly-once delivery
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • আইডেম্পোটেন্সির সংজ্ঞা এবং আইডেম্পোটেন্ট বনাম নন-আইডেম্পোটেন্ট অপারেশনের উদাহরণ
  • কেন নেটওয়ার্ক অনিশ্চয়তা রিট্রাইকে বিপজ্জনক করে তোলে
  • আইডেম্পোটেন্সি-কী প্যাটার্ন — কীভাবে পেমেন্ট সিস্টেম নিরাপদে রিট্রাই সামলায়
  • Python দিয়ে idempotency-key-aware payment ফাংশন — প্রমাণ যে একই কী দুবার পাঠালেও আসল চার্জ একবারই ঘটে

১ · আইডেম্পোটেন্সি কী

একটি অপারেশন আইডেম্পোটেন্টIdempotentএকবার এক্সিকিউট করা বা N বার — চূড়ান্ত ফলাফল একই থাকে। যদি সেটি একবার চালানো আর একাধিকবার চালানো — উভয়ের ফলাফল সম্পূর্ণ একই হয়। উদাহরণ —

আইডেম্পোটেন্ট
"ব্যালেন্স = ৫০০ সেট করো" — যতবার চালাই, ব্যালেন্স ৫০০-ই থাকবে। HTTP-তে GET, PUT, DELETE সাধারণত আইডেম্পোটেন্ট (L06)।
নন-আইডেম্পোটেন্ট
"ব্যালেন্সে ৫০০ যোগ করো" — দুবার চালালে ১,০০০ যোগ হয়ে যাবে। HTTP POST সাধারণত নন-আইডেম্পোটেন্ট।

২ · কেন এটি এত গুরুত্বপূর্ণ — নেটওয়ার্ক অনিশ্চয়তা

ধরুন একটি ক্লায়েন্ট "৫০০ টাকা চার্জ করো" রিকোয়েস্ট পাঠালো। সার্ভার সফলভাবে চার্জ করলো, কিন্তু রেসপন্স ফেরত আসার পথে নেটওয়ার্ক টাইমআউট হলো। ক্লায়েন্ট এখন জানে না — রিকোয়েস্টটি আদৌ পৌঁছায়নি, নাকি পৌঁছেছিল এবং সফল হয়েছিল কিন্তু শুধু রেসপন্সটাই হারিয়ে গেছে। নিরাপদ থাকতে ক্লায়েন্ট রিট্রাই (L27) করে — কিন্তু অপারেশনটি নন-আইডেম্পোটেন্ট হলে এই রিট্রাই গ্রাহককে দ্বিতীয়বার চার্জ করে দিতে পারে।

মূল সমস্যা

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

৩ · আইডেম্পোটেন্সি-কী প্যাটার্ন

সমাধান — ক্লায়েন্ট প্রতিটি লজিক্যাল অপারেশনের (যেমন "এই একটি পেমেন্ট") জন্য একটি ইউনিক আইডেম্পোটেন্সি-কীIdempotency Keyক্লায়েন্টের জেনারেট করা একটি ইউনিক আইডি — একই লজিক্যাল অপারেশনের সব রিট্রাইতে একই কী পাঠানো হয়, যাতে সার্ভার ডুপ্লিকেট চিনতে পারে। জেনারেট করে সেটি প্রতিটি রিট্রাইতে একই রাখে (একটি নতুন র‍্যান্ডম UUID, একবার জেনারেট করে সংরক্ষণ করা)। সার্ভার একবার একটি কী প্রসেস করলে সেটি ও তার ফলাফল মনে রাখে — একই কী নিয়ে আবার রিকোয়েস্ট এলে সার্ভার আসল অপারেশন পুনরায় না চালিয়ে সরাসরি সংরক্ষিত ফলাফল ফেরত দেয়। Stripe-এর মতো পেমেন্ট API ঠিক এই প্যাটার্নেই নিরাপদ রিট্রাই সাপোর্ট করে।

প্রথম রিকোয়েস্ট key = "abc123" সার্ভার: নতুন কী real_charge() রান ফলাফল cache-এ সেভ processed_keys[key] রিট্রাই (timeout) একই key = "abc123" সার্ভার: পুরনো কী real_charge() স্কিপ cached ফলাফল ফেরত no duplicate charge
একই আইডেম্পোটেন্সি-কী নিয়ে আসা রিট্রাই আসল অপারেশন পুনরায় চালায় না — শুধু আগের ফলাফল ফেরত দেয়।

৪ · এক্সাক্টলি-ওয়ান্স ডেলিভারি — বাস্তবে যা আসলে ঘটে

"এক্সাক্টলি-ওয়ান্স ডেলিভারি" শব্দটি শুনতে মনে হয় নেটওয়ার্ক লেভেলে একটি বার্তা ঠিক একবারই পৌঁছানোর নিশ্চয়তা। বাস্তবে বিশুদ্ধ নেটওয়ার্ক-লেভেল এক্সাক্টলি-ওয়ান্স প্রায় অর্জনযোগ্য নয় (বার্তা হারাতে পারে, ডুপ্লিকেট হতে পারে)। তাই ব্যবহারিক সিস্টেমগুলো ভিন্নভাবে এটি অর্জন করে —

এক্সাক্টলি-ওয়ান্স = at-least-once + আইডেম্পোটেন্ট প্রসেসিং

সিস্টেম নিশ্চিত করে বার্তা অন্তত একবার পৌঁছাবে (সাড়া না পেলে রিট্রাই করে — at-least-once ডেলিভারি), এবং প্রসেসিং লজিক আইডেম্পোটেন্ট বানিয়ে রাখে (ডুপ্লিকেট এলেও ক্ষতি নেই)। এই দুটো মিলে ক্লায়েন্টের দৃষ্টিকোণ থেকে ফলাফল দেখতে ঠিক "এক্সাক্টলি-ওয়ান্স"-এর মতোই মনে হয়, যদিও নিচে আসলে ডুপ্লিকেট বার্তা এসেছে এবং নিরাপদে উপেক্ষা করা হয়েছে।

নিচের কোড সেলে একটি idempotency-key-aware charge() ফাংশন — একই কী দিয়ে দুবার কল করা হবে (একটি নেটওয়ার্ক রিট্রাই সিমুলেট করে), এবং প্রমাণ করা হবে আসল "real charge" লজিক শুধু একবারই এক্সিকিউট হয়েছে।

Python
processed_keys = {}      # key -> cached result
real_charge_count = 0    # আসল চার্জ লজিক কতবার সত্যিকারে চললো তার কাউন্টার

def real_charge(amount):
    global real_charge_count
    real_charge_count += 1
    return f"৳{amount} সফলভাবে চার্জ করা হলো (real charge #{real_charge_count})"

def charge(idempotency_key, amount):
    if idempotency_key in processed_keys:
        print(f"[cache hit] key={idempotency_key} — আগেই প্রসেস হয়েছে, cached ফলাফল ফেরত")
        return processed_keys[idempotency_key]
    result = real_charge(amount)
    processed_keys[idempotency_key] = result
    return result

key = "idem-key-abc123"

print("প্রথম কল:            ", charge(key, 500))
print("দ্বিতীয় কল (রিট্রাই):", charge(key, 500))
print("তৃতীয় কল (আবার রিট্রাই):", charge(key, 500))

print(f"\nমোট real_charge() এক্সিকিউট হয়েছে: {real_charge_count} বার")
print(f"অথচ charge() কল করা হয়েছে: ৩ বার — গ্রাহকের ৳500 শুধু একবারই কাটা হয়েছে।")

    
লক্ষ্য করুন — তিনটি কলই সঠিক ফলাফল রিটার্ন করে (ক্লায়েন্টের দৃষ্টিকোণ থেকে সবকিছু ঠিকঠাক দেখায়), কিন্তু real_charge_count শেষে ১-ই থাকে। এটাই আইডেম্পোটেন্সি-কী প্যাটার্নের মূল শক্তি — ক্লায়েন্ট যতবার ইচ্ছা নিরাপদে রিট্রাই করতে পারে, প্রকৃত সাইড-ইফেক্ট (টাকা কাটা) একবারের বেশি ঘটবে না।
মূল কথা · Key takeaway

আইডেম্পোটেন্সি রিট্রাইকে "নিরাপদ" করে তোলে — এবং ডিস্ট্রিবিউটেড সিস্টেমে রিট্রাই এড়ানো যায় না, কারণ নেটওয়ার্ক অনির্ভরযোগ্য। প্রতিবার একটি নতুন এন্ডপয়েন্ট ডিজাইন করার সময় প্রশ্ন করুন — "এই অপারেশনটি যদি দুবার চলে, কী ঘটবে?" — উত্তর যদি "খারাপ কিছু" হয়, তাহলে একটি আইডেম্পোটেন্সি-কী মেকানিজম যোগ করা দরকার।

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

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

প্র ০১ HTTP GET, PUT, DELETE সাধারণত আইডেম্পোটেন্ট ধরা হয়, কিন্তু POST নয় কেন?

GET শুধু ডেটা পড়ে, কিছু বদলায় না — যতবার চালান, ফলাফল একই। PUT "এই রিসোর্সকে এই অবস্থায় সেট করো" (L06) — দুবার চালালেও একই চূড়ান্ত অবস্থা থাকে। DELETE একবার ডিলিট করলে বা দশবার চেষ্টা করলে — রিসোর্স মুছে যাওয়ার চূড়ান্ত অবস্থা একই (দ্বিতীয়বার "already deleted" ফেরত দিলেও)। কিন্তু POST সাধারণত "নতুন কিছু তৈরি করো" (যেমন একটি নতুন অর্ডার) বোঝায় — দুবার POST করলে দুটো ভিন্ন রিসোর্স/সাইড-ইফেক্ট তৈরি হয়ে যায়, যা ঠিক এই কারণেই POST-ভিত্তিক এন্ডপয়েন্টে (যেমন পেমেন্ট) আইডেম্পোটেন্সি-কী সবচেয়ে বেশি প্রয়োজন হয়।

প্র ০২ আইডেম্পোটেন্সি-কী যদি ক্লায়েন্ট প্রতিবার রিট্রাইতে নতুন করে জেনারেট করে ফেলে (ভুল করে), তাহলে কী সমস্যা হবে?

পুরো প্যাটার্নটাই ভেঙে পড়বে — সার্ভার প্রতিটি রিট্রাইকে একটি সম্পূর্ণ নতুন, ভিন্ন লজিক্যাল অপারেশন হিসেবে দেখবে (কারণ কী-ই তো একমাত্র জিনিস যা ডুপ্লিকেট শনাক্ত করে), এবং real_charge() প্রতিবার নতুন করে চলবে — ঠিক সেই ডাবল-চার্জ সমস্যাটাই ঘটবে যা আইডেম্পোটেন্সি-কী প্রতিরোধ করার কথা ছিল। তাই ক্লায়েন্ট-সাইড দায়িত্ব হলো একটি লজিক্যাল অপারেশনের সব রিট্রাইতে ঠিক একই কী পুনঃব্যবহার করা (একবার জেনারেট করে স্থানীয়ভাবে সংরক্ষণ করে), নতুন করে তৈরি না করা।

প্র ০৩ "এক্সাক্টলি-ওয়ান্স ডেলিভারি" নামটা কি একরকম ভুল ধারণা তৈরি করে? ব্যাখ্যা করুন।

কিছুটা হ্যাঁ — নামটা শুনে মনে হয় নেটওয়ার্ক লেভেলে একটি বার্তা ঠিক একবারই পাঠানো ও গ্রহণ করা নিশ্চিত করা হচ্ছে, যা বাস্তবে অত্যন্ত কঠিন (এবং বেশিরভাগ সিস্টেম এটি সত্যিকারভাবে করেই না)। প্রকৃতপক্ষে যা ঘটে তা হলো at-least-once ডেলিভারি (ডুপ্লিকেট হতে পারে) + আইডেম্পোটেন্ট প্রসেসিং (ডুপ্লিকেট নিরাপদে উপেক্ষিত হয়) — চূড়ান্ত পর্যবেক্ষিত প্রভাব এক্সাক্টলি-ওয়ান্সের মতো দেখায়, কিন্তু নিচের মেকানিজম সম্পূর্ণ ভিন্ন। এই পার্থক্য বোঝা গুরুত্বপূর্ণ কারণ এটি ইঞ্জিনিয়ারদের সঠিক জায়গায় (প্রসেসিং লজিকে) সমাধান খুঁজতে সাহায্য করে।

অনুশীলন

  1. চিন্তা করুন: আপনার পরিচিত একটি অ্যাপে (যেমন একটি ফুড-ডেলিভারি অ্যাপে "অর্ডার প্লেস করো" বাটন) কোথায় আইডেম্পোটেন্সি-কী দরকার হতে পারে এবং কেন?

    "অর্ডার প্লেস করো" বাটন একটি ভালো উদাহরণ — ইউজার যদি ধীর ইন্টারনেটে বাটনে দুবার ট্যাপ করে ফেলে, বা অ্যাপ একটি টাইমআউটের পর স্বয়ংক্রিয়ভাবে রিট্রাই করে, তাহলে আইডেম্পোটেন্সি-কী ছাড়া দুটো আলাদা অর্ডার ও দুবার পেমেন্ট কেটে যেতে পারে। অ্যাপটি অর্ডার তৈরির মুহূর্তেই একটি ইউনিক কী জেনারেট করে সেটি বাটন-প্রেস জুড়ে পুনঃব্যবহার করা উচিত, যতক্ষণ না একটি চূড়ান্ত সফল/ব্যর্থ রেসপন্স পাওয়া যায়।

  2. কোড পরিবর্তন করুন: উপরের কোড সেলে একটি ভিন্ন আইডেম্পোটেন্সি-কী (যেমন "idem-key-xyz789") দিয়ে charge(new_key, 200) একবার কল করুন — real_charge_count কী হবে বলে আশা করেন, এবং কেন?

    real_charge_count ২-এ পৌঁছাবে। কারণ নতুন কী processed_keys-এ নেই — সার্ভার এটিকে একটি সম্পূর্ণ নতুন, ভিন্ন লজিক্যাল অপারেশন হিসেবে দেখবে (সঠিকভাবেই, কারণ এটি আসলেই একটি নতুন ৳200 চার্জ, আগের ৳500 চার্জের রিট্রাই নয়) এবং real_charge() আবার চালাবে। এটি দেখায় কী-ই হলো একমাত্র সিগন্যাল যা সিস্টেমকে বলে দুটো রিকোয়েস্ট একই অপারেশন নাকি ভিন্ন দুটি অপারেশন।

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

পূর্ববর্তী পাঠ
L37 · ফেইলওভার ও ডিজাস্টার রিকভারি