রেট লিমিটার ডিজাইন (Design a Rate Limiter)
রেট লিমিট (Rate Limit) কেন প্রয়োজন?
কল্পনা করুন একটি পানির কল। এটি সামান্য খুলে রাখলে আপনি সুন্দরভাবে পানি পেতে থাকবেন। কিন্তু যদি আপনি এর সাথে ৫০টি হোজ পাইপ যুক্ত করে একেবারে পুরোপুরি খুলে দেন — তবে পাইপগুলো ফেটে যাবে। একটি রেট লিমিটার (rate limiter) হলো একটি ভালভ (valve) যা পানির প্রবাহ নিয়ন্ত্রণ করে।
রেট লিমিটিং ছাড়া আপনার API যেসব ঝুঁকির সম্মুখীন হতে পারে:
- ডিনায়াল অফ সার্ভিস (DoS) আক্রমণ: কোনো ক্ষতিকারক ব্যক্তি বা হ্যাকার আপনার সার্ভারে লক্ষ লক্ষ রিকোয়েস্ট পাঠিয়ে বন্যা (flood) বইয়ে দেয়, যাতে আসল ব্যবহারকারীদের জন্য সার্ভিসটি আন-অ্যাভেইলেবল বা ব্যবহার অযোগ্য হয়ে যায়।
- ত্রুটিপূর্ণ বা মিসবিহেভিং ক্লায়েন্ট: কোনো বাগ যুক্ত স্ক্রিপ্ট ভুল করে সেকেন্ডে ১০টি রিকোয়েস্টের পরিবর্তে ১০,০০০টি রিকোয়েস্ট পাঠাতে পারে।
- রিসোর্সেস স্টারভেশন: কোনো একজন ভারী ব্যবহারকারী একাই আপনার সার্ভারের সমস্ত ক্ষমতা বা সক্ষমতা ব্যবহার করে ফেলে, যার ফলে অন্যদের জন্য কিছুই অবশিষ্ট থাকে না।
- অতিরিক্ত খরচ: ক্লাউড এনভায়রনমেন্টে প্রতিটি রিকোয়েস্টের জন্য টাকা খরচ হয়। অনিয়ন্ত্রিত ট্র্যাফিক রাতের ঘুম হারাম করে দিতে পারে।
রেট লিমিটিং আপনার সিস্টেমের ন্যায্য ব্যবহার (fair usage) নিশ্চিত করে এবং সিস্টেমকে ওভারলোড হওয়া থেকে রক্ষা করে। গুগল, টুইটার, গিটহাব, স্ট্রাইপ এর মতো প্রতিটি বড় API তে রেট লিমিটার ব্যবহার করা হয়।
রেট লিমিটার কোথায় বসাবেন
আপনার কাছে কয়েকটি বিকল্প রয়েছে:
- ক্লায়েন্ট-সাইড: ক্লায়েন্ট নিজেই তার রিকোয়েস্টগুলো লিমিট বা সীমাবদ্ধ করে। এটি অনির্ভরযোগ্য — কারণ কোনো দূষিত ক্লায়েন্ট বা হ্যাকার খুব সহজেই এটি এড়িয়ে যেতে পারে।
- সার্ভার-সাইড: আপনার API সার্ভার রিকোয়েস্ট প্রসেস করার আগেই লিমিট চেক করে। এটি একটি সাধারণ এবং সহজ পদ্ধতি।
- মিডলওয়্যার / এপিআই গেটওয়ে: ব্যাকএন্ড সার্ভারে পৌঁছানোর আগেই একটি সম্পূর্ণ আলাদা স্তর (যেমন Nginx, Kong, বা AWS API Gateway) রেট লিমিটিং হ্যান্ডেল করে। প্রোডাকশনের ক্ষেত্রে এটি সবচেয়ে সাধারণ পদ্ধতি — যা আপনার সার্ভারকে অতিরিক্ত ট্রাফিকের হাত থেকে পুরোপুরি রক্ষা করে।
অধিকাংশ সিস্টেম API Gateway পদ্ধতি ব্যবহার করে থাকে। এটি সেন্ট্রালাইজড, যেকোনো ল্যাঙ্গুয়েজের জন্য ব্যবহার করা যায় (language-agnostic), এবং এর জন্য আপনার অ্যাপ্লিকেশনের কোডে কোনো পরিবর্তন করার প্রয়োজন পড়ে না।
অ্যালগরিদম ১: টোকেন বাকেট (Token Bucket)
এটি সবচেয়ে জনপ্রিয় রেট লিমিটিং অ্যালগরিদম। একে একটি আর্কেড টোকেন মেশিনের মতো ভাবতে পারেন:
- আপনার কাছে একটি বাকেট (bucket) বা বালতি আছে যা টোকেন ধারণ করতে পারে (ধরা যাক, সর্বোচ্চ ১০টি টোকেন)।
- একটি নির্দিষ্ট হারে টোকেন যোগ করা হয় (ধরা যাক, প্রতি সেকেন্ডে ১টি টোকেন)।
- প্রতিটি রিকোয়েস্ট করতে ১টি টোকেনের প্রয়োজন হয়। যদি বালতিতে টোকেন থাকে, তবে রিকোয়েস্টটি কার্যকর হয়।
- যদি বাকেটটি খালি থাকে, তবে রিকোয়েস্টটি বাতিল হয়ে যায় (HTTP 429)।
- যদি বাকেটটি পূর্ণ থাকে, তবে নতুন টোকেন উপচে পড়ে (অর্থাৎ বাতিল হয়ে যায়)।
এটি কেন দুর্দান্ত: এটি অল্প সময়ের জন্য অতিরিক্ত ট্র্যাফিক (burs of traffic - বাকেটের সাইজ পর্যন্ত) পারমিট বা অ্যালাউ করে, এবং একই সাথে দীর্ঘমেয়াদী গড়ের হার বা রেট (long-term average rate) বজায় রাখে। ১০ সাইজের বাকেট এবং ১/সেকেন্ড রিফিল রেট যুক্ত একজন ব্যবহারকারী তাৎক্ষণিকভাবে ১০টি রিকোয়েস্ট পাঠাতে পারেন, এবং এরপরে প্রতি সেকেন্ডে ১টি রিকোয়েস্ট পাঠাতে পারেন।
কারা ব্যবহার করে: Amazon, Stripe, এবং বেশিরভাগ ক্লাউড API।
টোকেন বাকেটের ইমপ্লিমেন্টেশন
অ্যালগরিদম ২: লিকিং বাকেট (Leaking Bucket)
এটি টোকেন বাকেটের মতোই, তবে এটি একটি স্থির, কনস্ট্যান্ট রেটে রিকোয়েস্ট প্রসেস করে। নিচে ছিদ্র যুক্ত একটি বালতির কথা ভাবুন — আপনি যত দ্রুতই পানি ঢালুন না কেন, একটি নির্দিষ্ট স্থির হারেই তা থেকে পানি বের হবে।
- রিকোয়েস্টগুলো একটি কিউ (queue) তে (অর্থাৎ বালতিতে) প্রবেশ করে।
- রিকোয়েস্টগুলো একটি স্থির হারে (fixed rate) প্রসেস করা হয় (যেন পানি লিক হয়ে বের হচ্ছে)।
- যদি কিউ বা বালতি পূর্ণ থাকে, তবে নতুন রিকোয়েস্টগুলো বাতিল বা ড্রপ হয়ে যায়।
টোকেন বাকেট থেকে পার্থক্য: টোকেন বাকেট বার্স্ট (bursts) বা হঠাৎ অনেক রিকোয়েস্ট একবারে অ্যালাউ করে (আপনি একবারে সব টোকেন ব্যবহার করতে পারেন)। লিকিং বাকেট একটি সম্পূর্ণ মসৃণ বা স্মুদ, ধ্রুব আউটপুট রেট (constant output rate) প্রয়োগ করে। এটি তখন সবচেয়ে বেশি উপযোগী যখন আপনার প্রেডিক্টেবল বা অনুমানযোগ্য, অবিচ্ছিন্ন প্রসেসিং প্রয়োজন হয় — যেমন ইমেইল পাঠানো বা পেমেন্ট প্রসেস করা।
অ্যালগরিদম ৩: ফিক্সড উইন্ডো কাউন্টার (Fixed Window Counter)
এটি অত্যন্ত সহজ পদ্ধতি। সময়কে একটি নির্দিষ্ট উইন্ডোতে (যেমন, প্রতি মিনিটে) ভাগ করা হয় এবং বর্তমান উইন্ডোতে কতটা রিকোয়েস্ট এসেছে তা গণনা করা হয়। যদি গণনা নির্দিষ্ট সীমা ছাড়ায়, তবে রিকোয়েস্ট বাতিল করা হয়।
- উইন্ডো: ১২:০০:০০ – ১২:০০:৫৯ → ম্যাক্স বা সর্বোচ্চ ১০০টি রিকোয়েস্ট
- কাউন্টার: ০, ১, ২, ... ১০০ → ১২:০১:০০ পর্যন্ত বাকি সব কিছু বাতিল করুন
সমস্যা: বাউন্ডারি স্পাইক (Boundary spikes) এর অন্যতম প্রধান সমস্যা। যদি একজন ব্যবহারকারী ১২:০০:৫৯ টায় ১০০টি রিকোয়েস্ট পাঠায় এবং ১২:০১:০০ টায় আরও ১০০টি পাঠায়, তবে সে ২ সেকেন্ডে মোট ২০০টি রিকোয়েস্ট পাঠিয়েছে — যা নির্ধারিত হারের দ্বিগুণ! যদিও প্রতিটি উইন্ডোর ভেতরে এটি ঠিক মনে হয়, তবে বাউন্ডারির ফাঁকফোকর দিয়ে এক সাথে প্রচুর রিকোয়েস্ট চলে আসে। এটি ফিক্সড উইন্ডোর সবচেয়ে বড় দুর্বলতা।
অ্যালগরিদম ৪: স্লাইডিং উইন্ডো (Sliding Window)
এটি মূলত ফিক্সড উইন্ডোর বাউন্ডারি সমস্যাটি সমাধান করে। এর দুটি প্রকারভেদ বা রূপ রয়েছে:
স্লাইডিং উইন্ডো লগ (Sliding Window Log): এটি মূলত সকল রিকোয়েস্টের টাইমস্ট্যাম্পের (timestamps) একটি স্টোর করা লগ হিসেবে কাজ করে। প্রতিটি নতুন রিকোয়েস্টের জন্য উইন্ডোর বাইরের পুরনো টাইমস্ট্যাম্পগুলো মুছে ফেলা হয় এবং অবশিষ্টগুলো গণনা করা হয়। যদি এই গগণা নির্দিষ্ট সীমার নিচে থাকে, তবে রিকোয়েস্ট অ্যালাউ করা হয়। এটি বেশ সঠিক হলেও মেমোরির প্রচুর বা ভারী ব্যবহার করে — কারণ আপনাকে প্রতিটি টাইমস্ট্যাম্প স্টোর করে রাখতে হয়।
স্লাইডিং উইন্ডো কাউন্টার (Sliding Window Counter): এটি মূলত একটি হাইব্রিড বা মিশ্র পদ্ধতি। এটি পূর্ববর্তী উইন্ডো এবং বর্তমান উইন্ডোর কাউন্ট বা গণনা ধরে রাখে। একটি ওয়েটেড এভারেজ বা গড় পদ্ধতি ব্যবহার করে বর্তমান রেট অনুমান করা হয়: estimated = prev_count × overlap_ratio + curr_count। এটি একটি অনুমান নির্ভর হিসাব হলেও খুব কম মেমরি ব্যবহার করে এবং রেডিজ (Redis) এর মাধ্যমে বাউন্ডারির সমস্যা খুব দারুণভাবে সমাধান করতে পারে।
স্লাইডিং উইন্ডো কাউন্টার বেশিরভাগ সিস্টেমের জন্য একটি দুর্দান্ত ব্যালেন্স প্রদান করে — এটি যথেষ্ট সঠিক, কম মেমোরি ব্যবহার করে এবং খুব সহজেই ইমপ্লিমেন্ট বা প্রয়োগ করা যায়।
ডিস্ট্রিবিউটেড রেট লিমিটিং (Distributed Rate Limiting)
উপরের অ্যালগরিদমগুলো একটি মাত্র সার্ভারে দারুণভাবে কাজ করে। কিন্তু যদি একটি লোড ব্যালান্সারের পেছনে ১০টি API সার্ভার থাকে, তখন কী করবেন? প্রতিটি সার্ভারের নিজস্ব কাউন্টার থাকে — একজন ব্যবহারকারী ১০টি সার্ভারে নক (hit) করে তার রেট লিমিট ১০ গুণ বাড়িয়ে নিতে পারে!
সমাধান:
সেন্ট্রালাইজড স্টোর (Redis): সকল সার্ভার Redis-এর মতো সেন্ট্রাল ডেটাবেসে থাকা একই কাউন্টার চেক এবং আপডেট করে। রেস কন্ডিশন (race conditions) এড়াতে Redis-এর অ্যাটমিক অপারেশন (INCR, EXPIRE) ব্যবহার করুন। এটি সবচেয়ে কমন বা সাধারণ একটি অ্যাপ্রোচ।
স্টিকি সেশন (Sticky sessions): প্রতিটি ব্যবহারকারীকে একটি নির্দিষ্ট সার্ভারে রাউট (IP হ্যাশের মাধ্যমে) করুন। এতে লোকাল রেট লিমিটিং ভালোভাবে কাজ করে। তবে এটি লোড ব্যালান্সারের ফ্লেক্সিবিলিটি কমিয়ে দেয় এবং SPOF (Single Point of Failure) তৈরি করতে পারে।
লোড ব্যালান্সার/গেটওয়েতে রেট লিমিট: মূল সার্ভারে রিকোয়েস্ট যাওয়ার আগে রেট লিমিটার বসান। Nginx, Kong, এবং AWS API Gateway-র মতো টুলে শেয়ার্ড স্টেটসহ বিল্ট-ইন রেট লিমিটার থাকে।
অ্যাটমিক রেট লিমিটিং-এর জন্য Redis স্ক্রিপ্ট:
Redis-এ একটি Lua স্ক্রিপ্ট ব্যবহার করে পারমাণবিকভাবে বা অ্যাটমিকভাবে চেক-এবং-ইনক্রিমেন্ট (check-and-increment) করুন। এটি রেস কন্ডিশন এড়ায়, যেখানে দুটি সার্ভার একসাথে কাউন্ট ৯৯ (যা লিমিট ১০০-এর কম) রিড করতে পারে এবং উভয়ই তাদের রিকোয়েস্ট অ্যালাউ বা অনুমতি দিতে পারে।
HTTP 429 রেসপন্স
যখন কোনো রিকোয়েস্ট রেট লিমিট করে দেওয়া হয়, তখন নিচের প্রয়োজনীয় হেডারসহ একটি 429 Too Many Requests রিটার্ন করুন:
X-RateLimit-Limit: 100— একটি নির্দিষ্ট উইন্ডোতে সর্বোচ্চ অনুমোদিত রিকোয়েস্টের সংখ্যা।X-RateLimit-Remaining: 0— আপনার আর কতগুলো রিকোয়েস্ট বাকি আছে।X-RateLimit-Reset: 1640000060— উইন্ডোটি কখন রিসেট হবে (Unix Timestamp অনুযায়ী)।Retry-After: 30— কত সেকেন্ড অপেক্ষা করার পর আবার চেষ্টা করা যাবে।
একটি ভালো রেট লিমিটার রেসপন্স তথ্যাবহুল হওয়া উচিত, শাস্তিমূলক নয়। তারা ক্লায়েন্টদের ঠিক কীরকম আচরণ করতে হবে তা বলে দেয়। এটি অপ্রয়োজনীয় রিট্রাই স্ট্রম বা ক্রমাগত পুনরায় চেষ্টার প্রবণতাকে কমিয়ে দেয় এবং আপনার API-কে আরও বেশি ডেভেলপার-বান্ধব করে তোলে।
Key Metrics
ছোট কুইজ
পড়া চালিয়ে যান