সেশন ২১

Circuit Breaker, Retry, Rate Limiting - একজনের সমস্যা যেন সবার সমস্যা না হয়

এই সেশনের লক্ষ্য: একটা ধীর dependency কীভাবে পুরো system কে থামিয়ে দেয়, আর সেটা ঠেকানোর কৌশলগুলো বোঝা। প্রতিটা call এ timeout, নিরাপদ retry (backoff, jitter, retry budget), circuit breaker এর তিনটা state, fallback আর bulkhead। সাথে rate limiting এর চারটা algorithm, 429 আর Retry-After, আর load shedding।

০০ যে প্রশ্নগুলোর উত্তর খুঁজবো

সেশন ১২ তে আমরা একটা ভয়ের ছবি দেখেছিলাম। একটা service ধীর হলে তাকে যে service গুলো call করে, সেগুলোও একে একে আটকে যায়। এই ঘটনার নাম cascading failure। সেখানে শুধু সমস্যাটা চিনেছিলাম। এই সেশনে আমরা তার প্রতিকার শিখবো।

প্রতিকার দুই রকম। প্রথম দলটা নিজের system কে অন্যের সমস্যা থেকে বাঁচায়: timeout, retry, circuit breaker, bulkhead। দ্বিতীয় দলটা system কে অতিরিক্ত request থেকে বাঁচায়: rate limiting আর load shedding। শেষে আপনি বুঝবেন, বড় sale এর রাতেও একটা ছোট dependency কেন পুরো site কে থামাতে পারে না।

  • ১ SMS provider ধীর হলে পুরো checkout কেন বন্ধ হয়ে যায়? (cascading failure)
  • ২ প্রতিটা call এ timeout কেন লাগে, আর কত রাখবো? (request-level timeout)
  • ৩ Fail করলে আবার চেষ্টা করাই তো ভালো। তাহলে retry storm কেন হয়?
  • ৪ Retry কে নিরাপদ করবো কীভাবে? (exponential backoff, jitter, retry budget)
  • ৫ Circuit breaker কী, আর closed, open, half-open state কীভাবে কাজ করে?
  • ৬ Circuit open থাকলে user কী পাবে, আর একটা ধীর dependency যেন সব thread দখল না করে? (fallback, bulkhead)
  • ৭ Rate limiting কেন লাগে, আর fixed window ও sliding window কীভাবে গোনে?
  • ৮ Token bucket আর leaky bucket এর পার্থক্য কী?
  • ৯ Limit পার হলে client কে কী বলবো, আর পুরো server চাপে পড়লে কী করবো? (429, Retry-After, load shedding)

০১ SMS provider ধীর হলে পুরো checkout কেন বন্ধ হয়ে যায়?

রাত এগারোটা আটান্ন। শাওন অফিসের চেয়ারে বসে dashboard এর দিকে তাকিয়ে আছে। দুই মিনিট পর 11.11 এর sale শুরু হবে। Marketing টিম আগেই চল্লিশ লাখ মানুষকে notification পাঠিয়ে দিয়েছে। বারোটা বাজতেই checkout এর graph খাড়া উপরে উঠে গেলো: সেকেন্ডে ১,০০০টা order। শাওন একটু হাসলো, সব ঠিকঠাক চলছে। রাত বারোটা তিনে Slack এ লাল message আসা শুরু হলো: “Checkout এ শুধু spinner ঘুরছে, কোনো order হচ্ছে না!”

শাওন একে একে সব দেখলো। Database ঠিক আছে। Payment gateway ঠিক আছে। Server এর CPU মাত্র ২০%। তাহলে সমস্যা কোথায়? শেষে সে খুঁজে পেলো। Order confirm হলে checkout service একটা SMS পাঠায়: “আপনার order #4711 confirm হয়েছে।” যে কোম্পানি এই SMS পাঠায় (SMS provider), সেই রাতে তাদের নিজেদের system চাপে পড়েছে। আগে প্রতিটা SMS এ ২০০ মিলিসেকেন্ড লাগতো। এখন লাগছে ৩০ সেকেন্ড।

মাত্র একটা SMS এর জন্য পুরো checkout কীভাবে থেমে গেলো? উত্তরটা লুকিয়ে আছে thread এ। সেশন ০৮ এ দেখেছেন, thread হলো server এর ভেতরের একজন কর্মীর মতো, যে একবারে একটা request সামলায়। শাওনদের checkout service এর ৪টা instance আছে, প্রতিটায় ২০০টা thread। মানে মোট ৮০০ জন কর্মী আছে।

স্বাভাবিক (11:58 PM) 1,000 checkout/s Checkout service 300 / 800 thread busy SMS provider SMS: 200ms Checkout ~0.3s এ শেষ SMS slow (12:03 AM) 1,000 checkout/s Checkout service 800 / 800 busy, বাকিরা লাইনে SMS provider SMS: 30s Checkout আটকে, order হয় না
প্রতিটা ছোট ঘর মানে 20 টা thread। স্বাভাবিক সময়ে 800 thread এর মধ্যে 300 টা ব্যস্ত থাকে। SMS provider 30 সেকেন্ড ঝুলিয়ে রাখা শুরু করতেই এক সেকেন্ডের মধ্যে সব thread SMS এর অপেক্ষায় আটকে যায়, আর নতুন checkout লাইনে দাঁড়িয়ে থাকে।

হিসাবটা খুব সহজ। একসাথে কয়টা thread ব্যস্ত থাকে, তা নির্ভর করে দুটো জিনিসের উপর: প্রতি সেকেন্ডে কয়টা request আসে, আর প্রতিটা request কতক্ষণ thread টা ধরে রাখে। দুটো গুণ করলেই উত্তর মেলে। এই সূত্রকে বলে Little’s Law।

একসাথে ব্যস্ত thread = প্রতি সেকেন্ডে request × প্রতিটা request এর সময়

স্বাভাবিক:        1,000/s × 0.3s   =    300 thread   (আছে 800, আরাম)
SMS 30s ঝুলছে:    1,000/s × 30.3s  = 30,300 thread   (আছে 800, সব আটকে)
timeout 1s:       1,000/s × 1.3s   =  1,300 thread   (আছে 800, তবুও কম পড়ে)
breaker open:     1,000/s × 0.3s   =    300 thread   (SMS কে আর ডাকাই হয় না)

স্বাভাবিক সময়ে ৮০০ এর মধ্যে ৩০০টা thread ব্যস্ত থাকে, তাই কোনো চিন্তা নেই। SMS ৩০ সেকেন্ড ঝুলিয়ে রাখা শুরু করতেই দরকার হয় ত্রিশ হাজার thread। আছে মাত্র ৮০০। এক সেকেন্ডের মধ্যে সব thread SMS এর উত্তরের অপেক্ষায় বসে যায়। নতুন order আর কোনো thread পায় না। যাদের SMS এর সাথে কোনো সম্পর্কই নেই, যেমন cart দেখা বা address বদলানো, তারাও আটকে যায়।

এটাই cascading failure। SMS provider এর সমস্যা ছড়িয়ে পড়লো checkout এ। Checkout এর সমস্যা ছড়িয়ে পড়লো mobile app এ। আর এর শুরুটা হলো এমন একটা কাজ থেকে, যেটা order এর জন্য জরুরিও নয়।

আসল সমস্যা

একটা dependency fail করলে সাধারণত বড় ক্ষতি হয় না। বিপদ হয় dependency টা ধীর হলে। Fail করা call সাথে সাথে ফিরে আসে, কিন্তু ধীর call thread টাকে ধরে রাখে। আপনার system এর thread, connection আর memory সীমিত। কেউ সেগুলো ধরে রাখলে বাকিদের জন্য আর কিছু থাকে না।

০২ প্রতিটা call এ timeout কেন লাগে, আর কত রাখবো?

সেশন ০১ এ timeout এর সাথে পরিচয় হয়েছিলো: একটা নির্দিষ্ট সময়ের পর অপেক্ষা ছেড়ে দেওয়া। এখন সেটাকে প্রতিটা network call এ বসাতে হবে। একে বলে request-level timeout। শাওনদের code এ SMS call এ কোনো timeout ছিলো না। অনেক HTTP library তে default timeout অসীম, বা কয়েক মিনিট। তাই thread টা provider এর মর্জির উপর বসে ছিলো।

timeout নেই 30s পর্যন্ত বসে থাকে timeout 1s 1s পরেই ছেড়ে দেয় timeout 1s + 3 retry 4 বার চেষ্টা, 4s thread আটকে, user অপেক্ষায় fail fast: thread মুক্ত, অন্য কাজ ধরতে পারে প্রতিটা চেষ্টা SMS provider এ নতুন load 0s 5s 10s 15s 20s 25s 30s সময়
Timeout না থাকলে একটা thread পুরো 30 সেকেন্ড আটকে থাকে। 1 সেকেন্ডের timeout দিলে thread টা তাড়াতাড়ি ছাড়া পায়। কিন্তু সাথে 3 বার retry জুড়ে দিলে একই user এর জন্য SMS provider এ 4 টা call যায়।

Timeout কত রাখবেন? অনুমান করে নয়, মেপে ঠিক করুন। স্বাভাবিক সময়ে SMS provider এর p99 latency দেখুন। মানে ১০০টা call এর মধ্যে সবচেয়ে ধীর ১টা কত সময় নেয় (সেশন ১৯)। ধরুন সেটা ৬০০ মিলিসেকেন্ড। তাহলে timeout তার একটু উপরে রাখুন, যেমন ১ সেকেন্ড। এতে স্বাভাবিক ধীর call গুলো কাটা পড়ে না, আবার অস্বাভাবিক অপেক্ষাও থামে।

আরেকটা জিনিস মনে রাখুন। User এর পুরো checkout এর জন্য একটা মোট সময়সীমা থাকে, ধরুন ৩ সেকেন্ড। ভেতরের সব call এর timeout মিলিয়ে সেই সীমার মধ্যে থাকতে হবে। Mobile app ৩ সেকেন্ড পরে হাল ছেড়ে দিলে ভেতরে ১০ সেকেন্ডের timeout রাখার কোনো মানে নেই। তখন server এমন একটা কাজ করতে থাকে, যার উত্তর শোনার জন্য কেউ আর অপেক্ষা করছে না।

উপরের হিসাবে আবার তাকান। ১ সেকেন্ডের timeout দিলেও দরকার হয় ১,৩০০টা thread। আছে ৮০০। মানে timeout ক্ষতি অনেক কমায়, কিন্তু একা পুরো সমস্যা মেটাতে পারে না। Provider যতক্ষণ ধীর, ততক্ষণ প্রতিটা order ১ সেকেন্ড করে নষ্ট করে। এর সমাধান আসবে সেকশন ০৫ এ।

নিয়ম

Network এর ওপারে যাওয়া প্রতিটা call এ timeout দিন: database, cache, অন্য service, third-party API, সব জায়গায়। Timeout না দিলে আপনার thread কতক্ষণ আটকে থাকবে, সেটা ঠিক করে অন্য কেউ।

০৩ Fail করলে আবার চেষ্টা করাই তো ভালো। তাহলে retry storm কেন হয়?

শাওনদের code এ timeout ছিলো না, কিন্তু retry ছিলো। SMS call fail করলে checkout আরো ৩ বার চেষ্টা করতো। Mobile app এও একই নিয়ম: checkout থেকে error এলে app নিজে থেকে আরো ৩ বার চেষ্টা করে। দুটো সিদ্ধান্তই আলাদা আলাদা টিম নিয়েছিলো, আর দুটোই আলাদাভাবে যুক্তিসংগত।

সাময়িক কোনো গোলমাল হলে retry সত্যিই কাজে দেয়। Network এ একটা packet হারিয়ে গেলো, বা একটা server restart হচ্ছিলো, এমন সমস্যা এক সেকেন্ড পরে নিজেই মিটে যায়। কিন্তু dependency টা চাপে পড়ে ধীর হয়ে গেলে retry তার উপর আরো চাপ বাড়ায়।

1k/s 4k/s 16k/s User Place order চাপে Mobile app fail হলে 3 retry Checkout fail হলে 3 retry SMS provider ক্ষমতা 2,000/s 1 + 3 = 4 গুণ 4 × 4 = 16 গুণ SMS PROVIDER এ LOAD স্বাভাবিক 1,000/s ক্ষমতা 2,000/s retry storm 16,000/s
Mobile app আর checkout দুজনেই 3 বার করে retry করে। তাই একটা tap থেকে SMS provider এ 16 টা call যায়। যে provider 2,000/s সামলাতে পারে, তার কাছে আসে 16,000/s। সে আর কখনো সামলে উঠতে পারে না।

একজন user একবার “Place order” চাপলো। App চেষ্টা করলো ৪ বার (প্রথমবার আর ৩টা retry)। প্রতিবার checkout SMS provider কে ৪ বার করে ডাকলো। এক tap থেকে provider এর কাছে গেলো ১৬টা call। সেকেন্ডে ১,০০০টা order এর বদলে provider পেলো সেকেন্ডে ১৬,০০০টা অনুরোধ। এর উপর অধৈর্য user রা নিজেরাও বারবার button চাপছে।

যে provider সেকেন্ডে ২,০০০টা SMS সামলাতে পারে, সে আর কোনোদিন সামলে ওঠার সুযোগ পায় না। Provider এর চাপ কমলে সে সেরে উঠতো। কিন্তু retry গুলো চাপ কমতেই দেয় না। এই অবস্থার নাম retry storm: সবার retry মিলে এত load তৈরি করে যে আসল সমস্যাটা আর মিটতেই পারে না।

আরেকটা বিপদও আছে। Order এর call টা retry হলে একই order দুইবার তৈরি হতে পারে। তাই যে call retry হবে, সেটাকে idempotent হতে হবে (সেশন ০৮)। Payment এর মতো কাজে এটা আরো জরুরি, বিস্তারিত আছে সেশন ১৮ তে।

মূল শিক্ষা

প্রতিটা retry আসলে বাড়তি load। একটা layer এ ৩ বার retry মানে চাপ ৪ গুণ। দুটো layer এ সেটা হয় ১৬ গুণ, তিনটা layer এ ৬৪ গুণ। তাই একটা call chain এ retry রাখুন শুধু একটা জায়গায়, সব layer এ নয়।

০৪ Retry কে নিরাপদ করবো কীভাবে?

Retry একেবারে বাদ দেওয়া যায় না, কারণ সাময়িক গোলমালে এটাই সবচেয়ে সহজ সমাধান। তাই retry রাখতে হবে, কিন্তু তিনটা নিয়ম মেনে।

প্রথম নিয়ম: exponential backoff আর jitter

সেশন ০৯ এ background job এর জন্য এই দুটো দেখেছেন। একই নিয়ম request এর বেলায়ও খাটে। Exponential backoff মানে প্রতিবার retry এর আগে আগের চেয়ে দ্বিগুণ অপেক্ষা: ১ সেকেন্ড, তারপর ২, তারপর ৪। Jitter মানে সেই অপেক্ষার সাথে একটু random সময় যোগ করা।

সাথে সাথে retry 6 client, প্রতিবার একসাথে Exponential backoff 1s, 2s, 4s: তবুও একসাথে Backoff + jitter ছড়িয়ে পড়ে, চাপ সমান 0s 1s 2s 3s 4s 5s 6s 7s 8s প্রথম fail এর পর
প্রতিটা বিন্দু একটা retry। শুধু backoff দিলে অপেক্ষা বাড়ে, কিন্তু 6 জন client একই মুহূর্তে আবার ঝাঁপিয়ে পড়ে। Jitter প্রত্যেকের অপেক্ষায় একটু random সময় যোগ করে, তাই retry গুলো ছড়িয়ে যায়।

শুধু backoff দিলে অপেক্ষা বাড়ে ঠিকই। কিন্তু যে হাজার জন client একই মুহূর্তে fail করেছিলো, তারা সবাই ঠিক ১ সেকেন্ড পরে একসাথে ফিরে আসে। তারপর আবার একসাথে, ২ সেকেন্ড পরে। Provider এর কাছে load আসে ঢেউয়ের মতো। Jitter সেই ঢেউ ভেঙে দেয়। AWS এর পরামর্শ হলো “full jitter”: ০ থেকে backoff এর পুরো সময়ের মধ্যে যেকোনো একটা random সময় অপেক্ষা করা।

দ্বিতীয় নিয়ম: সব error এ retry নয়

Timeout, 503 বা connection কেটে যাওয়ার মতো সাময়িক error এ retry করা যায়। কিন্তু 400 (ভুল request) বা 401 (login নেই) এ retry করে লাভ নেই। একই ভুল request দশবার পাঠালে দশবারই fail করবে। আর retry এর সংখ্যার একটা সীমা রাখুন, সাধারণত ২ থেকে ৩ বার।

তৃতীয় নিয়ম: retry budget

Backoff আর jitter থাকলেও একটা প্রশ্ন থেকে যায়। সব request ই যখন fail করছে, তখন প্রত্যেককে ৩ বার retry করতে দেওয়ার মানে কী? তার উত্তর হলো retry budget। পুরো service এর জন্য একটা সীমা থাকে: retry এর সংখ্যা মূল request এর একটা নির্দিষ্ট অংশের বেশি হবে না। Google এর SRE বইতে এই সীমা ১০%।

নিয়ম: প্রতি সেকেন্ডে retry ≤ মূল request এর 10%

স্বাভাবিক সময়: 1,000 request/s, fail হয় ~5 টা    → 5 টা retry, budget এর ভেতরে
SMS down:       1,000 request/s, fail হয় সবগুলো

  budget ছাড়া:  প্রত্যেকে 3 বার retry   → SMS এ 1,000 + 3,000 = 4,000/s
  budget সহ:     মাত্র 100 টা retry/s    → SMS এ 1,000 +   100 = 1,100/s
                 বাকি 900 টা সাথে সাথে fallback এ যায়

স্বাভাবিক সময়ে budget টের পাওয়াই যায় না, কারণ অল্প কয়েকটা request fail করে আর সবাই retry পায়। কিন্তু dependency পুরো down হলে budget নিজে থেকেই retry বন্ধ করে দেয়। Provider এর উপর load ৪,০০০ থেকে নেমে আসে ১,১০০ তে।

নিরাপদ retry এর তিন শর্ত

অপেক্ষা বাড়িয়ে আর ছড়িয়ে retry করুন (backoff আর jitter)। শুধু সাময়িক error এ retry করুন। আর পুরো service এর retry একটা budget এর মধ্যে রাখুন। এই তিনটা ছাড়া retry বিপদের সময় সাহায্য না করে উল্টো ক্ষতি করে।

০৫ Circuit breaker কী, আর closed, open, half-open state কীভাবে কাজ করে?

শাওনের মাথায় এবার একটা সহজ প্রশ্ন এলো। SMS provider যে এখন ধীর, সেটা তো আমরা জানিই। প্রতিটা order এ তাকে আবার ডেকে ১ সেকেন্ড নষ্ট করার দরকার কী? কিছুক্ষণ তাকে ডাকা বন্ধ রাখলেই তো হয়।

ঠিক এই কাজটাই করে circuit breaker। নামটা এসেছে বাসার বিদ্যুতের main switch থেকে। কোথাও short circuit হলে main switch নিজে থেকেই trip করে, মানে লাইন কেটে দেয়। তাতে পুরো বাসার wiring পুড়ে যাওয়া থেকে বাঁচে। Software এর circuit breaker ও তেমন। একটা dependency বারবার fail করলে সে কিছুক্ষণের জন্য সেই dependency কে call করা বন্ধ করে দেয়। Michael Nygard তাঁর Release It! বইতে এই pattern কে জনপ্রিয় করেন।

শেষ 20 call এ ≥ 50% fail 30s পর trial fail 3 টা trial সফল CLOSED সব call provider এ যায় fail গুনে রাখে OPEN কোনো call যায় না 1ms এ fallback HALF-OPEN অল্প কয়েকটা trial যায় বাকিরা fallback পায় স্বাভাবিক অবস্থা
স্বাভাবিক সময়ে breaker CLOSED: সব call যায়। শেষ 20 call এর অর্ধেক fail করলে breaker OPEN হয়, তখন provider কে আর ডাকাই হয় না। 30 সেকেন্ড পর HALF-OPEN হয়ে অল্প কয়েকটা trial পাঠায়। সফল হলে আবার CLOSED, fail হলে আবার OPEN।

Breaker এর তিনটা state। শাওনদের checkout এর সংখ্যাগুলো দিয়ে দেখি:

Half-open state টা খুব জরুরি। এটা না থাকলে breaker কখনো জানতেই পারতো না provider সেরে উঠেছে কিনা। আবার হঠাৎ সব traffic ছেড়ে দিলে সদ্য সেরে ওঠা provider আবার চাপে পড়তো।

Code এ breaker এর মূল যুক্তি দেখতে এরকম:

def send_sms(order):
    if breaker.state == OPEN:
        if now() - breaker.opened_at < 30s:
            return fallback(order)          # 1ms, provider কে ডাকাই হলো না
        breaker.state = HALF_OPEN           # 30s হয়ে গেছে, এবার একটু পরখ করি

    try:
        sms_provider.send(order, timeout=1s)
        breaker.record_success()            # HALF_OPEN এ 3 টা সফল হলে CLOSED
    except (Timeout, ServerError):
        breaker.record_failure()            # শেষ 20 call এ ≥ 50% fail হলে OPEN
        return fallback(order)

নিজে বানানোর দরকার নেই। Java তে Resilience4j, .NET এ Polly এর মতো library এটা দেয়। Service mesh (সেশন ১২) ব্যবহার করলে sidecar ও এই কাজ করতে পারে।

এবার সেশনের প্রথম হিসাবটা মনে করুন। Breaker open থাকলে SMS এর জন্য আর কোনো অপেক্ষা নেই। Checkout আবার ০.৩ সেকেন্ডে শেষ হয়, আর দরকার হয় মাত্র ৩০০টা thread।

Circuit breaker কী বাঁচায়

Circuit breaker দুই পক্ষকেই বাঁচায়। আপনার service আর ধীর call এর পেছনে thread নষ্ট করে না। আর অসুস্থ dependency টা বাড়তি চাপ থেকে মুক্তি পেয়ে সেরে ওঠার সময় পায়। Timeout বলে “এই call টায় আর অপেক্ষা করবো না।” Breaker বলে “এই dependency কে কিছুক্ষণ আর ডাকবোই না।“

০৬ Circuit open থাকলে user কী পাবে, আর একটা ধীর dependency যেন সব thread দখল না করে?

Fallback: পরিকল্পিত বিকল্প উত্তর

Breaker open হলে call টা আর হয় না। তখন user কে কী দেবেন? এর উত্তর আগে থেকে ঠিক করে রাখতে হয়। এই বিকল্প উত্তরের নাম fallback।

শাওনের ক্ষেত্রে উত্তরটা সহজ। SMS না গেলে order বাতিল করার কোনো কারণ নেই। SMS টা একটা queue তে রেখে দিলেই হয় (সেশন ০৮)। Provider সেরে উঠলে worker রা queue থেকে নিয়ে SMS গুলো পাঠিয়ে দেবে।

Order #4711 confirm এখন SMS পাঠাতে হবে Circuit breaker state কী? CLOSED OPEN fail হলে SMS provider call timeout 1s Fallback SMS টা queue তে রাখা হয় সফল: SMS গেলো ~200ms User দেখে: order confirm SMS একটু পরে আসবে
Breaker CLOSED থাকলে SMS provider কে ডাকা হয়। Breaker OPEN থাকলে, বা call fail করলে, SMS টা queue তে চলে যায়। দুই পথেই user এর order confirm হয়। শুধু SMS টা কয়েক মিনিট দেরিতে আসে।

সব dependency এর fallback এক রকম নয়। কয়েকটা প্রচলিত ধরন:

DependencyFallbackUser কী দেখে
SMS, emailQueue তে রেখে পরে পাঠানোOrder confirm, SMS একটু পরে আসে
“আপনার পছন্দ হতে পারে” sectionCache এ রাখা পুরোনো list, বা সবার জনপ্রিয় listএকটু কম personal suggestion
Product review এর সংখ্যাএকটা default মান, বা section টা লুকিয়ে রাখাReview section নেই
Paymentকোনো fallback নেই, পরিষ্কার error“Payment হয়নি, একটু পরে চেষ্টা করুন”

শেষ সারিটা খেয়াল করুন। সব কিছুর fallback হয় না। টাকা কাটা হয়েছে কিনা না জেনে “order confirm” দেখানো যায় না। এমন জায়গায় সবচেয়ে ভালো fallback হলো দ্রুত আর সৎ একটা error, যাতে user কে ৩০ সেকেন্ড spinner এর দিকে তাকিয়ে বসে থাকতে না হয়।

Bulkhead: ক্ষতিকে এক ঘরে আটকে রাখা

বড় জাহাজের তলাটা কয়েকটা আলাদা কামরায় ভাগ করা থাকে। মাঝখানের দেয়ালগুলো পানি আটকায়। একটা কামরায় ফুটো হলে শুধু সেই কামরায় পানি ঢোকে, পুরো জাহাজ ডোবে না। এই দেয়ালকে বলে bulkhead।

Software এ bulkhead মানে প্রতিটা dependency এর জন্য আলাদা thread pool বা connection pool রাখা। Pool মানে আগে থেকে তৈরি করে রাখা কয়েকটা thread বা connection এর একটা দল। শাওনদের checkout এ সব call একই ২০০টা thread এর pool ভাগ করে নিতো। Bulkhead দিলে ভাগটা এমন হয়: SMS এর জন্য ২০টা, payment এর জন্য ১০০টা, inventory এর জন্য ৮০টা।

একটা SHARED POOL Checkout · 200 thread সব thread SMS এর অপেক্ষায় payment, inventory জায়গা পায় না একটা slow dependency, সব বন্ধ BULKHEAD: আলাদা POOL Checkout · 200 thread SMS (20) Payment (100) Inventory (80) SMS এর 20 টা আটকে, বাকিরা চলছে ক্ষতি শুধু SMS এর ঘরে slow call এ আটকে কাজ করছে খালি
বাঁ দিকে সবাই একটা pool ভাগ করে নেয়, তাই slow SMS provider সব thread দখল করে ফেলে। ডান দিকে SMS এর জন্য মাত্র 20 টা thread আলাদা করা। সেগুলো আটকালেও payment আর inventory এর thread ঠিকই কাজ করে।

এখন SMS provider ধীর হলে শুধু তার ২০টা thread আটকে যায়। ২১ নম্বর SMS call আর thread পায় না, সাথে সাথে fallback এ চলে যায়। Payment আর inventory এর thread গুলোর গায়ে আঁচও লাগে না। Order চলতেই থাকে।

তিনটা একসাথে

Timeout প্রতিটা call কে একটা সময়সীমা দেয়। Bulkhead প্রতিটা dependency কে একটা জায়গার সীমা দেয়। Circuit breaker বারবার ব্যর্থ dependency কে কিছুক্ষণ বিশ্রাম দেয়। আর fallback ঠিক করে, তখন user কী দেখবে। চারটা মিলে একটা ধীর dependency এর ক্ষতি তার নিজের ঘরেই আটকে রাখে।

০৭ Rate limiting কেন লাগে, আর fixed window ও sliding window কীভাবে গোনে?

এতক্ষণ আমরা নিজেদের বাঁচিয়েছি একটা অসুস্থ dependency থেকে। এবার বিপদ আসছে উল্টো দিক থেকে। Sale শুরুর পাঁচ মিনিট পর শাওন দেখলো, একটা account থেকে flash deal এর API তে সেকেন্ডে ৫০০টা request আসছে। এটা কোনো মানুষ নয়, একটা bot। সে সস্তা phone গুলো সবার আগে কিনে নিতে চায়। একটা bot একাই হাজার জন সাধারণ user এর সমান load তৈরি করছে।

এর প্রতিকার rate limiting: একজন client একটা নির্দিষ্ট সময়ে সর্বোচ্চ কয়টা request পাঠাতে পারবে, তার একটা সীমা বেঁধে দেওয়া। সীমা পার হলে বাড়তি request গুলো ফিরিয়ে দেওয়া হয়। সেশন ১৩ তে দেখেছেন, এই কাজটা সাধারণত API gateway তে হয়, যাতে বাড়তি request ভেতরের service পর্যন্ত পৌঁছাতেই না পারে।

“Client” কে চেনা হবে কীভাবে, সেটাও ঠিক করতে হয়। Login করা user এর বেলায় user ID দিয়ে। Login ছাড়া request এ IP address দিয়ে। কিন্তু বাংলাদেশের মোবাইল নেটওয়ার্কে হাজার হাজার user প্রায়ই একই public IP এর পেছনে থাকে। তাই শুধু IP দিয়ে কড়া limit বসালে নিরীহ user রাও আটকে যেতে পারে।

এবার আসল প্রশ্ন: request গুলো গুনবো কীভাবে? চারটা প্রচলিত algorithm আছে। প্রতিটার জন্য আলাদা ছবি দেখবো। ধরে নিন limit হলো প্রতি user এর জন্য মিনিটে ১০০টা request।

Fixed window counter

সবচেয়ে সহজ পদ্ধতি। সময়কে এক মিনিটের টুকরোয় ভাগ করুন: ১২:০০, ১২:০১, ১২:০২। প্রতিটা টুকরোকে বলে একটা window। প্রতিটা user এর প্রতিটা window এর জন্য একটা counter রাখুন। Request এলে counter এক বাড়ান। Counter ১০০ পার হলে request ফিরিয়ে দিন। নতুন মিনিট শুরু হলে counter আবার শূন্য থেকে গোনা শুরু করে। এই পদ্ধতির নাম fixed window।

Window 1 · limit 100 Window 2 · limit 100 count = 0 থেকে শুরু 12:01:00 এ count আবার 0 শেষ 1s এ 100 প্রথম 1s এ 100 2 সেকেন্ডে 200 request, limit এর দ্বিগুণ 101 নম্বর: 429 12:00:00 12:01:00 12:02:00
প্রতি মিনিটের জন্য একটা counter, limit 100। 12:01:00 এ counter আবার শূন্য হয়ে যায়। তাই কেউ 12:00:59 এ 100 টা আর 12:01:00 এ আরো 100 টা পাঠালে দুটোই পাস করে: 2 সেকেন্ডে 200 টা request।

Memory লাগে খুব কম, user প্রতি একটা সংখ্যা। কিন্তু সীমানায় একটা ফাঁক থাকে। Bot টা ১২:০০:৫৯ এ ১০০টা আর ১২:০১:০০ এ আরো ১০০টা request পাঠালো। দুটো আলাদা window, তাই দুটোই পাস করলো। অথচ মাত্র ২ সেকেন্ডে গেলো ২০০টা request, limit এর দ্বিগুণ।

Sliding window

এই ফাঁক বন্ধ করে sliding window। এখানে “এই মিনিট” নয়, সবসময় “ঠিক শেষ ৬০ সেকেন্ড” গোনা হয়। Window টা সময়ের সাথে সাথে পিছলে এগোয়, তাই এই নাম।

সবচেয়ে নিখুঁত উপায় হলো প্রতিটা request এর সময় আলাদা করে লিখে রাখা, আর প্রতিবার শেষ ৬০ সেকেন্ডের request গুনে দেখা। একে বলে sliding window log। কিন্তু user প্রতি ১০০টা timestamp রাখতে অনেক memory লাগে। তাই বেশিরভাগ system একটা কাছাকাছি হিসাব ব্যবহার করে, যার নাম sliding window counter:

আগের window: 80 টা এই window: এখন পর্যন্ত 30 টা শেষ 60 সেকেন্ড (sliding) আগের window এর 45s 80 × 45/60 = 60 15s 30 এখন 12:01:15 12:00:00 12:00:15 12:01:00 12:02:00 হিসাব: 60 + 30 = 90, limit 100 এর নিচে, তাই allow
Sliding window সবসময় ঠিক শেষ 60 সেকেন্ড দেখে। এখন 12:01:15, তাই আগের window এর শেষ 45 সেকেন্ড আর এই window এর 15 সেকেন্ড মিলিয়ে গোনা হয়। আগের window এর 80 টার 45/60 অংশ, মানে 60 টা, ধরে নেওয়া হয়।

এখন ১২:০১:১৫। আগের window এ ৮০টা request এসেছিলো, এই window এ এখন পর্যন্ত ৩০টা। শেষ ৬০ সেকেন্ডের মধ্যে আগের window এর শেষ ৪৫ সেকেন্ড পড়েছে, মানে তার ৪৫/৬০ অংশ। ধরে নেওয়া হয়, আগের window এর request গুলো সমানভাবে ছড়ানো ছিলো। তাহলে আগের window থেকে ধরা হয় ৮০ এর ০.৭৫ অংশ, মানে ৬০টা। তার সাথে এই window এর ৩০টা যোগ করলে মোট হয় ৯০। এটা ১০০ এর নিচে, তাই request টা পাস করে।

এবার bot এর কৌশলটা আবার চালান। ১২:০১:০০ এ আগের window এর পুরোটা শেষ ৬০ সেকেন্ডে পড়ে। তাই আগের window এর পুরো ১০০টাই গোনায় ধরা হয়। প্রথম নতুন request টাই limit পার করে, তাই ফিরে যায়। মাত্র দুটো counter রেখে সীমানার ফাঁক প্রায় পুরোটাই বন্ধ হলো। Cloudflare এর মতো বড় কোম্পানিও এই পদ্ধতি ব্যবহার করে।

০৮ Token bucket আর leaky bucket এর পার্থক্য কী?

Window এর পদ্ধতিগুলো প্রশ্ন করে, “শেষ এক মিনিটে কয়টা request এলো?” Bucket এর পদ্ধতিগুলো অন্যভাবে ভাবে। এদের ভেতরে একটা কাল্পনিক বালতি থাকে।

Token bucket

প্রতিটা user এর একটা বালতি আছে, যাতে সর্বোচ্চ ১০টা token ধরে। প্রতি সেকেন্ডে বালতিতে ২টা করে নতুন token পড়ে। বালতি ভরা থাকলে নতুন token উপচে পড়ে নষ্ট হয়। প্রতিটা request ঢোকার আগে বালতি থেকে একটা token নেয়। Token থাকলে request পাস। বালতি খালি হলে request ফিরে যায়। এই পদ্ধতির নাম token bucket।

Refill প্রতি সেকেন্ডে 2 টা token token নেই capacity 10 এখন আছে 6 টা Request প্রতিটা 1 token নেয় token পেলো allow token শেষ 429 Too Many Requests Bucket ভরা থাকলে নতুন token উপচে পড়ে নষ্ট হয়। তাই জমানো যায় সর্বোচ্চ 10 টা।
Bucket এ প্রতি সেকেন্ডে 2 টা করে token জমে, সর্বোচ্চ 10 টা। প্রতিটা request একটা token নেয়। token থাকলে allow, না থাকলে 429। অনেকক্ষণ চুপ থাকা user তাই একসাথে 10 টা request পাঠাতে পারে, এটাই burst।

এর সবচেয়ে বড় গুণ হলো burst সামলানো। Burst মানে অল্প সময়ে হঠাৎ অনেকগুলো request। একজন সাধারণ user অনেকক্ষণ চুপ ছিলো, তারপর app খুলতেই একসাথে ১০টা request গেলো: product list, cart, offer, profile। বালতিতে ১০টা token জমা ছিলো, তাই সবগুলো পাস করলো। কিন্তু bot টা টানা সেকেন্ডে ৫০০টা পাঠালে প্রথম ১০টার পর সে সেকেন্ডে মাত্র ২টা করে পায়। মানুষের স্বাভাবিক আচরণ বাধা পায় না, অথচ টানা অত্যাচার আটকে যায়।

এজন্য public API তে token bucket সবচেয়ে জনপ্রিয়। Stripe তাদের API তে এটা ব্যবহার করে, AWS API Gateway এর throttling ও token bucket দিয়ে চলে।

Leaky bucket

এবার শাওনের অন্য দিকের সমস্যাটা দেখুন। SMS provider এর সাথে চুক্তি আছে, শাওনরা সেকেন্ডে সর্বোচ্চ ৫০০টা SMS পাঠাতে পারবে। কিন্তু sale শুরুর সেকেন্ডে order আসে ৩,০০০টা, পরের সেকেন্ডে হয়তো অনেক কম। Provider এর কাছে এই ঝাঁকুনি পাঠালে সে নিজেই 429 ফেরত দেবে।

এখানে কাজে লাগে leaky bucket। এমন একটা বালতি কল্পনা করুন, যার তলায় একটা ছোট ফুটো। উপর থেকে যত জোরেই পানি ঢালুন, ফুটো দিয়ে পানি বের হয় একই গতিতে। বালতি ভরে গেলে বাড়তি পানি উপচে পড়ে যায়।

Sale শুরু: SMS অনুরোধ এক সেকেন্ডে 3,000, পরের সেকেন্ডে 0 উপচে পড়লে queue ভরা নতুনটা drop / 429 ঠিক 500/s queue · capacity 10,000 বের হয় সবসময় একই গতিতে SMS provider limit 500 SMS/s
Request গুলো আগে একটা queue তে জমে। তলার ফুটো দিয়ে ঠিক 500/s গতিতে বের হয়ে SMS provider এ যায়। Queue ভরে গেলে নতুন request ফেলে দেওয়া হয়। Provider তাই কখনো তার limit এর বেশি চাপ পায় না।

Software এ বালতিটা আসলে একটা queue। Request গুলো queue তে ঢোকে, আর একটা নির্দিষ্ট গতিতে (এখানে সেকেন্ডে ঠিক ৫০০টা) বের হয়। Queue ভরা থাকলে নতুন request ফিরিয়ে দেওয়া হয়। NGINX এর rate limiting এই পদ্ধতিতে চলে।

চারটা পাশাপাশি

Algorithmকী গোনেBurstMemoryকোথায় মানায়
Fixed windowপ্রতি মিনিটের টুকরোয় কয়টা এলোসীমানায় দ্বিগুণ পর্যন্ত চলে যায়খুব কমসহজ internal limit
Sliding windowঠিক শেষ ৬০ সেকেন্ডে কয়টা এলোপ্রায় ঠেকায়কম (counter) বা বেশি (log)নিখুঁত per-user limit
Token bucketহাতে কয়টা token জমা আছেcapacity পর্যন্ত allow করেকমPublic API, user এর request
Leaky bucketQueue থেকে স্থির গতিতে বের করেবাইরে যাওয়ার গতি সবসময় সমানqueue এর সমানThird-party এর limit মানা

এক কথায় পার্থক্যটা এরকম। Token bucket ঠিক করে কতটা ঢুকতে পারবে, আর ছোটখাটো burst মেনে নেয়। Leaky bucket ঠিক করে কত গতিতে বের হবে, আর বাইরের দিকে কোনো burst যেতে দেয় না।

অনেকগুলো server মিলে একটা limit কীভাবে মানবে, মানে Redis এ counter রাখা আর race condition সামলানো, সেটা দেখবো সেশন ২৪ এ।

০৯ Limit পার হলে client কে কী বলবো, আর পুরো server চাপে পড়লে কী করবো?

429 আর Retry-After

Request ফিরিয়ে দেওয়ার সময় চুপচাপ connection কেটে দেবেন না। পরিষ্কার করে জানান, কী হয়েছে আর কখন আবার আসা যাবে। এর জন্য HTTP তে একটা নির্দিষ্ট code আছে: 429 Too Many Requests। সেশন ০৮ এ এর সাথে এক লাইনে পরিচয় হয়েছিলো।

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Content-Type: application/json

{"error": "rate_limited", "message": "একটু বেশি তাড়াতাড়ি হয়ে গেছে, 30 সেকেন্ড পর আবার চেষ্টা করুন"}

ভালো client এর দায়িত্বও আছে। 429 পেলে সে Retry-After এ বলা সময় পর্যন্ত অপেক্ষা করে। তার আগে আবার চেষ্টা করলে সে retry storm এর নতুন উৎস হয়ে ওঠে। শাওনদের mobile app এ এখন ঠিক এই নিয়ম বসানো হয়েছে।

Load shedding: বাংলাদেশের মানুষ এটা আগে থেকেই চেনে

Rate limiting একজন client এর বাড়াবাড়ি ঠেকায়। কিন্তু সবাই নিয়ম মেনে চললেও মোট traffic server এর ক্ষমতার চেয়ে বেশি হতে পারে। রাত বারোটার sale এ চল্লিশ লাখ মানুষের প্রত্যেকে হয়তো নিজের limit এর মধ্যেই আছে। তবু সব মিলিয়ে server আর পারছে না।

গরমের সন্ধ্যায় বিদ্যুতের চাহিদা যখন উৎপাদনের চেয়ে বেশি হয়, তখন কী হয়? বিদ্যুৎ বিভাগ কিছু এলাকায় কিছুক্ষণের জন্য লাইন বন্ধ রাখে। না রাখলে পুরো grid বসে যেতো, আর সবাই অন্ধকারে থাকতো। এটাই লোডশেডিং। Software এর load shedding ঠিক একই চিন্তা। সেশন ০৮ এ এক লাইনে দেখেছিলেন: চাপ খুব বেশি হলে কম জরুরি কাজ ইচ্ছা করে বাদ দেওয়া।

শাওনদের checkout service এখন নিজের অবস্থা মাপে: কয়টা request লাইনে অপেক্ষা করছে, আর তারা কতক্ষণ ধরে অপেক্ষা করছে। চাপ একটা সীমা পার হলে সে request গুলোকে গুরুত্ব অনুযায়ী আলাদা করে:

  1. সবার আগে বাদ: “আপনার পছন্দ হতে পারে” section আর analytics event। এগুলো না থাকলে কেউ টেরও পায় না।
  2. তারপর: Product এর review আর ছবির gallery। User একটু কম তথ্য পায়, কিন্তু কেনাকাটা চলে।
  3. শেষ পর্যন্ত রক্ষা: Cart, checkout আর payment। এগুলোই কোম্পানির আয়।

বাদ পড়া request গুলো সাথে সাথে 503 পায়। ভালো করে বাদ দেওয়ার মূল কথা হলো, তাড়াতাড়ি বাদ দেওয়া। একটা request ২ সেকেন্ড কাজ করিয়ে তারপর ফেলে দিলে সেই ২ সেকেন্ডের CPU পুরোটাই নষ্ট।

Rate limitingLoad shedding
কাকে থামায়যে নির্দিষ্ট client নিজের সীমা পার করেছেসব client মিলে যখন server এর ক্ষমতা পার হয়
কখন চালু থাকেসবসময়শুধু চাপের সময়
HTTP code429503
প্রশ্নটা“এই user কি বেশি চাইছে?”“এই মুহূর্তে আমি কতটা পারবো?”

পুরো ছবি

সেই রাতের পরের সপ্তাহে শাওনের টিম সব কিছু ঢেলে সাজালো। প্রতিটা প্রতিকার এখন নিজের জায়গায় বসে আছে:

CLIENT GATEWAY SERVICE DEPENDENCY Mobile app backoff + jitter · Retry-After মানে API Gateway rate limit (token bucket) · 429 Checkout service load shedding bulkhead pool timeout 1s retry budget 10% circuit breaker Payment নিজস্ব pool Inventory নিজস্ব pool SMS provider fallback: SMS queue
Client নিজে backoff আর jitter মেনে চলে। Gateway প্রতিটা user এর rate limit করে। Service এর ভেতরে load shedding, bulkhead, timeout, retry budget আর circuit breaker থাকে। ফলে একটা dependency slow হলেও ক্ষতি তার নিজের ঘরেই আটকে থাকে।

পরের বছর 11.11 এর রাতে SMS provider আবার ধীর হয়ে গেলো। এবার breaker ৩ সেকেন্ডের মধ্যে open হলো। SMS গুলো queue তে জমলো। Checkout একবারের জন্যও থামলো না। চল্লিশ মিনিট পর provider সেরে উঠলো, আর জমে থাকা সব SMS পরের দশ মিনিটে চলে গেলো। শাওন সেই রাতে এক কাপ চা নিয়ে শান্তিতে বসে থাকতে পারলো।

সবচেয়ে গুরুত্বপূর্ণ শিক্ষা

Distributed system এ কিছু না কিছু সবসময় ধীর বা ভাঙা থাকবে। লক্ষ্যটা তাই সব ব্যর্থতা ঠেকানো নয়। লক্ষ্য হলো, একটা অংশের ব্যর্থতা যেন বাকি অংশে ছড়াতে না পারে। Timeout, retry budget, circuit breaker আর bulkhead ব্যর্থতাকে ভেতর থেকে আটকায়। Rate limiting আর load shedding অতিরিক্ত চাপকে দরজাতেই আটকায়।

এই কৌশলগুলো সত্যিই কাজ করে কিনা, সেটা আসল ঘটনার আগেই ইচ্ছা করে failure ঘটিয়ে পরখ করা যায়। সেটাই সেশন ২২ এর বিষয়।

১০ মগজে প্রেশার দিন

আগে নিজে ভাবুন। তারপর উত্তর খুলে দেখুন: বেশিরভাগ মানুষ কীভাবে ভাবে, সেই চিন্তায় কী বাদ পড়ে, আর আসল কথাটা কোথায়।

প্রশ্ন ১: Retry তো নিরাপদ জিনিস। একটা call fail করলে আবার চেষ্টা করায় ক্ষতি কী?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“বেশি retry মানে বেশি reliability। Network এ মাঝে মাঝে গোলমাল হয়, তাই প্রতিটা layer এ ৩ বার retry রাখলে user কম error দেখবে।”

সাময়িক গোলমালের বেলায় কথাটা ঠিক। কিন্তু dependency চাপে পড়লে কী হয়, সেটা এখানে বাদ পড়েছে।

যে জায়গাটা যোগ করা দরকার

Dependency টা চাপের কারণে ধীর হলে প্রতিটা retry সেই চাপ আরো বাড়ায়। প্রতিটা layer এর retry গুণ হয়ে যায়: দুটো layer এ ৩ বার করে retry মানে ১৬ গুণ load। অসুস্থ service তখন আর সেরে ওঠার সুযোগ পায় না। এটাই retry storm।

তাই retry রাখতে হলে শুধু একটা layer এ রাখুন। Exponential backoff আর jitter দিন। শুধু সাময়িক error এ retry করুন। পুরো service এর জন্য একটা retry budget রাখুন। আর যে call retry হবে, সেটা idempotent কিনা নিশ্চিত করুন।

সঠিক ভাষায়

Retry একটা বাড়তি load, যেটা আপনি ঠিক সেই সময়ে পাঠান, যখন dependency সবচেয়ে দুর্বল। তাই retry কে সবসময় একটা সীমার মধ্যে রাখুন।

প্রশ্ন ২: আমাদের circuit breaker আছে। তাহলে আলাদা করে timeout দেওয়ার কি দরকার আছে?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“Circuit breaker তো ব্যর্থ dependency কে call করা বন্ধ করে দেয়। তাই timeout না দিলেও চলে, breaker নিজেই সামলে নেবে।”

শুনতে যুক্তিসংগত। কিন্তু breaker কীভাবে “ব্যর্থতা” চেনে, সেটা ভেবে দেখা হয়নি।

যে জায়গাটা যোগ করা দরকার

Breaker open হয় fail হওয়া call গুনে। Timeout না থাকলে ধীর call কখনো fail করে না, শুধু ঝুলে থাকে। শাওনদের SMS call ৩০ সেকেন্ড পরে হয়তো সফলও হতো। Breaker তখন কোনো fail ই দেখে না, তাই কখনো open হয় না। এদিকে সব thread আটকে যায়।

Timeout ই “ধীর” কে “fail” এ রূপান্তর করে। তখনই breaker সেটা গুনতে পারে। তাই দুটো একসাথে লাগে। Timeout একটা call কে বাঁচায়, আর breaker পরের হাজারটা call কে বাঁচায়।

মনে রাখার উপায়

Timeout ছাড়া circuit breaker হলো এমন fuse, যেটা short circuit চিনতেই পারে না।

প্রশ্ন ৩: Interview তে প্রশ্ন: login API তে bot ঠেকাতে কী rate limit বসাবেন?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“প্রতি IP থেকে মিনিটে ৫টা login চেষ্টা। Fixed window counter দিয়ে গুনবো, বেশি হলে request reject করবো।”

সংখ্যাটা যুক্তিসংগত। কিন্তু তিনটা জায়গায় এই উত্তর দুর্বল।

যে জায়গাটা যোগ করা দরকার

প্রথমত, শুধু IP দিয়ে গুনলে দুই দিক থেকেই ভুল হয়। মোবাইল নেটওয়ার্কে হাজার জন user একই IP তে থাকতে পারে, তারা অকারণে আটকে যাবে। আবার bot অনেকগুলো IP ব্যবহার করলে প্রতিটা IP limit এর নিচেই থাকবে। তাই IP এর পাশাপাশি যে account এ login এর চেষ্টা হচ্ছে, সেটা ধরেও গুনুন।

দ্বিতীয়ত, fixed window এর সীমানায় দ্বিগুণ চেষ্টা ঢুকে যেতে পারে। Login এর মতো সংবেদনশীল জায়গায় sliding window বা token bucket ভালো।

তৃতীয়ত, শুধু reject করলেই কাজ শেষ নয়। 429 আর Retry-After দিয়ে পরিষ্কার উত্তর দিন। আর বারবার ভুল password এর পর অপেক্ষার সময় বাড়াতে থাকুন। অনেকগুলো server এ একটা limit কীভাবে মানা হবে, সেই প্রশ্ন এলে বলুন: counter গুলো Redis এর মতো একটা shared জায়গায় রাখতে হবে (সেশন ২৪)।

Interview এর জন্য

Rate limiting এর উত্তরে চারটা জিনিস বলুন: কাকে ধরে গুনবেন (key), কোন algorithm, limit পার হলে client কী পাবে, আর অনেক server এ counter কোথায় থাকবে।

১১ যে টার্মগুলো শিখলাম

টার্মমানে
Cascading failureএকটা service এর সমস্যা তাকে call করা service গুলোতে একে একে ছড়িয়ে পড়া। (সেশন ১২)
Request-level timeoutপ্রতিটা network call এর জন্য একটা সময়সীমা, যার পর call টা ছেড়ে দেওয়া হয়।
Little’s Lawএকসাথে ব্যস্ত কাজের সংখ্যা, প্রতি সেকেন্ডে আসা কাজ আর প্রতিটা কাজের সময়ের গুণফলের সমান।
Retry stormসবার retry মিলে এত load তৈরি হওয়া যে অসুস্থ dependency আর সেরে উঠতে পারে না।
Exponential backoffপ্রতিবার retry এর আগে আগের চেয়ে দ্বিগুণ সময় অপেক্ষা করা। (সেশন ০৯)
JitterRetry এর অপেক্ষার সাথে random সময় যোগ করা, যাতে সবাই একসাথে ফিরে না আসে। (সেশন ০৯)
Retry budgetপুরো service এর retry মূল request এর একটা নির্দিষ্ট অংশের (যেমন ১০%) বেশি হতে না দেওয়া।
Circuit breakerবারবার fail করা dependency কে কিছুক্ষণের জন্য call করা বন্ধ রাখার ব্যবস্থা।
ClosedBreaker এর স্বাভাবিক state: সব call যায়, আর fail গোনা হয়।
OpenBreaker এর যে state এ কোনো call যায় না, সবাই সাথে সাথে fallback পায়।
Half-openOpen এর পর পরখ করার state: অল্প কয়েকটা trial call যায়, ফল দেখে breaker closed বা open হয়।
Fallbackআসল call না হলে user কে দেওয়ার জন্য আগে থেকে ঠিক করা বিকল্প উত্তর।
Bulkheadপ্রতিটা dependency এর জন্য আলাদা thread বা connection pool, যাতে একজনের সমস্যা অন্যদের জায়গা দখল না করে।
Rate limitingএকজন client একটা নির্দিষ্ট সময়ে কয়টা request পাঠাতে পারবে, তার সীমা।
Fixed windowসময়কে নির্দিষ্ট টুকরোয় ভাগ করে প্রতিটা টুকরোর request গোনা। সীমানায় burst ঢুকে যায়।
Sliding windowসবসময় ঠিক শেষ নির্দিষ্ট সময়ের (যেমন ৬০ সেকেন্ড) request গোনা।
Token bucketনির্দিষ্ট গতিতে token জমে, প্রতিটা request একটা token খরচ করে। Capacity পর্যন্ত burst মেনে নেয়।
Leaky bucketRequest একটা queue তে জমে আর স্থির গতিতে বের হয়। ভরে গেলে নতুন request ফিরে যায়।
Burstঅল্প সময়ের মধ্যে হঠাৎ অনেকগুলো request আসা।
429 Too Many RequestsClient তার rate limit পার করেছে, এটা জানানোর HTTP code।
Retry-Afterকত সময় পর আবার চেষ্টা করা যাবে, তা জানানোর HTTP header।
Load sheddingServer এর মোট চাপ সীমা পার হলে কম জরুরি request ইচ্ছা করে তাড়াতাড়ি বাদ দেওয়া। (সেশন ০৮)

১২ শর্ট সামারি

  1. একটা dependency fail করার চেয়ে ধীর হওয়া বেশি বিপজ্জনক, কারণ ধীর call আপনার thread ধরে রাখে আর cascading failure শুরু করে। প্রতিটা network call এ মেপে ঠিক করা timeout দিন।
  2. নিয়ন্ত্রণ ছাড়া retry চাপের সময় load বহুগুণ বাড়িয়ে retry storm তৈরি করে। শুধু একটা layer এ retry রাখুন, আর exponential backoff, jitter আর retry budget মেনে চলুন।
  3. Circuit breaker বারবার fail করা dependency কে কিছুক্ষণ call করা বন্ধ রাখে (closed, open, half-open)। Fallback ঠিক করে তখন user কী পাবে, আর bulkhead ক্ষতিকে একটা pool এর মধ্যে আটকে রাখে।
  4. Rate limiting একজন client এর বাড়াবাড়ি ঠেকায়। এর চারটা algorithm হলো fixed window, sliding window, token bucket আর leaky bucket, আর limit পার হলে client পায় 429 আর Retry-After। সবার মিলিত চাপ ক্ষমতা পার করলে load shedding কম জরুরি কাজ আগে বাদ দেয়।

১৩ মনে রাখুন