Redis-ൽ ഒരു സ്ലൈഡിംഗ് വിൻഡോ റേറ്റ് ലിമിറ്റർ (Sliding Window Rate Limiter) നിർമ്മിക്കുന്നു
കഴിഞ്ഞ പാദത്തിൽ രണ്ട് തവണ രാവിലെ 9:00 മണിക്ക് മുമ്പേ ഞങ്ങളുടെ YouTube API ക്വാട്ട പൂജ്യമായി. ഇത് ഞങ്ങളുടെ പകുതിയോളം റീജിയനുകളിലെ ട്രെൻഡിംഗ് ഫീഡുകൾ കാലഹരണപ്പെടാൻ (stale) കാരണമായി.
പ്രശ്നം ട്രാഫിക് വർദ്ധനവല്ലായിരുന്നു. അത് ഞങ്ങളുടെ റേറ്റ് ലിമിറ്ററിലായിരുന്നു.
ഞങ്ങൾ ഒരു ഫിക്സഡ്-വിൻഡോ കൗണ്ടർ (fixed-window counter) ആണ് ഉപയോഗിച്ചിരുന്നത്. വിൻഡോയുടെ അതിർവരമ്പുകളിൽ (boundary) API കോളുകൾ വന്നാൽ, വലിയ തോതിലുള്ള രണ്ട് ബർസ്റ്റുകൾ (bursts) കടന്നുപോകാൻ ഇത് അനുവദിച്ചു. 8 റീജിയനുകളുള്ള ഒരു ഗ്ലോബൽ പൈപ്പ്ലൈനിൽ, ഈ ബൗണ്ടറി ഓവർലാപ്പ് പലപ്പോഴും സംഭവിക്കാറുണ്ടായിരുന്നു. ഇത് ഞങ്ങളുടെ കൃത്യമായ ഡെയ്ലി ക്വാട്ടയെ ഒരു ബജറ്റ് ചോർച്ചയാക്കി മാറ്റി.
ഞാൻ ഫിക്സഡ് വിൻഡോയ്ക്ക് പകരം Redis sorted sets ഉപയോഗിച്ചുള്ള ഒരു സ്ലൈഡിംഗ് വിൻഡോ ലോഗ് (sliding window log) നടപ്പിലാക്കി.
ഇത് എങ്ങനെയാണ് പ്രവർത്തിക്കുന്നത് എന്ന് നോക്കാം:
- ഒരു ടൈംസ്റ്റാമ്പ് ഉപയോഗിച്ച് റിക്വസ്റ്റ് രേഖപ്പെടുത്താൻ
ZADDഉപയോഗിക്കുക. - വിൻഡോയ്ക്ക് പുറത്തുള്ള പഴയ എൻട്രികൾ നീക്കം ചെയ്യാൻ
ZREMRANGEBYSCOREഉപയോഗിക്കുക. - വിൻഡോയിൽ എത്ര റിക്വസ്റ്റുകൾ ബാക്കിയുണ്ടെന്ന് കൃത്യമായി കണക്കാക്കാൻ
ZCARDഉപയോഗിക്കുക. - ഉപയോഗിക്കാത്ത (idle) കീകൾ സ്വയമേവ നീക്കം ചെയ്യാൻ
PEXPIREഉപയോഗിക്കുക.
ഈ രീതി വളരെ കൃത്യമാണ്. അതിർവരമ്പുകൾ മറികടക്കാൻ ഇതിൽ സാധ്യതയില്ല.
ഞാൻ ഇത് Redis-ൽ ഒരു Lua സ്ക്രിപ്റ്റ് ഉപയോഗിച്ചാണ് നടപ്പിലാക്കിയത്. ഇത് പരിശോധനയും (check) റെക്കോർഡിംഗും (record) ഒരേസമയം അറ്റോമിക് (atomic) ആയി നടക്കുന്നുവെന്ന് ഉറപ്പാക്കുന്നു. നിങ്ങൾ ഈ ഘട്ടങ്ങൾ ആപ്ലിക്കേഷൻ കോഡിൽ നേരിട്ട് പ്രവർത്തിപ്പിക്കുകയാണെങ്കിൽ, റേസ് കണ്ടീഷനുകൾ (race conditions) കാരണം അധിക റിക്വസ്റ്റുകൾ കടന്നുപോകാൻ സാധ്യതയുണ്ട്.
പ്രൊഡക്ഷനുമായി ബന്ധപ്പെട്ട പ്രധാന സാങ്കേതിക തീരുമാനങ്ങൾ:
Redis TIMEഉപയോഗിക്കുക: നിങ്ങളുടെ ആപ്ലിക്കേഷൻ സെർവറുകളിൽ നിന്നുള്ള ടൈംസ്റ്റാമ്പുകൾ ഉപയോഗിക്കരുത്. സെർവറുകൾ തമ്മിലുള്ള ക്ലോക്ക് വ്യത്യാസം (clock skew) വിൻഡോയുടെ കൃത്യതയെ ബാധിക്കും.- വെയിറ്റഡ് കോസ്റ്റുകൾ (Weighted costs): എല്ലാ API കോളുകളും ഒരുപോലെയല്ല. ഒരു സെർച്ച് കോസ്റ്റിന് 100 യൂണിറ്റുകൾ ആവശ്യമായി വന്നേക്കാം, എന്നാൽ ഒരു വീഡിയോ ലിസ്റ്റിന് 1 യൂണിറ്റ് മതിയാകും. ഓരോ കോളിലും ഒന്നിലധികം മെമ്പറുകളെ ഉൾപ്പെടുത്തിക്കൊണ്ട് എന്റെ സ്ക്രിപ്റ്റ് ഇത് കൈകാര്യം ചെയ്യുന്നു.
- കൃത്യമായ Retry-After: സോർട്ടഡ് സെറ്റിലെ ഏറ്റവും പഴയ എൻട്രി പരിശോധിക്കുന്നതിലൂടെ, കപ്പാസിറ്റി എപ്പോൾ ലഭ്യമാകുമെന്ന് സിസ്റ്റത്തിന് കൃത്യമായി കണക്കാക്കാൻ കഴിയും.
- ഫെയിൽ-സേഫ് ലോജിക് (Fail-safe logic): ഞാൻ
EVALSHA-യും ഒരുEVALഫോളബാക്കും (fallback) ഉപയോഗിക്കുന്നു. റീസ്റ്റാർട്ട് സമയത്ത് Redis സ്ക്രിപ്റ്റ് കാഷെ ക്ലിയർ ആയാൽ പോലും, ആപ്ലിക്കേഷൻ അത് സുഗമമായി കൈകാര്യം ചെയ്യും.
ഇതിന്റെ ദോഷവശം മെമ്മറി ഉപയോഗമാണ്. ഓരോ റിക്വസ്റ്റും ഏകദേശം 100 ബൈറ്റുകൾ എടുക്കും. 10,000 യൂണിറ്റ് ഡെയ്ലി ക്വാട്ടയ്ക്ക് ഏകദേശം 1 MB മെമ്മറി മാത്രമേ ആവശ്യമുള്ളൂ. മിക്ക ഉപയോഗങ്ങൾക്കും, ഈ കൃത്യതയ്ക്കായി മെമ്മറി ചെലവാക്കുന്നത് ലാഭകരമാണ്.
ഈ മാറ്റത്തിന് ശേഷം, ഞങ്ങളുടെ ക്വാട്ട ഒരു തവണ പോലും തീർന്നുപോയിട്ടില്ല. 403 എററുകൾക്ക് പകരം ഞങ്ങളുടെ ജോബുകൾ കൃത്യമായി അവസാനിക്കുന്നു.
നിങ്ങളുടെ റേറ്റ് ലിമിറ്റർ ക്ലോക്കിലെ കൃത്യമായ സമയങ്ങളിൽ (round number) റീസെറ്റ് ആകുന്നുണ്ടെങ്കിൽ, നിങ്ങൾക്ക് ഒരു പരിധിയുമില്ല എന്നാണ് അർത്ഥം. നിങ്ങൾക്ക് ഒരു ലൂപ്പ്ഹോൾ (loophole) ആണ് ഉള്ളത്.