পাঠ ৩৮ · ৫৭-এর মধ্যে · মডিউল ৯
Home / Courses / Mobile App Development / রিট্রাই ও টাইমআউট

ফ্লেকি মোবাইল নেটওয়ার্ক হ্যান্ডলিং — রিট্রাই ও টাইমআউট

Handling flaky mobile networks — retries and timeouts
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • এক্সপোনেনশিয়াল ব্যাকঅফ ফর্মুলা এবং কেন এটি লিনিয়ার রিট্রাইয়ের চেয়ে ভালো
  • একটি ডিটারমিনিস্টিক ফ্লেকি ফাংশনের বিপরীতে সত্যিকারের রিট্রাই লুপ — প্রতিটি অ্যাটেম্পটের গণনা করা ডিলে
  • সর্বোচ্চ রিট্রাই সীমা পার হলে কীভাবে হাল ছেড়ে দিতে হয় — দুটি ভিন্ন ফলাফল কেস (সফল বনাম ব্যর্থ)

১ · কেন এক্সপোনেনশিয়াল ব্যাকঅফ

একটি নেটওয়ার্ক কল ব্যর্থ হলে সহজ সমাধান মনে হতে পারে — "সাথে সাথে আবার চেষ্টা করো"। কিন্তু যদি ব্যর্থতার কারণ সাময়িক নেটওয়ার্ক কনজেশন হয়, তাহলে তাৎক্ষণিক রিট্রাই একই সমস্যায় আবার পড়বে, এবং হাজারো ডিভাইস একসাথে এভাবে রিট্রাই করলে সার্ভারের ওপর চাপ আরও বাড়বে। এক্সপোনেনশিয়াল ব্যাকঅফ প্রতিটি ব্যর্থ অ্যাটেম্পটের পর অপেক্ষার সময় দ্বিগুণ করে বাড়ায়, যাতে সাময়িক সমস্যা সেরে ওঠার সুযোগ পায়:

$$delay = \min(base \times 2^{attempt},\ delay_{max})$$

একটি সর্বোচ্চ সীমা (delay_max) না রাখলে অপেক্ষার সময় অসীম বড় হয়ে যেতে পারে — তাই সবসময় একটি বাস্তবসম্মত ক্যাপ থাকে।

২ · একটি ডিটারমিনিস্টিক ফ্লেকি ফাংশনের বিপরীতে রিট্রাই

বাস্তব নেটওয়ার্ক ব্যর্থতা এলোমেলো মনে হলেও, এই সিমুলেশনে আমরা একটি ডিটারমিনিস্টিক ফ্লেকি ফাংশন ব্যবহার করব — এটি নির্দিষ্ট সংখ্যক বার ব্যর্থ হয়ে তারপর সফল হয়, যাতে আউটপুট প্রতিবার একই থাকে এবং আমরা হাতে-কলমে যাচাই করতে পারি।

Python
# এক্সপোনেনশিয়াল ব্যাকঅফ + ডিটারমিনিস্টিক ফ্লেকি নেটওয়ার্ক সিমুলেশন

def flaky_request(call_count, fail_until):
    """ডিটারমিনিস্টিক ফ্লেকি কল -- এলোমেলো নয়:
    call_count <= fail_until হলে ব্যর্থ হয়, তার পরের কলে সফল হয়"""
    if call_count <= fail_until:
        raise ConnectionError(f"নেটওয়ার্ক টাইমআউট (call #{call_count})")
    return {"status": "ok", "data": "সার্ভার রেসপন্স পাওয়া গেছে"}


def compute_backoff_delay(attempt, base=1.0, max_delay=10.0):
    # delay = min(base * (2 ** attempt), max_delay)
    return min(base * (2 ** attempt), max_delay)


def retry_with_backoff(fail_until, max_attempts, base=1.0, max_delay=10.0):
    total_wait = 0.0
    for attempt in range(max_attempts):
        call_count = attempt + 1
        try:
            result = flaky_request(call_count, fail_until=fail_until)
            print(f"অ্যাটেম্পট {call_count:2d} | সফল -- {result['data']}")
            print(f"\n  মোট অ্যাটেম্পট: {call_count}, মোট অপেক্ষার সময়: {total_wait:.1f}s -- (সফল)")
            return result
        except ConnectionError as e:
            if call_count >= max_attempts:
                print(f"অ্যাটেম্পট {call_count:2d} | ব্যর্থ -- {e} -- সর্বোচ্চ রিট্রাই সীমা ({max_attempts}) শেষ, হাল ছেড়ে দেওয়া হলো")
                print(f"\n  মোট অ্যাটেম্পট: {call_count}, মোট অপেক্ষার সময়: {total_wait:.1f}s -- (ব্যর্থ, গিভ-আপ)")
                return None
            delay = compute_backoff_delay(attempt, base=base, max_delay=max_delay)
            total_wait += delay
            print(f"অ্যাটেম্পট {call_count:2d} | ব্যর্থ -- {e} -- {delay:.1f}s পর আবার চেষ্টা করা হবে")


print("== কেস ১: ৩ বার ব্যর্থ হয়ে ৪র্থ অ্যাটেম্পটে সফল (max_attempts=5) ==")
retry_with_backoff(fail_until=3, max_attempts=5)

print("\n== কেস ২: ৬ বার পরপর ব্যর্থ, কিন্তু max_attempts=4 হওয়ায় হাল ছেড়ে দেওয়া হয় ==")
retry_with_backoff(fail_until=6, max_attempts=4)

    
কেস ১ হাতে-কলমে যাচাই: অ্যাটেম্পট ১-এ ডিলে min(1 × 2^0, 10) = 1.0s, অ্যাটেম্পট ২-এ min(1 × 2^1, 10) = 2.0s, অ্যাটেম্পট ৩-এ min(1 × 2^2, 10) = 4.0s — তিনবার ব্যর্থ হওয়ার পর মোট অপেক্ষা 1.0 + 2.0 + 4.0 = 7.0s, এবং ৪র্থ অ্যাটেম্পটে call_count(4) > fail_until(3) হওয়ায় সফল হয় — মোট ৪টি অ্যাটেম্পট। কেস ২-তে প্রতিটি অ্যাটেম্পট ব্যর্থ হয় (fail_until=6), এবং max_attempts=4 হওয়ায় ৪র্থ অ্যাটেম্পটেই call_count(4) >= max_attempts(4) শর্ত সত্য হয়ে রিট্রাই লুপ থেমে যায় — সীমাহীন রিট্রাইয়ের বদলে একটি সুনির্দিষ্ট "গিভ-আপ" ফলাফল আসে।
মূল কথা · Key takeaway

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

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

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

প্র ০১ যদি সব ডিভাইস একই base ও একই মুহূর্তে ব্যর্থ হয়ে একই ডিলে দিয়ে রিট্রাই করে, তাহলে কী সমস্যা হতে পারে?

হাজারো ডিভাইস একই সেকেন্ডে একসাথে রিট্রাই করলে সার্ভারে একটি "থান্ডারিং হার্ড" (thundering herd) তৈরি হতে পারে — সবাই ঠিক একই মুহূর্তে আঘাত করে সার্ভারকে আবার ওভারলোড করে ফেলে। বাস্তব সিস্টেমে এড়াতে ডিলেতে সামান্য এলোমেলো "জিটার" (jitter) যোগ করা হয়, যাতে রিট্রাইগুলো সময়ের সাথে ছড়িয়ে যায়।

প্র ০২ উপরের কোডে max_delay=10.0 ক্যাপটি না থাকলে ৫ম বা ৬ষ্ঠ অ্যাটেম্পটে ডিলে কত হতো?

ক্যাপ ছাড়া ফর্মুলা base * 2^attempt হতো — অ্যাটেম্পট ৫-এ (attempt index 4) 1 * 2^4 = 16.0s, অ্যাটেম্পট ৬-এ (attempt index 5) 1 * 2^5 = 32.0s। এভাবে দ্রুত বেড়ে ব্যবহারকারীর ধৈর্যের বাইরে চলে যেত — তাই max_delay ক্যাপ থাকা জরুরি।

প্র ০৩ এই পাঠের ফ্লেকি ফাংশন ডিটারমিনিস্টিক কেন রাখা হলো, বাস্তব নেটওয়ার্ক তো এলোমেলোভাবে ব্যর্থ হয়?

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

অনুশীলন

  1. চিন্তা করুন: এমন একটি মোবাইল অপারেশনের কথা ভাবুন যেখানে রিট্রাই করাই উচিত না (যেমন "পেমেন্ট সম্পন্ন করুন" বাটনে দুবার ক্লিক করলে দুবার টাকা কেটে যাওয়ার ঝুঁকি) — কেন এই ধরনের অপারেশনে সাধারণ রিট্রাই লজিক বিপজ্জনক?

    যদি প্রথম রিকোয়েস্টটি আসলে সার্ভারে পৌঁছে সফল হয়ে থাকে কিন্তু শুধু রেসপন্সটি ক্লায়েন্টের কাছে ফিরে আসার আগেই সংযোগ বিচ্ছিন্ন হয়, তাহলে ক্লায়েন্ট ভুলভাবে "ব্যর্থ" ধরে নিয়ে রিট্রাই করলে অপারেশনটি দুবার ঘটে যেতে পারে। এই ধরনের ক্ষেত্রে "আইডেমপোটেন্সি কী" (idempotency key) ব্যবহার করা হয়, যাতে সার্ভার একই কী দিয়ে দ্বিতীয়বার আসা রিকোয়েস্ট চিনে ফেলে এবং দ্বিতীয়বার কার্যকর না করে।

  2. পরীক্ষা করুন: উপরের কোড সেলে কেস ১-এর কলটি retry_with_backoff(fail_until=3, max_attempts=3) দিয়ে পরিবর্তন করুন (আগে ছিল max_attempts=5), তারপর কোডটি আবার চালিয়ে দেখুন এখন এটি সফল হয় নাকি হাল ছেড়ে দেয়, এবং কেন।

    max_attempts=3 করলে ৩য় অ্যাটেম্পটেই (call_count=3) ফাংশন এখনও ব্যর্থ হবে (কারণ fail_until=3, অর্থাৎ ৪র্থ অ্যাটেম্পট পর্যন্ত সফল হয় না), এবং call_count(3) >= max_attempts(3) শর্ত সত্য হওয়ায় এটি "সর্বোচ্চ রিট্রাই সীমা শেষ" বার্তা দিয়ে হাল ছেড়ে দেবে — কেস ১ আগে যা সফল হয়েছিল তা এখন ব্যর্থতায় পরিণত হবে, কারণ সফল হওয়ার জন্য প্রয়োজনীয় ৪র্থ অ্যাটেম্পট পর্যন্ত পৌঁছানোর সুযোগই থাকবে না।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেটিভ বনাম ক্রস-প্ল্যাটফর্ম, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • L36 · অফলাইন-ফার্স্ট ডেটা স্ট্র্যাটেজি সহোদর পাঠ পেন্ডিং সিঙ্ক কিউয়ের ব্যর্থ এন্ট্রি রিট্রাই করতে এই পাঠের ব্যাকঅফ প্যাটার্ন সরাসরি ব্যবহৃত হয়।
  • সব 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 ও Mobile App Development — সব এক জায়গায়।
আগের পাঠ
মোবাইলে নেটওয়ার্ক রিকোয়েস্ট করা — ওয়েব থেকে পার্থক্য