How I Built A Sliding Window Rate Limiter in Redis
અમારી વિડિયો API દરરોજ સાંજે 8pm UTC વાગ્યે ક્રેશ થઈ જતી હતી.
તે વાસ્તવિક ટ્રાફિક નહોતો. કેટલાક સ્ક્રૅપર્સ (scrapers) એ અમારા ટ્રેન્ડિંગ એન્ડપોઇન્ટને શોધી કાઢ્યું અને તેના પર સતત હુમલો કર્યો. અમારી ડેટાબેઝ ક્વેરીઝનો પહાડ ઊભો થઈ ગયો. વાસ્તવિક વપરાશકર્તાઓને વિડિયોને બદલે લોડિંગ સ્પિનર્સ જોવા મળતા હતા.
અમને રેટ લિમિટિંગની જરૂર હતી. મોટાભાગની સરળ પદ્ધતિઓએ સમસ્યાને વધુ વણસી દીધી.
The problem with Fixed Window counters: મોટાભાગના લોકો ફિક્સ્ડ વિન્ડો (fixed windows) થી શરૂઆત કરે છે. તમે એક નિશ્ચિત સમયના બ્લોકમાં વિનંતીઓ (requests) ગણો છો. જો તમારી મર્યાદા પ્રતિ મિનિટ 100 હોય, તો વપરાશકર્તા 11:59:59 પર 100 વિનંતીઓ અને 12:00:00 પર બીજી 100 વિનંતીઓ મોકલી શકે છે. એટલે કે એક સેકન્ડમાં 200 વિનંતીઓ. સ્ક્રૅપર્સ તમારી મર્યાદાઓને બાયપાસ કરવા માટે આ સીમાઓનો (boundaries) ઉપયોગ કરે છે.
The solution: Sliding Window. સ્લાઇડિંગ વિન્ડો અત્યારના સમયના સંદર્ભમાં છેલ્લા N સેકન્ડની વિનંતીઓ ગણે છે. અહીં ઉપયોગ કરવા માટે કોઈ સીમા (boundary) હોતી નથી.
અમે અમારી જરૂરિયાતોના આધારે બે અલગ-અલગ પદ્ધતિઓનો ઉપયોગ કરીએ છીએ:
- Sliding Window Log (ચોકસાઈ માટે) અમે પ્રમાણિત (authenticated) વપરાશકર્તાઓ માટે Redis Sorted Sets (ZSET) નો ઉપયોગ કરીએ છીએ. અમે દરેક વિનંતી માટે ટાઇમસ્ટેમ્પ સ્ટોર કરીએ છીએ.
- અમે જૂની એન્ટ્રીઓ કાઢી નાખવા માટે
ZREMRANGEBYSCOREનો ઉપયોગ કરીએ છીએ. - અમે વર્તમાન વિનંતીઓ ગણવા માટે
ZCARDનો ઉપયોગ કરીએ છીએ. - અમે નવી વિનંતીઓ નોંધવા માટે
ZADDનો ઉપયોગ કરીએ છીએ.
રેસ કંડિશન (race conditions) રોકવા માટે, અમે આ લોજિક એક સિંગલ Lua સ્ક્રિપ્ટમાં ચલાવીએ છીએ. આ ઓપરેશનને એટમિક (atomic) બનાવે છે. એક જ સ્ક્રિપ્ટ Redis સુધીના એક જ રાઉન્ડમાં બધું સંભાળી લે છે. આનાથી બે વિનંતીઓ એકસાથે એવું ન વિચારી શકે કે તેઓ મર્યાદાની અંદર છે.
- Sliding Window Counter (સ્કેલ માટે) અનામી (anonymous) ટ્રાફિક માટે, અમારી પાસે લાખો કી (keys) છે. અમે દરેક ટાઇમસ્ટેમ્પ સ્ટોર કરી શકતા નથી. તેના બદલે, અમે એક કાઉન્ટરનો ઉપયોગ કરીએ છીએ જે વર્તમાન અને અગાઉના વિન્ડોને વજન (weight) આપે છે. તે ખૂબ ઓછી મેમરી વાપરે છે અને અત્યંત કાર્યક્ષમ છે.
Key lessons from production:
- Lua સ્ક્રિપ્ટ્સનો ઉપયોગ કરો. આ સુનિશ્ચિત કરે છે કે તમારી ચેક અને રાઈટ પ્રક્રિયા એક જ ક્રિયા તરીકે થાય.
- સ્ટાન્ડર્ડ હેડર્સ મોકલો. હંમેશા
X-RateLimit-RemainingઅનેRetry-Afterમોકલો. આનાથી વ્યવસ્થિત ક્લાયન્ટ્સ આપમેળે પીછેહઠ કરવામાં મદદ મળે છે. - ફેલ ઓપન (Fail open) રાખો. જો Redis બંધ થઈ જાય, તો વિનંતીને મંજૂરી આપો. સંપૂર્ણ રીતે બંધ API કરતા ઓવરલોડ થયેલ API વધુ સારો છે.
- ક્લાયન્ટ્સને યોગ્ય રીતે ઓળખો. વપરાશકર્તાઓ માટે API કી અને ગેસ્ટ માટે હેશ્ડ (hashed) IPs નો ઉપયોગ કરો.
- મર્યાદાઓને એન્ડપોઇન્ટ્સ સાથે મેળવો. એક ભારે સર્ચ એન્ડપોઇન્ટને સાદા લિસ્ટ એન્ડપોઇન્ટ કરતા વધુ કડક મર્યાદાની જરૂર હોય છે.
પરિણામ તાત્કાલિક મળ્યું. સાંજનો ટ્રાફિક સ્પાઇક (spike) અદૃશ્ય થઈ ગયો. અમારી ડેટાબેઝ પરફોર્મન્સ સામાન્ય થઈ ગઈ.
