আমি কীভাবে Redis-এ একটি Sliding Window Rate Limiter তৈরি করেছি
আমাদের ভিডিও API প্রতি সন্ধ্যায় ৮টার সময় (UTC) ক্র্যাশ করত।
এটি আসল ট্রাফিক ছিল না। কিছু স্ক্র্যাপার (scraper) আমাদের ট্রেন্ডিং এন্ডপয়েন্ট (trending endpoint) খুঁজে পেয়েছিল এবং সেখানে ক্রমাগত রিকোয়েস্ট পাঠাতে শুরু করেছিল। আমাদের ডাটাবেস কুয়েরিগুলো স্তূপ হয়ে গিয়েছিল। আসল ব্যবহারকারীরা ভিডিওর পরিবর্তে শুধু লোডিং স্পিনার দেখতে পাচ্ছিলেন।
আমাদের রেট লিমিটিং (rate limiting) প্রয়োজন ছিল। বেশিরভাগ সাধারণ পদ্ধতি সমস্যাটিকে আরও বাড়িয়ে দিচ্ছিল।
Fixed Window কাউন্টারগুলোর সমস্যা: বেশিরভাগ মানুষ ফিক্সড উইন্ডো (fixed window) দিয়ে শুরু করেন। আপনি একটি নির্দিষ্ট সময়ের ব্লকে রিকোয়েস্ট গণনা করেন। যদি আপনার লিমিট প্রতি মিনিটে ১০০ হয়, তবে একজন ব্যবহারকারী ১১:৫৯:৫৯-এ ১০০টি রিকোয়েস্ট এবং ১২:০০:০০-এ আরও ১০০টি রিকোয়েস্ট পাঠাতে পারে। এর মানে হলো এক সেকেন্ডের মধ্যে ২০০টি রিকোয়েস্ট। স্ক্র্যাপাররা আপনার লিমিট বাইপাস করার জন্য এই সীমানাগুলোকে কাজে লাগায়।
সমাধান: Sliding Window। একটি স্লাইডিং উইন্ডো বর্তমান সময়ের সাপেক্ষে গত N সেকেন্ডের রিকোয়েস্ট গণনা করে। এখানে কাজে লাগানোর মতো কোনো নির্দিষ্ট সীমানা নেই।
আমাদের প্রয়োজন অনুযায়ী আমরা দুটি ভিন্ন পদ্ধতি ব্যবহার করি:
- Sliding Window Log (নির্ভুলতার জন্য) আমরা অথেন্টিকেটেড (authenticated) ব্যবহারকারীদের জন্য Redis Sorted Sets (ZSET) ব্যবহার করি। আমরা প্রতিটি রিকোয়েস্টের জন্য একটি টাইমস্ট্যাম্প (timestamp) সংরক্ষণ করি।
- আমরা পুরনো এন্ট্রিগুলো বাদ দিতে
ZREMRANGEBYSCOREব্যবহার করি। - বর্তমান রিকোয়েস্ট গণনা করতে আমরা
ZCARDব্যবহার করি। - নতুন রিকোয়েস্ট রেকর্ড করতে আমরা
ZADDব্যবহার করি।
রেস কন্ডিশন (race conditions) প্রতিরোধ করতে, আমরা এই লজিকটি একটি একক Lua স্ক্রিপ্টে চালাই। এটি অপারেশনটিকে অ্যাটমিক (atomic) করে তোলে। একটি স্ক্রিপ্ট Redis-এ মাত্র একবার যাওয়ার মাধ্যমেই সবকিছু সামলে নেয়। এটি দুটি রিকোয়েস্টকে একই সময়ে লিমিটের নিচে থাকা বলে ভুল করতে বাধা দেয়।
- Sliding Window Counter (স্কেলেবিলিটির জন্য) অ্যানোনিমাস (anonymous) ট্রাফিকের জন্য আমাদের লক্ষ লক্ষ কী (key) থাকে। আমরা প্রতিটি টাইমস্ট্যাম্প সংরক্ষণ করতে পারি না। পরিবর্তে, আমরা এমন একটি কাউন্টার ব্যবহার করি যা বর্তমান এবং পূর্ববর্তী উইন্ডোর গুরুত্ব (weight) বিবেচনা করে। এটি অনেক কম মেমরি ব্যবহার করে এবং অত্যন্ত দক্ষ।
প্রোডাকশন থেকে প্রাপ্ত মূল শিক্ষা:
- Lua স্ক্রিপ্ট ব্যবহার করুন। এটি নিশ্চিত করে যে আপনার চেক এবং রাইট অপারেশন একটি একক কাজ হিসেবে সম্পন্ন হয়।
- স্ট্যান্ডার্ড হেডার (standard headers) প্রদান করুন। সর্বদা
X-RateLimit-RemainingএবংRetry-Afterপাঠান। এটি সুশৃঙ্খল ক্লায়েন্টদের স্বয়ংক্রিয়ভাবে বিরতি নিতে সাহায্য করে। - Fail open পদ্ধতি অনুসরণ করুন। যদি Redis ডাউন হয়ে যায়, তবে রিকোয়েস্টটি গ্রহণ করুন। একটি ওভারলোডেড API সম্পূর্ণ অচল API-এর চেয়ে ভালো।
- ক্লায়েন্টদের সঠিকভাবে শনাক্ত করুন। ব্যবহারকারীদের জন্য API key এবং গেস্টদের জন্য হ্যাশ করা (hashed) IP ব্যবহার করুন।
- এন্ডপয়েন্টের সাথে লিমিট সামঞ্জস্যপূর্ণ করুন। একটি ভারী সার্চ এন্ডপয়েন্টের জন্য সাধারণ লিস্ট এন্ডপয়েন্টের তুলনায় আরও কঠোর লিমিটের প্রয়োজন।
ফলাফল ছিল তাৎক্ষণিক। সন্ধ্যার সেই স্পাইকগুলো (spikes) অদৃশ্য হয়ে গেল। আমাদের ডাটাবেসের পারফরম্যান্স স্বাভাবিক অবস্থায় ফিরে এল।
