Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle redis NOSCRIPT errors #182

Open
lewisfzhang opened this issue Aug 12, 2024 · 1 comment
Open

Handle redis NOSCRIPT errors #182

lewisfzhang opened this issue Aug 12, 2024 · 1 comment

Comments

@lewisfzhang
Copy link

Background: Rate limiter library throwing redis.exceptions.NoScriptError: No matching script. Please use EVAL during Redis failover

This is likely due to use of Redis scripts for the standard implementation of Redis bucket puts

Per Redis protocol, Redis script cache is always volatile. It isn't considered as a part of the database and is not persisted. The cache may be cleared when the server restarts, during fail-over when a replica assumes the master role. That means that cached scripts are ephemeral, and the cache's contents can be lost at any time.

In case of getting this error on runtime, the application should first re-load it with SCRIPT LOAD and then call EVALSHA once more to run the cached script by its SHA1 sum. Most of Redis' clients already provide utility APIs for doing that automatically. Please see register_script method and scripting docs for redis-py library.

Another alternative is you can use the SCRIPT EXISTS command first to see if a given SHA-1 digest represents a cached script.

https://redis.io/docs/latest/develop/interact/programmability/eval-intro/
https://redis.io/blog/bullet-proofing-lua-scripts-in-redispy/

@taobojlen
Copy link

This caused an incident for us -- our servers broke when Redis restarted.

To work around it in our Django middleware, we did something like

from redis.exceptions import NoScriptError

class RateLimitMiddleware:
    def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
        self.get_response = get_response
        second_rate = Rate(10, Duration.SECOND)
        pool = "redis://something"
        redis_db = Redis(connection_pool=pool)
        bucket = RedisBucket.init([second_rate], redis_db, "rate_limit")
        clock = MonotonicClock()
        bucket_factory = SingleBucketFactory(bucket, clock)

        self.limiter = Limiter(bucket_factory)
        
    def __call__(self, request: HttpRequest):
        try:
            self.limiter.try_acquire(identity, weight=weight)
        except NoScriptError:
            self.__init__(self.get_response)
            self.limiter.try_acquire(identity, weight=weight)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants