মূল কনটেন্টে যান
ডক•24

Reliability Patterns — Resiliency

এই পথে যেখানে আসে

এই পথের কোনো দিনে এই ডক নেই — জেনেশুনে বাদ, দরকার হলে পরে। 🧠 Pareto principle

Resiliency হলো ব্যর্থতা থেকে সেরে ওঠার ও কার্যক্ষম থাকার ক্ষমতা। এর ভিত্তিমূলক দর্শনটা গুরুত্বপূর্ণ: ব্যর্থতা এড়ানো যায় না — ব্যর্থতা ধরে নিয়েই ডিজাইন করতে হবে। যথেষ্ট বড় সিস্টেমে কোনো না কোনো কিছু সবসময়ই ভাঙা অবস্থায় থাকে; প্রশ্নটা হলো সেই ভাঙা অংশটা সবাইকে টেনে নামাবে কি না।

Bulkhead

জাহাজের নকশা থেকে ধার করা ধারণা — জাহাজকে জলনিরোধী কুঠুরিতে ভাগ করা হয়, যাতে একটা কুঠুরিতে পানি ঢুকলেও পুরো জাহাজ না ডোবে। সফটওয়্যারে এর মানে হলো resource গুলোকে আলাদা আলাদা pool-এ ভাগ করা, যাতে একটা অংশের সমস্যা অন্য অংশের resource শেষ করে না ফেলে।

উদাহরণ: প্রতিটা downstream service-এর জন্য আলাদা connection pool ও thread pool। Service A ধীর হয়ে তার ১০টা thread খেয়ে ফেললেও service B-র জন্য নির্ধারিত thread গুলো অক্ষত থাকে, তাই B-নির্ভর feature গুলো চলতেই থাকে।

অন্যান্য প্রয়োগ: গুরুত্বপূর্ণ ও সাধারণ গ্রাহকের জন্য আলাদা instance pool; ভিন্ন অগ্রাধিকারের কাজের জন্য আলাদা queue ও worker; container-এ CPU/memory সীমা।

খরচ: resource-এর ব্যবহার কম দক্ষ হয় — একটা pool অলস বসে থাকলেও অন্য pool সেটা ধার নিতে পারে না। এই অদক্ষতাটাই isolation-এর মূল্য, এবং সাধারণত এটা যুক্তিসঙ্গত মূল্য।

Circuit Breaker

বারবার ব্যর্থ হওয়া downstream-এর দিকে call পাঠানো সাময়িকভাবে বন্ধ করে দেওয়া, যাতে cascading failure ঠেকানো যায় এবং কষ্টে থাকা service সেরে ওঠার সুযোগ পায়। Closed → Open → Half-Open চক্র ও বাস্তবায়নের নিয়ম: High Availability।

Bulkhead-এর সাথে সম্পর্ক: এই দুটো একসাথে ব্যবহার করলে সবচেয়ে ভালো কাজ করে — bulkhead ক্ষতির বিস্তার সীমিত করে, circuit breaker ক্ষতির সময়কাল সীমিত করে।

Compensating Transaction

Distributed system-এ বহু service জুড়ে একটামাত্র ACID transaction চালানো কার্যত অসম্ভব। তাই "rollback"-এর বদলে ব্যবহৃত হয় ক্ষতিপূরণ — ইতিমধ্যে সম্পন্ন হওয়া প্রতিটা ধাপের জন্য একটা বিপরীত কাজ চালানো।

উদাহরণ (ভ্রমণ বুকিং): ফ্লাইট বুক ✓ → হোটেল বুক ✓ → গাড়ি বুক ✗। এখন উল্টো পথে ক্ষতিপূরণ: হোটেল বাতিল করো, ফ্লাইট বাতিল করো।

এটাই Saga প্যাটার্নের ভিত্তি — বহু ধাপের একটা ব্যবসায়িক লেনদেনকে ধারাবাহিক স্থানীয় transaction-এর ক্রম হিসেবে দেখা, যার প্রতিটার একটা compensating action আছে।

বাস্তবায়নের কঠিন দিক:

  • ক্ষতিপূরণ প্রায়ই নিখুঁত বিপরীত নয় — ইমেইল পাঠিয়ে ফেললে সেটা "unsend" করা যায় না; তখন ক্ষমাপ্রার্থনার আরেকটা ইমেইল পাঠাতে হয়। ব্যবসায়িকভাবে গ্রহণযোগ্য ক্ষতিপূরণ কী, সেটা ব্যবসার সাথে আলোচনা করে ঠিক করতে হয়।
  • ক্ষতিপূরণের কাজটাও ব্যর্থ হতে পারে, তাই সেটাকেও idempotent ও retryable হতে হবে, এবং শেষ পর্যন্ত মানুষের হস্তক্ষেপের একটা পথ রাখতে হবে।
  • মাঝের সময়টায় ডেটা অসামঞ্জস্যপূর্ণ অবস্থায় থাকে — এটা মেনে নিতে হবে ও UI-তে প্রকাশ করতে হবে ("প্রক্রিয়াধীন")।

Health Endpoint Monitoring

ব্যর্থতা শনাক্ত করার ভিত্তি — না দেখলে সেরে ওঠার প্রক্রিয়া শুরুই হবে না। Liveness বনাম readiness এবং জরুরি বনাম ঐচ্ছিক নির্ভরতার পার্থক্য: Availability।

Leader Election

সমপর্যায়ের instance গুলোর মধ্যে একজনকে সমন্বয়কারী নির্বাচন করা, এবং নেতা মারা গেলে স্বয়ংক্রিয়ভাবে নতুন নেতা বেছে নেওয়া। Resiliency-র দৃষ্টিকোণে মূল কথা — নেতৃত্ব একটা lease, স্থায়ী পদ নয়। TTL/lease ছাড়া lock ব্যবহার করলে নেতা মরে গেলে পুরো সিস্টেম চিরতরে অচল হয়ে থাকবে। নিজে বাস্তবায়ন না করে পরীক্ষিত ব্যবস্থা (ZooKeeper, etcd, Consul) ব্যবহার করুন। বিস্তারিত: Design & Implementation Patterns।

Queue-Based Load Leveling

Queue দিয়ে ট্রাফিকের চূড়া সমতল করা। Resiliency-র দিক থেকে এর দুটো অবদান — চাপের মুখে সিস্টেম ভাঙে না কেবল ধীর হয়, এবং consumer সম্পূর্ণ ডাউন থাকলেও কাজগুলো queue-তে টিকে থাকে ও ফিরে আসার পর সম্পন্ন হয়। বিস্তারিত: Messaging Patterns।

Retry

ক্ষণস্থায়ী (transient) ব্যর্থতার ক্ষেত্রে অপারেশনটা আবার চেষ্টা করা। Cloud পরিবেশে বেশিরভাগ ব্যর্থতাই ক্ষণস্থায়ী — সাময়িক network সমস্যা, throttling, instance পুনরায় চালু হওয়া — তাই সঠিকভাবে করা retry অসাধারণ কার্যকর।

সঠিকভাবে করার নিয়ম:

  • সব ব্যর্থতা retry করবেন না। 400 Bad Request, 401, 403, 404 — এগুলো retry করে কিছুই বদলাবে না। Retry করুন কেবল timeout, connection error, 429, 502/503/504-এ।
  • Exponential backoff ব্যবহার করুন — 1s, 2s, 4s, 8s। সাথে সাথে বারবার চেষ্টা করা ব্যর্থ service-কে আরও ডোবায়।
  • Jitter অপরিহার্য — backoff-এ এলোমেলোতা যোগ না করলে সব client নিখুঁতভাবে একই মুহূর্তে ফিরে আসবে এবং একটা synchronized ঢেউ তৈরি করবে।
  • সর্বোচ্চ চেষ্টার সংখ্যা বেঁধে দিন, এবং মোট সময়েরও একটা সীমা রাখুন — অসীম retry একটা লুকানো outage।
  • শুধু একটা স্তরে retry করুন। প্রতিটা স্তরে ৩ বার retry থাকলে তিন স্তরে সেটা ২৭ গুণ ট্রাফিক (Retry Storm)।
  • অপারেশনটা idempotent হতে হবে — নাহলে retry duplicate তৈরি করবে (Idempotent Operations)।
  • Circuit breaker দিয়ে ঘেরাও করুন — এই দুটো একসাথে ব্যবহার করাই সঠিক পদ্ধতি: retry ক্ষণস্থায়ী সমস্যা সামলায়, circuit breaker দীর্ঘস্থায়ী সমস্যায় হাল ছেড়ে দেয়।

Scheduler Agent Supervisor

বহু ধাপের বিতরণকৃত কাজকে scheduler (সমন্বয়), agent (সম্পাদন) ও supervisor (তদারকি ও পুনরুদ্ধার) — এই তিন ভূমিকায় ভাগ করা। Resiliency-তে এর মূল অবদান supervisor: সে সক্রিয়ভাবে আটকে থাকা বা ব্যর্থ কাজ খুঁজে বের করে পুনরায় চেষ্টা বা ক্ষতিপূরণ শুরু করে। এটা ছাড়া দীর্ঘ প্রক্রিয়াগুলো নীরবে অর্ধসমাপ্ত অবস্থায় পড়ে থাকে — এবং কেউ জানতেও পারে না। বিস্তারিত: Messaging Patterns।


Resiliency-র সংস্কৃতি

প্যাটার্নগুলো লিখে রাখা সহজ, কিন্তু সেগুলো সত্যিই কাজ করে কি না তা কেবল পরীক্ষা করেই জানা যায়। তাই —

  • Chaos engineering — নিয়ন্ত্রিতভাবে ইচ্ছাকৃত ব্যর্থতা ঘটান (instance মারুন, latency ঢোকান, dependency বন্ধ করুন) এবং দেখুন সিস্টেম কীভাবে সাড়া দেয়। প্রোডাকশনে ব্যর্থতা আবিষ্কার করার চেয়ে নিজে ঘটিয়ে শেখা অনেক সস্তা।
  • সবকিছুতে timeout দিন — timeout ছাড়া কোনো network call করবেন না। ডিফল্ট timeout প্রায়ই অসীম বা অযৌক্তিকভাবে দীর্ঘ, আর একটা ঝুলে থাকা call হলো সব cascading failure-এর সূচনাবিন্দু।
  • Blameless postmortem — প্রতিটা ঘটনার পর কারণ খুঁজুন, দোষী নয়। সিস্টেম শেখে তখনই, যখন মানুষ ভয় ছাড়া সত্য বলতে পারে।

পূর্ববর্তী: High Availability · পরবর্তী: Security