Skip to content

Conversation

@MeirShpilraien
Copy link

@MeirShpilraien MeirShpilraien commented Jan 6, 2022

Redis Functions Flags

Following the discussion on #10025 Added Functions Flags support. The PR is divided to 2 sections:

  • Add named argument support to redis.register_function API.
  • Add support for function flags

redis.register_function named argument support

The first part of the PR adds support for named argument on redis.register_function, example:

redis.register_function{
    function_name='f1',
    callback=function()
        return 'hello'
    end,
    description='some desc'
}

The positional arguments is also kept, which means that it still possible to write:

redis.register_function('f1', function() return 'hello' end)

But notice that it is no longer possible to pass the optional description argument on the positional argument version. Positional argument was change to allow passing only the mandatory arguments (function name and callback). To pass more arguments the user must use the named argument version.

As with positional arguments, the function_name and callback is mandatory and an error will be raise if those are missing. Also, an error will be raise if an unknown argument name is given or the arguments type is wrong.

Tests was added to verify the new syntax.

Functions Flags

The second part of the PR is adding functions flags support. Flags are given to Redis when the engine calls functionLibCreateFunction, supported flags are:

  • no-writes - indicating the function perform no writes which means that it is OK to run it on:

    • read-only replica
    • Using FCALL_RO
    • If disk error detected

    It will not be possible to run a function in those situations unless the function turns on the no-writes flag

  • allow-oom - indicate that its OK to run the function even if Redis is in OOM state, if the function will not turn on this flag it will not be possible to run it if OOM reached. If this flag is set, any command will be allow on OOM (even those that is marked with CMD_DENYOOM). The assumption is that this flag is for advance users that knows its meaning and understand what they are doing, and Redis trust them to not increase the memory usage. (e.g. it could be an INCR or a modification on an existing key, or a DEL command)
    Note: starting with redis 7.0.1, the no-writes flag also implies allow-oom, so read-only scripts (including ones used by FCALL_RO and EVAL_RO) can run in OOM state without explicitly specifying this flag. see Scripts that declare the no-writes flag are implicitly allow-oom too. #10699

  • allow-state - indicate that its OK to run the function on stale replica, in this case we will also make sure the function is only perform stale commands and raise an error if not.

  • no-cluster - indicate to disallow running the function if cluster is enabled.

Default behaviure of functions (if no flags is given):

  1. Allow functions to read and write
  2. Do not run functions on OOM
  3. Do not run functions on stale replica
  4. Allow functions on cluster

Lua API for functions flags

On Lua engine, it is possible to give functions flags as flags named argument:

redis.register_function{function_name='f1', callback=function() return 1 end, flags={'no-writes', 'allow-oom'}, description='description'}

The function flags argument must be a Lua table that contains all the requested flags, The following will result in an error:

  • Unknown flag
  • Wrong flag type

Default behaviour is the same as if no flags are used.

Tests were added to verify all flags functionality

Additional changes

  • mark FCALL and FCALL_RO with CMD_STALE flag (unlike EVAL), so that they can run if the function was registered with the allow-stale flag.
  • Verify CMD_STALE on scriptCall (redis.call), so it will not be possible to call commands from script while stale unless the command is marked with the CMD_STALE flags. so that even if the function is allowed while stale we do not allow it to bypass the CMD_STALE flag of commands.
  • Flags section was added to FUNCTION LIST command to provide the set of flags for each function:
> FUNCTION list withcode
1)  1) "library_name"
    2) "test"
    3) "engine"
    4) "LUA"
    5) "description"
    6) (nil)
    7) "functions"
    8) 1) 1) "name"
          2) "f1"
          3) "description"
          4) (nil)
          5) "flags"
          6) (empty array)
    9) "library_code"
   10) "redis.register_function{function_name='f1', callback=function() return 1 end}"
  • Added API to get Redis version from within a script, The redis version can be provided using:

    1. redis.REDIS_VERSION - string representation of the redis version in the format of MAJOR.MINOR.PATH
    2. redis.REDIS_VERSION_NUM - number representation of the redis version in the format of 0x00MMmmpp (MM - major, mm - minor, pp - patch). The number version can be used to check if version is greater or less another version. The string version can be used to return to the user or print as logs.

    This new API is provided to eval scripts and functions, it also possible to use this API during functions loading phase.

Considerations

  • After discussion with @oranagra, @yossigo and @guybe7 we decided User experience should be smooth even at the cost of not matching functions flags to modules and commands flags. This is why we see different flags for functions on this PR.

todo:

  • Decide if we want to allow EVAL\_RO, EVALSHA\_RO to be allowed while stale and in worst case, if the script performs a command which is not allowed while stale, an error will be raised.
    • An argument against it is that scripts that publish some data and then read some data from the key space will fail when trying to read data and after already performed the publish command. This can be considered as breaking change and maybe even break atomicity?
    • An argument in favor is that we already have such issue with write commands.
  • Add functions flags to function list
  • redis.version API

Following the discussion on redis#10025
Added the option to specify the parameters to `redis.register_function` as
named arguments, example:
```
redis.register_function{
    function_name='f1',
    callback=function()
        return 'hello'
    end,
    description='some desc'
}
```

The possisional arguments is also kept, which means that it still possible
to write the same example like this:
```
redis.register_function('f1', function() return 'hello' end, 'some desc')
```

As with positional arguments, the `function_name` and `callback` is mandatory
and an error will be raise if those are missing. Also, an error will be raise
if an unknown argument name is given or the arguments type is wrong.

Tests was added to verify the new syntax.
@MeirShpilraien MeirShpilraien added 7.0-must-have approval-needed Waiting for core team approval to be merged labels Jan 6, 2022
meir added 2 commits January 9, 2022 11:59
Use a struct to hold register_function arguments instead of adding
each argument as out parameter by itself. This will allow us to add
more named arguments easily in the future.
Following the discussion on redis#10025
added functions flags support. Flags are give to Redis when the engine
calls `functionLibCreateFunction`, supported flags are:

* write - the function is performing write operation on the key space
  do not allow executing the function on the following conditions:
    * On readonly replica
    * Using fcall_ro
    * If disc error detected and
  In addition, if this flag is not set, any attempt to execute a write
  command from within the function will result in an error.
* deny-oom - any attempt to execute the function will result in an error
  if OOM is reached.
* allow-stale - indicate that it is ok to run this function on stale replica
* no-cluster - any attempt to execute the function will result in an error
  on cluster enabled server.

## Lua API

On Lua engine, it is possible to give functions flags in 2 ways:
* Third argument to positional arguments (notice that the description
  arugment moved to fourth position compared to unstable):

```
redis.register_function('f1', function() return 1 end, {'write', 'deny-oom'})
```

* using `flags` named argument:

```
redis.register_function{function_name='f1', callback=function() return 1 end, flags={'write', 'deny-oom'}}
```

The following will result in an error:
* Unknown flag
* Wrong flag type

Default behaviure is the same as if no flags are used.

Tests were added to verify all flags functionality
@MeirShpilraien MeirShpilraien changed the title Function libraries named arguments Function Flags support Jan 9, 2022
meir added 3 commits January 9, 2022 22:14
Verify that we are not executing commands on stale replica inside
a scripts unless the command is marked with CMD_STALE flag.

This verification allows us to set CMD_STALE on EVAL, EVALSHA,
EVAL_RO and EVALSHA_RO and in the wrost case, if the script will
try to perform a command which is not allow while stale, an error
will be raise.

On functions, we also enforce this stale verification. The difference
from eval is that function can leverage functions flags to allow earily
exit on stale state.
1. Better error messages
2. Indentation fixes
@oranagra oranagra added release-notes indication that this issue needs to be mentioned in the release notes state:major-decision Requires core team consensus labels Jan 10, 2022
@oranagra
Copy link
Member

@redis/core-team please approve or comment

@oranagra oranagra linked an issue Jan 10, 2022 that may be closed by this pull request
@oranagra
Copy link
Member

This was discussed in a core-team meeting and got an approval to be merge.
@MeirShpilraien @yoav-steinberg is this ready? any reason to wait and not merge it?
@yossigo i'd like at least one additional set of eyes on this, at least on the top comment. please ack.

@MeirShpilraien
Copy link
Author

If we agree on the last open conversations I do not see a reason why not to merge it.

@yossigo
Copy link
Collaborator

yossigo commented Jan 12, 2022

@MeirShpilraien @oranagra I have one concern about usability here. This means that as a function developer, I need to learn about flags and always explicitly specify them.

Ideally I think functions should be write and deny-oom by default, with the option to read-only (negate the write) and allow-oom (negate deny-oom).

Since flags become optional, this also implies they need to be an optional last argument (swap with description) or possibly even available only in the named form to allow additional mandatory args in the future.

@oranagra
Copy link
Member

@yossigo i'm afraid that negating the flags can cause some confusion (and maybe bugs), since they'll be different than modules commands and redis native commands.

regarding optional, both flags and description are optional, and it's not very hard to skip one of them (just use "") if you wanna skip one and use the other, so it's just a matter of which one we think will be used less (i think description)

@yoav-steinberg
Copy link
Contributor

For the record I'm also for negating the flags, ie:

Ideally I think functions should be write and deny-oom by default, with the option to read-only (negate the write) and allow-oom (negate deny-oom).

@yossigo
Copy link
Collaborator

yossigo commented Jan 12, 2022

@oranagra But they're not really optional, because most functions need write and deny-oom.

@MeirShpilraien
Copy link
Author

MeirShpilraien commented Jan 12, 2022

Summery discussion with @yossigo @oranagra @guybe7

Functions

User experience should be smooth even at the cost of not matching functions flags to modules and commands flags. To achieve this we believe that the default behaviour for functions should be:

  1. Allow functions to read and write
  2. Do not run functions on OOM
  3. Do not run functions on stale replica
  4. Allow functions on cluster

Flags purpose is to allow users to change this default behaviour, We will change the flags as follow:

  1. no-writes - indicating the function perform no writes which means that it is OK to run it on:

    • read-only replica
    • Using FCALL_RO
    • If disc error detected

    It will not be possible to run a function in those situations unless the function turns on the no-writes flag

  2. allow-oom - indicate that its OK to run the function even if Redis is in OOM state, if the function will not turn on this flag if will not be possible to run it if OOM reached (even if the function declares no-writes).

  3. allow-state - indicate that its OK to run the function on stale replica, in this case we will also make sure the function is only perform stale commands and raise an error if not.

  4. no-cluster - indicate to disallow running the function if cluster is enabled.

We will also change redis.register_function API, positional arguments version will only accept function name and function callback and will register the functions with no flags (default behaviour as described above) and no description. Setting different flags or description will only be possible using name arguments.

Eval Scripts

In addition, an idea was raise to allow setting such flags for eval scripts. We can put those flags as a comment in the first line on the script. If flags are given we use them and set default behaviour just like function. If flags are not given we keep the behaviour as it is today for backward compatibility.
Example, writing this on the first line of eval script will set the script flag to allow-stale, which means that it will be possible to run the script on stale replica

#! {'allow-stale'}

Another example, setting empty flags on eval script will change the behaviour to be the default behaviour as describe above. So setting empty flags will allow the user to indicate that the script should not be invoke if OOM reached (unlike today where it is possible to run scripts if OOM reached):

#! {}

This idea will allow having the same flags capabilities on eval scripts as we define it on function while still keep backward comparability.

Notice that this idea allows solving this issue #8478 on eval scripts and that we need to consider how combine it with #10093 (in addition or as a replacement?).

@yossigo
Copy link
Collaborator

yossigo commented Jan 12, 2022

@MeirShpilraien Last comment LGTM.

1. Change `write` flag to `no-writes`
2. Change `deny-oom` to `allow-oom`
3. flags and discription can only be given as named arguments.
Meir Shpilraien (Spielrein) and others added 3 commits January 13, 2022 09:58
Co-authored-by: Oran Agra <oran@redislabs.com>
Set server.script_oom to 0 anyway, if we will find out
the we are out of memory, it will be set to 1.
When `allow-oom` is set, allow running any command even
those which set with CMD_DENYOOM flag.
@MeirShpilraien
Copy link
Author

@oranagra @yoav-steinberg @yossigo and I had a discussion about the allow-oom flag, we see 3 options how to handle this flag:

  • Allow running the functions without any writes
  • Allow running the function with only writes commands that does not mark with DENY_OOM flag
  • Allow running the function without any restrictions

We believe that option 3 is the best for the following reason:

  • Its not the default, so naive users will not encounter it by mistake, if a user use this flags then he read the docs and he understand what this flag means and Redis should trust him not to increase memory usage.
  • User might want to change existing data (perform incrby on an already existing counter)

Code and top comment was updated accordingly.

Extra verification on `allow-omm` test.
meir added 2 commits January 13, 2022 13:58
Only set server.script_oom = 0 if no script timedout.

Set it also on fcall_ro to not allow running scripts
on OOM even with fcall_ro. Test was added to verify it.
meir added 2 commits January 13, 2022 15:32
1. rename FUNCTION_FLAG_* to SCRIPT_FLAG_* and moved it to script unit
   so later on it could also be consumed by eval scripts.
2. Avoid using `setDeferredSetLen`
3. Checked `server.maxmemory` when checking if its OK to run a function
   on OOM conditions.
The redis version is provided from within a script using:
1. redis.REDIS_VERSION - string representation of the redis
   version in the format of MAJOR.MINOR.PATH
2. redis.REDIS_VERSION_NUM - number representation of the
   redis version in the format of 0x00MMmmpp
   MM - major version
   mm - minor version
   PP - patch version

The number version can be used to check if version is greater
or less another version.

The string version can be used to return to the user or print
as logs.
@oranagra oranagra changed the title Function Flags support Function Flags support (no-writes, no-cluster, allow-state, allow-oom) Jan 14, 2022
@oranagra oranagra merged commit 4db4b43 into redis:unstable Jan 14, 2022
enjoy-binbin added a commit to enjoy-binbin/redis that referenced this pull request Jan 15, 2022
Fixes cluster test introduced in redis#10066.
```
Function no-cluster flag: ERR Error registering functions: @user_function: 1: wrong number of arguments to redis.register_function
```
oranagra pushed a commit that referenced this pull request Jan 15, 2022
Fixes cluster test introduced in #10066.
```
Function no-cluster flag: ERR Error registering functions: @user_function: 1: wrong number of arguments to redis.register_function
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval-needed Waiting for core team approval to be merged release-notes indication that this issue needs to be mentioned in the release notes state:major-decision Requires core team consensus

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Redis Functions Flags

6 participants