• Binbin's avatar
    Lua eval scripts first in first out LRU eviction (#13108) · ad28d222
    Binbin authored
    In some cases, users will abuse lua eval. Each EVAL call generates
    a new lua script, which is added to the lua interpreter and cached
    to redis-server, consuming a large amount of memory over time.
    
    Since EVAL is mostly the one that abuses the lua cache, and these
    won't have pipeline issues (i.e. the script won't disappear
    unexpectedly,
    and cause errors like it would with SCRIPT LOAD and EVALSHA),
    we implement a plain FIFO LRU eviction only for these (not for
    scripts loaded with SCRIPT LOAD).
    
    ### Implementation notes:
    When not abused we'll probably have less than 100 scripts, and when
    abused we'll have many thousands. So we use a hard coded value of 500
    scripts. And considering that we don't have many scripts, then unlike
    keys, we don't need to worry about the memory usage of keeping a true
    sorted LRU linked list. We compute the SHA of each script anyway,
    and put the script in a dict, we can store a listNode there, and use
    it for quick removal and re-insertion into an LRU list each time the
    script is used.
    
    ### New interfaces:
    At the same time, a new `evicted_scripts` field is added to
    INFO, which represents the number of evicted eval scripts. Users
    can check it to see if they are abusing EVAL.
    
    ### benchmark:
    `./src/redis-benchmark -P 10 -n 1000000 -r 10000000000 eval "return
    __rand_int__" 0`
    
    The simple abuse of eval benchmark test that will create 1 million EVAL
    scripts. The performance has been improved by 50%, and the max latency
    has dropped from 500ms to 13ms (this may be caused by table expansion
    inside Lua when the number of scripts is large). And in the INFO memory,
    it used to consume 120MB (server cache) + 310MB (lua engine), but now
    it only consumes 70KB (server cache) + 210KB (lua_engine) because of
    the scripts eviction.
    
    For non-abusive case of about 100 EVAL scripts, there's no noticeable
    change in performance or memory usage.
    
    ### unlikely potentially breaking change:
    in theory, a user can maybe load a
    script with EVAL and then use EVALSHA to call it (by calculating the
    SHA1 value on the client side), it could be that if we read the docs
    carefully we'll realized it's a valid scenario, but we suppose it's
    extremely rare. So it may happen that EVALSHA acts on a script created
    by EVAL, and the script is evicted and EVALSHA returns a NOSCRIPT error.
    that is if you have more than 500 scripts being used in the same
    transaction / pipeline.
    
    This solves the second point in #13102.
    ad28d222
server.h 180 KB