-
Notifications
You must be signed in to change notification settings - Fork 24.4k
memory used by Lua is allocated and freed by zrealloc()/zfree() #7814
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
Conversation
|
@Axlgrep thanks for this PR.
There was at least one previous attempt to handle one of these problems: My problem with including this memory in used_memory is that this is not "evitable" memory, so when Lua scripts eat memory, the right thing to do is force them to stop or do GC, evicting keys won't help (will not prevent a malicious script from eating all the memory on the machine). on the other hand we can't just cause a running script to stop (specifically if it already performed some write commands), this will break the atomic nature of the script and leave the server in a bad sate (maybe the only thing we can do against such a script is I'm not sure how to proceed. If the PR was just replacing the allocator to jemalloc (bypassing zmalloc), i can easily merge it (although it does damage the effort of But about the eviction, i'm not sure... especially considering that you're not even solving the problem of a malicious script (it can keep running and eat memory and the eviction won't help, or even be triggered until the script exits). @redis/core-team please share your thoughts. |
|
@oranagra First of all, thank you for your patient answer, I previously ignored eval/evalsha/script commands without use-memory flags, which would result in these command being able to continue executing even after the maxmemory has been reached, now I've added it. I just want to control the upper limit of single Redis memory to avoid it eating all the memory on the machine. |
|
@Axlgrep sadly, you can't just mark the EVAL* commands a as This The above goes to show that a malicious script can run in a loop and eat redis (keys) memory forever. There's nothing to do once it generated its first write command except for terminating the server (killing the script would leave the partial database modifications it made and would violate atomicity). So we should decide what we want to focus on, protecting the host from malicious scripts? Or improving Lua heap handling? P.s. There's absolutely no reason to mark the SCRIPT command as use-memory (the only effect of that is to prevent SCRIPT KILL from being used) |
|
@oranagra Lua consume memory on the one hand is consume by Lua heap, on the other hand we cache Lua script in server.lua_scripts, it also consumes memory(Eval and Script Load command can lead to increased memory risk, even if read only scripts). If do not add the use-memory flag to these commands, when user loads a large number of Lua scripts with malicious intent, used_memory will not be limited, as shown in the illustration below, Script does have many subcommands, to avoid being unable to execute the Script kill command after the maxmemory is reached, I can clear the use-memory flags in Script command. however, need to do something special with the Scirpt Load command , as follows? finally, I don't have a good solution to the memory increase you mentioned due to the internal loop of the Lua script. |
|
I forgot about script cache consuming Lua heap, but i feel that this is another example of why we don't want to count the scripts as part of the normal used_memory and have them induce key eviction while the cache itself is unlimited. instead i think we need to "evict" old scripts from the script cache. IMO it may be ok to also count the Lua heap in used_memory too, so that if there are no scripts the keys can use the whole maxmemory, and if there are scripts, there's a little less memory for keys, but still the scripts should be "evictable" and have their own limit, so that Lua can't cause eviction of all keys, and at the same time be unlimited (no mechanism to remove scripts from the cache or limit the Lua heap). I feel that we may want to split this PR / discussion to several topics so that it's easier to reach a decision and a design for each. I guess these are:
The last two topics are a serious problem, but i don't see any way out except |
|
We discussed this topic in a core team meeting. This means the following:
The above are related to the script memory usage in between script execution (and it's effect on keys eviction), and should be done all at once (i.e can be several PRs, but these should make it into one redis release) besides that we also want to stop malicious scripts from eating too much memory during a single script execution.
|
|
@oranagra , A naive option is to add a threshold for the max memory usage by Lua scripts. Once the usage exceed the threshold abort creation of new scripts. As you said the main reason of not removing old scripts in some LRU fashion is to keep the contract. In order to follow the contract Redis shall abort the script creation only if the following conditions are met:
I also added configuration for enforcing the limit, in the common case we just want to alert to the user and not enforce the limit. The user can either configure the limit or monitor the usage statistics. The attached code contains:
If you find it relevant, please review the attached code, the relevant part is in scripting.c at the function luaCreateFunction. Regarding the contract, few comments from the redis documentation.
""" The reason why scripts can be cached for long time is that it is unlikely for a well written application to have enough different scripts to cause memory problems. Every script is conceptually like the implementation of a new command, and even a large application will likely have just a few hundred of them. Even if the application is modified many times and scripts will change, the memory used is negligible. """
a. Copied from https://redis.io/commands/eval """ _The fact that the user can count on Redis not removing scripts is semantically useful in the context of pipelining. For instance an application with a persistent connection to Redis can be sure that if a script was sent once it is still in memory, so EVALSHA can be used against those scripts in a pipeline without the chance of an error being generated due to an unknown script (we'll see this problem in detail later). A common pattern is to call SCRIPT LOAD to load all the scripts that will appear in a pipeline, then use EVALSHA directly inside the pipeline without any need to check for errors resulting from the script hash not being recognized._ """ b. Copied from https://redis.io/topics/pipelining """ Sometimes the application may also want to send EVAL or EVALSHA commands in a pipeline. This is entirely possible and Redis explicitly supports it with the SCRIPT LOAD command (it guarantees that EVALSHA can be called without the risk of failing). |
|
@asafpor thanks for all docs references and patch.
|
Thanks for your feedback. |
|
I also meet the same problem recently. The user abuses the script (putting the parameter format into the lua body) and consumes a lot of memory (and keeps consuming it). @oranagra From the current perspective, is there anything else that i can do? i can do some work since i have some free time and like to working on it.
since we do have a luaScript and the lua body is a robj, we can put lru in like lookupkey, and then do the same thing in evict. other: |
|
@enjoy-binbin i'm not sure how to proceed at the present time. script eviction could be a breaking change, so unless we can somehow make it safe, it'll be hard to make a decision on that. |
|
I believe we can make |
great, i can take a try (I still have the context). it look solid, thanks |
@oranagra Almost all of these cases I have seen come from misuse of |
|
interesting idea. I support that idea. p.s. the only real downside in my eyes is that users that abuse EVAL generating a new script on each call, will get away with it, and won't ever know they're doing something wrong. but that's obliviously not an argument, there are many other things you can do wrong and get away with it. |
|
good, i am willing to take a try. So we will add a configuration item similar to lua-maxmemory. Then separate the eval and script load scripts, and in evict we will only evicts eval scripts, evict will using the same maxmemory-policy (or a new lua-maxmemory-policy) |
|
i think it can be a plain LRU, i don't think the eviction policy needs a config. arguably, if the LRU eviction mechanism becomes complicated, we can even just flush all these scripts when hitting the limit (won't be a breaking change). but this could cause performance overhead if one script is repeatedly used (with EVAL), and others are just one time scripts, the re-used one would require re-compilation. |
I suppose we can always add an info field so they know. |
|
i got tired of seeing this closed issue being tracked in the projects as ToDo. created a new one with summary of the ideas left to handle #13102 |


We are tencent cloud redis team, recently some redis Instance takes up a lot of memory, but user data takes up very little,we found that most of the memory was consumed by Lua.
Currently Lua memory control does not pass through Redis’s zrealloc()\zfree(), Redis total memory cannot be effectively controlled when user maliciously uses Lua(maxmemory does not work), This will bring disaster to cloud vendors.
We want to allocate Lua’s memory through zmalloc as well, it helps to control the total memory consume of the Redis, In addition, Redis uses Jemalloc to better control the memory fragmentation rate
We loaded an RDB file with a little bit of user data, but a lot of Lua scripts:

After Lua's memory control is pass zrealloc()\zfree(), load the same RDB file, the total memory consume is reduced(due to the use of Jemalloc, the memory fragmentation rate is reduced)