Redis ਵਿੱਚ ਇੱਕ Sliding Window Rate Limiter ਬਣਾਉਣਾ
ਪਿਛਲੀ ਤਿਮਾਹੀ ਵਿੱਚ ਦੋ ਵਾਰ ਸਾਡਾ YouTube API ਕੋਟਾ ਸਵੇਰੇ 9:00 ਵਜੇ ਤੋਂ ਪਹਿਲਾਂ ਹੀ ਜ਼ੀਰੋ ਹੋ ਗਿਆ ਸੀ। ਇਸ ਕਾਰਨ ਸਾਡੇ ਅੱਧੇ ਖੇਤਰਾਂ (regions) ਲਈ ਸਾਡੀਆਂ ਟ੍ਰੈਂਡਿੰਗ ਫੀਡਾਂ ਪੁਰਾਣੀਆਂ (stale) ਰਹਿ ਗਈਆਂ।
ਸਮੱਸਿਆ ਟ੍ਰੈਫਿਕ ਵਿੱਚ ਵਾਧਾ ਨਹੀਂ ਸੀ। ਸਮੱਸਿਆ ਸਾਡਾ rate limiter ਸੀ।
ਅਸੀਂ ਇੱਕ fixed-window counter ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੇ ਸੀ। ਇਸ ਕਾਰਨ ਜੇਕਰ API ਕਾਲਾਂ ਦੋ ਵੱਡੇ ਬਰਸਟ (bursts) ਵਜੋਂ ਵਿੰਡੋ ਦੀ ਸੀਮਾ (boundary) 'ਤੇ ਆਉਂਦੀਆਂ ਸਨ, ਤਾਂ ਉਹ ਲੰਘ ਜਾਂਦੀਆਂ ਸਨ। 8 ਖੇਤਰਾਂ ਵਾਲੇ ਗਲੋਬਲ ਪਾਈਪਲਾਈਨ ਲਈ, ਇਹ boundary overlap ਅਕਸਰ ਹੁੰਦਾ ਸੀ। ਇਸ ਨੇ ਸਾਡੇ ਸਖ਼ਤ ਰੋਜ਼ਾਨਾ ਕੋਟਾ ਨੂੰ ਬਜਟ ਲੀਕ ਵਿੱਚ ਬਦਲ ਦਿੱਤਾ।
ਮੈਂ fixed window ਨੂੰ Redis sorted sets ਦੀ ਵਰਤੋਂ ਕਰਕੇ sliding window log ਨਾਲ ਬਦਲ ਦਿੱਤਾ।
ਇਹ ਇਸ ਤਰ੍ਹਾਂ ਕੰਮ ਕਰਦਾ ਹੈ:
- ਇੱਕ timestamp ਦੇ ਨਾਲ ਰਿਕਵੈਸਟ ਨੂੰ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ZADD ਦੀ ਵਰਤੋਂ ਕਰੋ।
- ਵਿੰਡੋ ਤੋਂ ਬਾਹਰ ਦੀਆਂ ਪੁਰਾਣੀਆਂ ਐਂਟਰੀਆਂ ਨੂੰ ਹਟਾਉਣ ਲਈ ZREMRANGEBYSCORE ਦੀ ਵਰਤੋਂ ਕਰੋ।
- ਵਿੰਡੋ ਵਿੱਚ ਬਿਲਕੁਲ ਕਿੰਨੀਆਂ ਰਿਕਵੈਸਟਾਂ ਬਾਕੀ ਹਨ, ਇਹ ਗਿਣਨ ਲਈ ZCARD ਦੀ ਵਰਤੋਂ ਕਰੋ।
- idle keys ਨੂੰ ਆਪਣੇ ਆਪ ਸਾਫ਼ ਕਰਨ ਲਈ PEXPIRE ਦੀ ਵਰਤੋਂ ਕਰੋ।
ਇਹ ਤਰੀਕਾ ਬਿਲਕੁਲ ਸਹੀ ਹੈ। ਇਸ ਵਿੱਚ ਕੋਈ ਅਜਿਹੀ ਸੀਮਾ (boundary) ਨਹੀਂ ਹੈ ਜਿਸ ਨੂੰ ਪਾਰ ਕਰਨਾ ਮੁਸ਼ਕਲ ਹੋਵੇ।
ਮੈਂ ਇਸ ਨੂੰ Redis ਵਿੱਚ ਇੱਕ Lua script ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਲਾਗੂ ਕੀਤਾ ਹੈ। ਇਹ ਯਕੀਨੀ ਬਣਾਉਂਦਾ ਹੈ ਕਿ ਪੂਰੀ ਚੈੱਕ ਅਤੇ ਰਿਕਾਰਡ ਪ੍ਰਕਿਰਿਆ atomic ਹੋਵੇ। ਜੇਕਰ ਤੁਸੀਂ ਇਹਨਾਂ ਸਟੈਪਸ ਨੂੰ application code ਵਿੱਚ ਚਲਾਉਂਦੇ ਹੋ, ਤਾਂ race conditions ਵਾਧੂ ਰਿਕਵੈਸਟਾਂ ਨੂੰ ਲੰਘਣ ਦੇਣਗੀਆਂ।
Production ਲਈ ਮੁੱਖ ਤਕਨੀਕੀ ਫੈਸਲੇ:
- Redis TIME ਦੀ ਵਰਤੋਂ ਕਰੋ: ਆਪਣੇ application servers ਤੋਂ timestamps ਦੀ ਵਰਤੋਂ ਨਾ ਕਰੋ। ਸਰਵਰਾਂ ਵਿਚਕਾਰ clock skew ਵਿੰਡੋ ਦੀ ਸ਼ੁੱਧਤਾ ਨੂੰ ਖਰਾਬ ਕਰ ਦਿੰਦਾ ਹੈ।
- Weighted costs: ਸਾਰੀਆਂ API ਕਾਲਾਂ ਇੱਕੋ ਜਿਹੀਆਂ ਨਹੀਂ ਹੁੰਦੀਆਂ। ਇੱਕ search call ਦੀ ਕੀਮਤ 100 ਯੂਨਿਟ ਹੋ ਸਕਦੀ ਹੈ ਜਦੋਂ ਕਿ ਇੱਕ video list ਦੀ ਕੀਮਤ 1 ਹੋ ਸਕਦੀ ਹੈ। ਮੇਰੀ script ਹਰ ਕਾਲ ਲਈ ਕਈ ਮੈਂਬਰਾਂ ਨੂੰ ਇਨਸਰਟ ਕਰਕੇ ਇਸ ਨੂੰ ਸੰਭਾਲਦੀ ਹੈ।
- Precise Retry-After: sorted set ਵਿੱਚ ਸਭ ਤੋਂ ਪੁਰਾਣੀ ਐਂਟਰੀ ਨੂੰ ਦੇਖ ਕੇ, ਸਿਸਟਮ ਬਿਲਕੁਲ ਸਹੀ ਗਣਨਾ ਕਰਦਾ ਹੈ ਕਿ ਸਮਰੱਥਾ (capacity) ਕਦੋਂ ਖਾਲੀ ਹੋਵੇਗੀ।
- Fail-safe logic: ਮੈਂ EVAL fallback ਦੇ ਨਾਲ EVALSHA ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹਾਂ। ਜੇਕਰ ਰੀਸਟਾਰਟ ਦੌਰਾਨ Redis script cache ਸਾਫ਼ ਹੋ ਜਾਂਦਾ ਹੈ, ਤਾਂ application ਇਸ ਨੂੰ ਸੁਚਾਰੂ ਰੂਪ ਵਿੱਚ ਸੰਭਾਲ ਲੈਂਦੀ ਹੈ।
ਇਸ ਦਾ ਨੁਕਸਾਨ (trade-off) ਮੈਮੋਰੀ ਹੈ। ਹਰ ਰਿਕਵੈਸਟ ਲਗਭਗ 100 bytes ਲੈਂਦੀ ਹੈ। 10,000 ਯੂਨਿਟ ਦੇ ਰੋਜ਼ਾਨਾ ਕੋਟਾ ਲਈ, ਇਹ ਸਿਰਫ਼ ਲਗਭਗ 1 MB ਮੈਮੋਰੀ ਹੈ। ਜ਼ਿਆਦਾਤਰ ਵਰਤੋਂ ਦੇ ਮਾਮਲਿਆਂ (use cases) ਲਈ, ਇਹ ਸ਼ੁੱਧਤਾ ਇਸਦੀ ਕੀਮਤ ਦੇ ਬਰਾਬਰ ਹੈ।
ਇਸ ਬਦਲਾਅ ਤੋਂ ਬਾਅਦ, ਸਾਡਾ ਕੋਟਾ ਇੱਕ ਵਾਰ ਵੀ ਖਤਮ ਨਹੀਂ ਹੋਇਆ ਹੈ। ਸਾਡੇ jobs 403 errors ਆਉਣ ਦੀ ਬਜਾਏ ਸਾਫ਼ ਤਰੀਕੇ ਨਾਲ ਰੁਕ ਜਾਂਦੇ ਹਨ।
ਜੇਕਰ ਤੁਹਾਡਾ rate limiter ਘੜੀ ਦੇ ਕਿਸੇ ਪੂਰੇ ਅੰਕ (round number) 'ਤੇ ਰੀਸੈੱਟ ਹੁੰਦਾ ਹੈ, ਤਾਂ ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਸੀਮਾ (limit) ਨਹੀਂ ਹੈ। ਤੁਹਾਡੇ ਕੋਲ ਇੱਕ loophole ਹੈ।