मैंने Redis में Sliding Window Rate Limiter कैसे बनाया
हमारी वीडियो API हर शाम 8pm UTC पर क्रैश हो जाती थी।
यह असली ट्रैफिक नहीं था। कुछ स्क्रैपर्स (scrapers) ने हमारे ट्रेंडिंग एंडपॉइंट (trending endpoint) को ढूंढ लिया और उस पर भारी मात्रा में रिक्वेस्ट भेजने लगे। हमारी डेटाबेस क्वेरीज़ का ढेर लग गया। असली यूजर्स को वीडियो के बजाय लोडिंग स्पिनर्स दिखाई देने लगे।
हमें रेट लिमिटिंग (rate limiting) की ज़रूरत थी। ज़्यादातर सरल तरीके समस्या को और भी खराब कर रहे थे।
Fixed Window काउंटर्स के साथ समस्या:
ज़्यादातर लोग fixed windows से शुरुआत करते हैं। आप एक निश्चित समय ब्लॉक में रिक्वेस्ट को गिनते हैं। यदि आपकी सीमा प्रति मिनट 100 है, तो एक यूजर 11:59:59 पर 100 रिक्वेस्ट भेज सकता है और 12:00:00 पर अन्य 100 रिक्वेस्ट भेज सकता है। इसका मतलब है कि एक सेकंड में 200 रिक्वेस्ट। स्क्रैपर्स आपकी सीमाओं को बायपास करने के लिए इन सीमाओं (boundaries) का फायदा उठाते हैं।
समाधान: Sliding Window।
एक sliding window अभी के समय के सापेक्ष पिछले N सेकंड की रिक्वेस्ट को गिनता है। इसमें फायदा उठाने के लिए कोई सीमा (boundary) नहीं होती।
हम अपनी ज़रूरतों के आधार पर दो अलग-अलग तरीकों का उपयोग करते हैं:
1. Sliding Window Log (सटीकता के लिए)
हम ऑथेंटिकेटेड (authenticated) यूजर्स के लिए Redis Sorted Sets (ZSET) का उपयोग करते हैं। हम हर रिक्वेस्ट के लिए एक टाइमस्टैम्प (timestamp) स्टोर करते हैं।
- हम पुरानी एंट्रीज़ को हटाने के लिए
ZREMRANGEBYSCOREका उपयोग करते हैं। - हम वर्तमान रिक्वेस्ट को गिनने के लिए
ZCARDका उपयोग करते हैं। - हम नई रिक्वेस्ट को रिकॉर्ड करने के लिए
ZADDका उपयोग करते हैं।
Race conditions को रोकने के लिए, हम इस लॉजिक को एक सिंगल Lua script में चलाते हैं। यह ऑपरेशन को atomic बनाता है। एक ही स्क्रिप्ट Redis के साथ एक ही ट्रिप में सब कुछ संभाल लेती है। यह दो रिक्वेस्ट को एक ही समय में यह सोचने से रोकता है कि वे लिमिट के अंदर हैं।
2. Sliding Window Counter (स्केल के लिए)
Anonymous ट्रैफिक के लिए, हमारे पास लाखों कीज़ (keys) होती हैं। हम हर टाइमस्टैम्प स्टोर नहीं कर सकते। इसके बजाय, हम एक काउंटर का उपयोग करते हैं जो वर्तमान और पिछले विंडो को वेटेज (weight) देता है। यह बहुत कम मेमोरी का उपयोग करता है और अत्यधिक कुशल (efficient) है।
प्रोडक्शन से मुख्य सबक:
- Lua scripts का उपयोग करें। यह सुनिश्चित करता है कि आपकी जांच (check) और आपका राइट (write) एक ही सिंगल एक्शन के रूप में हो।
- स्टैंडर्ड हेडर्स (headers) भेजें। हमेशा
X-RateLimit-RemainingऔरRetry-Afterभेजें। यह अच्छे व्यवहार वाले क्लाइंट्स को स्वचालित रूप से पीछे हटने (back off) में मदद करता है। - Fail open रखें। यदि Redis डाउन हो जाता है, तो रिक्वेस्ट को अनुमति दें। एक ओवरलोडेड API, पूरी तरह से बंद API से बेहतर है।
- क्लाइंट्स की सही पहचान करें। यूजर्स के लिए API keys और गेस्ट्स के लिए hashed IPs का उपयोग करें।
- लिमिट्स को एंडपॉइंट्स के अनुसार सेट करें। एक भारी सर्च एंडपॉइंट को एक साधारण लिस्ट एंडपॉइंट की तुलना में अधिक सख्त लिमिट्स की आवश्यकता होती है।
परिणाम तुरंत मिले। शाम के समय होने वाले स्पाइक्स (spikes) गायब हो गए। हमारे डेटाबेस का प्रदर्शन सामान्य हो गया।
