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

Additional AOFRW deprecation cleanups #9794

Open
oranagra opened this issue Nov 17, 2021 · 26 comments
Open

Additional AOFRW deprecation cleanups #9794

oranagra opened this issue Nov 17, 2021 · 26 comments

Comments

@oranagra
Copy link
Member

oranagra commented Nov 17, 2021

  1. remove the option to disable AOF preamble RDB this adds a lot of code to redis and modules
  2. delete all the code in rewriteAppendOnlyFileRio and deprecate modules aof_rewrite callback
  3. always generate base rdb (even when starting empty) (done in Always create base AOF file when redis start from empty. #10102)
  4. disallow BGREWRITEAOF command when appendonly is off?

details: #9539 (comment)

@oranagra oranagra created this issue from a note in 7.0 (To Do) Nov 17, 2021
@MeirShpilraien MeirShpilraien mentioned this issue Nov 30, 2021
2 tasks
oranagra added a commit that referenced this issue Dec 2, 2021
# Redis Function
This PR added the Redis Functions capabilities that were suggested on #8693.
The PR also introduce a big refactoring to the current Lua implementation
(i.e `scripting.c`). The main purpose of the refactoring is to have better
code sharing between the Lua implementation that exists today on Redis
(`scripting.c`) and the new Lua engine that is introduced on this PR.
The refactoring includes code movements and file name changes as well as some
logic changes that need to be carefully reviewed. To make the review easier,
the PR was split into multiple commits. Each commit is deeply described later on
but the main concept is that some commits are just moving code around without
making any logical changes, those commits are less likely to cause any issues
or regressions and can be reviewed fast. Other commits, which perform code and
logic changes, need to be reviewed carefully, but those commits were created
after the code movements so it's pretty easy to see what was changed. To sum up,
it is highly recommended to review this PR commit by commit as it will be easier
to see the changes, it is also recommended to read each commit description
(written below) to understand what was changed on the commit and whether or not
it's just a huge code movement or a logic changes.

## Terminology
Currently, the terminology in Redis is not clearly defined. Scripts refer to Lua
scripts and eval also refers only to Lua. Introducing Redis Function requires
redefining those terms to be able to clearly understand what is been discussed
on each context.
* eval - legacy Lua script implementation.
* Function - new scripting implementation (currently implemented in Lua but in
  the future, it might be other languages like javascript).
* Engine - the component that is responsible for executing functions.
* Script - Function or legacy Lua (executed with `eval` or `evalsha`)

## Refactoring New Structure
Today, the entire scripting logic is located on `scripting.c`. This logic can
be split into 3 main groups:
1. Script management - responsible for storing the scripts that were sent to
   Redis and retrieving them when they need to be run (base on the script sha
   on the current implementation).
2. Script invocation - invoke the script given on `eval` or `evalsha` command
   (this part includes finding the relevant script, preparing the arguments, ..)
3. Interact back with Redis (command invocation)

Those 3 groups are tightly coupled on `scripting.c`. Redis Functions also need
to use those groups logics, for example,  to interact back with Redis or to
execute Lua code. The refactoring attempts to split those 3 groups and define
APIs so that we can reuse the code both on legacy Lua scripts and Redis Functions.

In order to do so we define the following units:
1. script.c: responsible for interaction with Redis from within a script.
2. script_lua.c: responsible to execute Lua code, uses `script.c` to interact
   with Redis from within the Lua code.
3. function_lua.c: contains the Lua engine implementation, uses `script_lua.c`
   to execute the Lua code.
4. functions.c: Contains Redis Functions implementation (`FUNCTION` command,),
   uses `functions_lua.c` if the function it wants to invoke needs the Lua
   engine.
4. eval.c: the original `scripting.c` contains the Lua legacy implementation and
   was refactored to use `script_lua.c` to invoke the Lua code.

## Commits breakdown
Notice: Some small commits are omitted from this list as they are small and
insignificant (for example build fixes)

### First commit - code movements
This commit rename `scripting.c` -> `eval.c` and introduce the new `script_lua.c`
unit. The commit moves relevant code from `eval.c` (`scripting.c`) to
`script_lua.c`, the purpose of moving the code is so that later we will be able
to re-use the code on the Lua engine (`function_lua.c`). The commit only moves
the code without modifying even a single line, so there is a very low risk of
breaking anything and it also makes it much easier to see the changes on the
following commits.
Because the commit does not change the code (only moves it), it does not compile.
But we do not care about it as the only purpose here is to make the review
processes simpler.

### Second commit - move legacy Lua variables into `eval.c`
Today, all Lua-related variables are located on the server struct. The commit
attempt to identify those variable and take them out from the server struct,
leaving only script related variables (variables that later need to be used
also by engines)
The following variable where renamed and left on the server struct:
   * lua_caller 			-> script_caller
   * lua_time_limit 		-> script_time_limit
   * lua_timedout 		-> script_timedout
   * lua_oom 			-> script_oom
   * lua_disable_deny_script 	-> script_disable_deny_script
   * in_eval			-> in_script

The following variables where moved to lctx under eval.c
   * lua
   * lua_client
   * lua_cur_script
   * lua_scripts
   * lua_scripts_mem
   * lua_replicate_commands
   * lua_write_dirty
   * lua_random_dirty
   * lua_multi_emitted
   * lua_repl
   * lua_kill
   * lua_time_start
   * lua_time_snapshot

This commit is in a low risk of introducing any issues and it is just moving
variables around and not changing any logic.

### Third commit - introducing script unit
This commit introduces the `script.c` unit. Its purpose (as described above) is
to provide an API for scripts to interact with Redis. Interaction includes
mostly executing commands, but also other functionalities. The interaction is
done using a `ScriptRunCtx` object that needs to be created by the user and
initialized using `scriptPrepareForRun`. A detailed list of functionalities
expose by the unit:
1. Calling commands (including all the validation checks such as
   acl, cluster, read only run, ...)
2. Set Resp
3. Set Replication method (AOF/REPLICATION/NONE)
4. Call Redis back on long-running scripts to allow Redis to reply to clients
   and perform script kill

The commit introduces the new unit and uses it on eval commands to interact with
Redis.

### Fourth commit - Moved functionality of invoke Lua code to `script_lua.c`
This commit moves the logic of invoking the Lua code into `script_lua.c` so
later it can be used also by Lua engine (`function_lua.c`). The code is located
on `callFunction` function and assumes the Lua function already located on the
top of the Lua stack. This commit also change `eval.c` to use the new
functionality to invoke Lua code.

### Fith commit - Added Redis Functions unit (`functions.c`) and Lua engine
(`function_lua.c`)
Added Redis Functions unit under `functions.c`, included:
1. FUNCTION command:
     * FUNCTION CREATE
     * FUNCTION CALL
     * FUNCTION DELETE
     * FUNCTION KILL
     * FUNCTION INFO
     * FUNCTION STATS
2. Register engines

In addition, this commit introduces the first engine that uses the Redis
Functions capabilities, the Lua engine (`function_lua.c`)

## API Changes
### `lua-time-limit`
configuration was renamed to `script-time-limit` (keep `lua-time-limit` as alias
for backward compatibility).

### Error log changes
When integrating with Redis from within a Lua script, the `Lua` term was removed
from all the error messages and instead we write only `script`. For example:
`Wrong number of args calling Redis command From Lua script` -> `Wrong number
of args calling Redis command From script`

### `info memory` changes:
Before stating all the changes made to memory stats we will try to explain the
reason behind them and what we want to see on those metrics:
* memory metrics should show both totals (for all scripting frameworks), as well
  as a breakdown per framework / vm.
* The totals metrics should have "human" metrics while the breakdown shouldn't.
* We did try to maintain backward compatibility in some way, that said we did
  make some repurpose to existing metrics where it looks reasonable.
* We separate between memory used by the script framework (part of redis's
  used_memory), and memory used by the VM (not part of redis's used_memory)

A full breakdown of `info memory` changes:
* `used_memory_lua` and `used_memory_lua_human` was deprecated,
  `used_memory_vm_eval` has the same meaning as `used_memory_lua`
* `used_memory_scripts` was renamed to `used_memory_scripts_eval`
* `used_memory_scripts` and `used_memory_scripts_human` were repurposed and now
  return the total memory used by functions and eval (not including vm memory,
  only code cache, and structs).
* `used_memory_vm_function` was added and represents the total memory used by
  functions vm's
* `used_memory_functions` was added and represents the total memory by functions
  (not including vm memory, only code cache, and structs)
* `used_memory_vm_total` and `used_memory_vm_total_human` was added and
  represents the total memory used by vm's (functions and eval combined)

### `functions.caches`
`functions.caches` field was added to `memory stats`, representing the memory
used by engines that are not functions (this memory includes data structures
like dictionaries, arrays, ...)

## New API
### FUNCTION CREATE

Usage: FUNCTION CREATE `ENGINE` `NAME` `[REPLACE]` `[DESC <DESCRIPTION>]` `<CODE>`

* `ENGINE` - The name of the engine to use to create the script.
* `NAME` - the name of the function that can be used later to call the function
  using `FUNCTION CALL` command.
* `REPLACE` - if given, replace the given function with the existing function
  (if exists).
* `DESCRIPTION` - optional argument describing the function and what it does
* `CODE` - function code.

The command will return `OK` if created successfully or error in the following
cases:
* The given engine name does not exist
* The function name is already taken and `REPLACE` was not used.
* The given function failed on the compilation.

### FCALL and FCALL_RO

Usage: FCALL/FCALL_RO `NAME` `NUM_KEYS key1 key2` … ` arg1 arg2`

Call and execute the function specified by `NAME`. The function will receive
all arguments given after `NUM_KEYS`. The return value from the function will
be returned to the user as a result.

* `NAME` - Name of the function to run.
* The rest is as today with EVALSHA command.

The command will return an error in the following cases:
* `NAME` does not exist
* The function itself returned an error.

The `FCALL_RO` is equivalent to `EVAL_RO` and allows only read-only commands to
be invoked from the script.

### FUNCTION DELETE

Usage: FUNCTION DELETE `NAME`

Delete a function identified by `NAME`. Return `OK` on success or error on one
of the following:
* The given function does not exist

### FUNCTION INFO

Usage: FUNCTION INFO `NAME` [WITHCODE]

Return information about a function by function name:
* Function name
* Engine name
* Description
* Raw code (only if WITHCODE argument is given)

### FUNCTION LIST

Usage: FUNCTION LIST

Return general information about all the functions:
* Function name
* Engine name
* Description

### FUNCTION STATS

Usage: FUNCTION STATS

Return information about the current running function:
* Function name
* Command that was used to invoke the function
* Duration in MS that the function is already running

If no function is currently running, this section is just a RESP nil.

Additionally, return a list of all the available engines.

### FUNCTION KILL

Usage: `FUNCTION KILL`

Kill the currently executing function. The command will fail if the function
already initiated a write command.

## Notes
Note: Function creation/deletion is replicated to AOF but AOFRW is not
implemented sense its going to be removed: #9794
@oranagra
Copy link
Member Author

For the record. we discussed these in a core-team meeting.
we concluded that that we wanna proceed with the intent to generate an empty preamble / rdb when the server starts empty.

however, we concluded that we don't want to delete the code in rewriteAppendOnlyFileRio and deprecate the preamble config yet.
one of the reasons is that there are users and tools that wish to convert and RDB to commands, and some of these may want to use the module api for aofrw in order to convert the module keys to commands.
once we delete the AOFRW code, the module API becomes dead code (only used for compatibility with old redis), and module developers can't test it even if they wanted to.

@chenyang8094
Copy link
Collaborator

chenyang8094 commented Dec 28, 2021

So, I understand that we gave up these plans, right?

@oranagra
Copy link
Member Author

as far as i'm aware, we only gave up the first two bullets on the top, we do wanna implement the 3rd, and maybe the 4th too.

@yossigo
Copy link
Member

yossigo commented Jan 3, 2022

@oranagra This is what I understand and support as well.
Always having a base RDB simplifies and streamlines persistence.
Same goes for disabling BGREWRITEAOF, unless anyone comes up with a good argument for maintaining support for it.

@oranagra
Copy link
Member Author

oranagra commented Jan 4, 2022

discussed this again in the core-team meeting. decided to proceed with items 3 and 4 in the top list

@chenyang8094
Copy link
Collaborator

I think this needs more detailed description, or I will mention a draft separately when I have time.

@oranagra
Copy link
Member Author

oranagra commented Jan 6, 2022

number 4 seems quite straight forward.
as for 3, i imagine just doing a foreground rdbSave on startup (only if preamble is enabled, we're configured to persist to AOF, and there's no pre-existing AOF we loaded from).
maybe when implementing it we'll face some dilemmas.

@chenyang8094
Copy link
Collaborator

chenyang8094 commented Jan 11, 2022

several questions:

  1. why “always generate base rdb (even when starting empty)” ? Just to avoid the situation where there is only incr file and no base file?
  2. If aof-use-rdb-preamble is set to no, do we still have to do this? Or force an empty AOF format BASE?
  3. Do we need to force create base if AOF is not turned on (loading RDB) ?
  4. If we want to force create a base, why not trigger the AOFRW in the background directly instead of doing it in the foreground? Just to avoid the cost of a fork?
  5. why “disallow BGREWRITEAOF command when appendonly is off” ? Just to avoid the situation where there is only base file and no incr file? This is a breaking change, will it affect existing users.

@oranagra
Copy link
Member Author

i don't recall if this was already specified or not, so i'll just respond instead of trying to find it.

the main reasons for always generating a base file are:

  1. when preamble is on, the vast majority of cases where someone will look at the aof folder (or aof file, pre redis 7), it'll have an rdb base. so the case where it's missing is an anomaly and some users / systems may assume it never happens and fail if / when they see it.
  2. some complex modules are loaded with a configuration at startup, and they persist that configuration into the RDB aux fields to be available for the replica and when recovering from persistence. without this info, the persistence may be incomplete or unusable. such a module would want the RDB aux fields to always be available in the AOF.

This feature of generating a base file when starting up empty isn't needed if preamble is disabled IMHO.

regarding disallowing BGREWRITEAOF, i think it complicates redis (IIRC we saw 2 bugs recently because of that feature), and i don't see why that feature is needed. so i thought it's a good idea to remove it.

@chenyang8094
Copy link
Collaborator

chenyang8094 commented Jan 11, 2022

@oranagra Do you mean that when redis starts and finds that base does not exist, it generates an RDB in the foreground and then loads it?

IIRC the previous AOF mechanism did not do this? The preamble is written to the AOF only after the first AOFRW.

@oranagra
Copy link
Member Author

I meant that if redis starts configured for appendonly yes, and there's no AOF file loaded (redis starts up empty, and configured to persist to AOF), then i'd like it to do a foreground RDB save (with the AOF flag in it's header) to generate a base file, and put it in the manifest.
since that base file is empty, there's no need to load it IMHO, but i don't mind loading it if you feel that's better.

@oranagra
Copy link
Member Author

yes, the previous AOF (with preamble rdb header) didn't have that mechanism, but in theory it could be added there too (if we wanted to "improve" redis 6.2)

@chenyang8094
Copy link
Collaborator

If we only have a INCR aof when redis starts, do we need to force create a BASE ?

@oranagra
Copy link
Member Author

If we only have a INCR aof when redis starts, do we need to force create a BASE ?

no, only create an empty base file if nothing was loaded on startup.
IMHO there's no need to load it. same as we don't load the RDB file we just generated after SAVE or BGSAVE, and don't load the AOF after an AOFRW (it should have the same content as what we already have, which is an empty redis, with some modules and their configuration).

@chenyang8094
Copy link
Collaborator

I got to know some Chinese cloud companies. They allow customers to use BGREWRITEAOF even AOF is off to generate an AOF file which will be used for backup, clone and replay instance , analysis (depending on AOF readability) , use to troubleshoot. I think the breaking change of disable BGREWRITEAOF is the fatal blow for these cloud services.

@soloestoy
Copy link
Collaborator

I think it's better to allow BGREWRITEAOF when appendonly is off, and @chenyang8094 has already gave the examples above.

If you want more detailed examples, here is a document we (Alibaba cloud) help people to use aof files to migrate data, https://www.alibabacloud.com/help/en/doc-detail/26357.html , in this doc we suggest users open aof to migrate data, but as I know some users still like close appendonly and just use BGREWRITEAOF, because they don't want to consume too much IO and fork.

@chenyang8094
Copy link
Collaborator

@oranagra
Copy link
Member Author

Thank you.
Indeed these documents don't mention BGREWRITEAOF, but i got the point.
I suppose these documents are a little bit old (they don't mention you need to disable preamble rdb, so they're already kinda broken).
I suppose that if someone would have wrote such a document today, it'll recommend using RedisShake or a similar tool (redis-rdb-tools --protocol).
I suppose that starting redis 7.0, if this document would have been updated, it should mention both disabling preamble, but also digging into the aof folder and manifest, to find the base file that was created.

so considering all of that, do you really think blocking BGREWRITEAOF in that scenario would be a breaking change in 7.0?

p.s. i still didn't give up the desire to completely delete the rewriteAppendOnlyFileRio logic and deprecate the ability to disable preamble some day (i think it complicates redis).

@chenyang8094
Copy link
Collaborator

@oranagra May be you right, but i think we can't give up compatibility just because a thing will cause complexity, and at least so far I haven't found how complicated it is and what problems it will cause, if there is, please point it out, I'll be happy to solve it this matter.
In addition, we have found that there are many cloud vendors and users using this method in reality, can we really ignore them directly? We can tolerate multiple redis sharing the same dir, we can accept that they upgrade redis 7.0 without changing their backup script, we can accept that appendfilename contains spaces, why can't we accept users to use BGREWRITEAOF when AOF is off?
So, do we have a unified standard for judging whether a feature is retained? That is, keeping compatibility first, reducing redis complexity second?

@chenyang8094
Copy link
Collaborator

chenyang8094 commented Jan 13, 2022

Also,IIRC, redis has a limit on the size of a single RESP or inputbuf (maybe 500MB?). Some tools will obtain AOF or RDB from a redis, then parse and replay them to another redis. At this time, if we have a key that exceeds 500MB, if the RDB is replayed, the corresponding single restore cmd may exceed 500MB , which will cause the replay to fail. On the contrary, these data structures will be split into multiple commands when rewriting AOF (for example, the hash will be split into multiple hmset), which can solve the problem of replaying bigkey.
So, removing rewriteAppendOnlyFileRio will make us lose the possibility of dealing with such problems.

@oranagra
Copy link
Member Author

there's obviously no standard way to decide, we just try to do what we think is best.
and this feature (BGREWRITEAOF when AOF is off), isn't adding a huge overhead, just some small random edge cases that surface from time to time (the rewriteAppendOnlyFileRio mechanism is a huge overhead though).

I suppose it's just that i wasn't aware of the use cases you're mentioning now, which is why i thought we can chuck it.
but now that i am aware of it, and reading the links you sent, i think they're already broken, and will become even more broken in 7.0 due to the manifest thing.

so the advise in these links doesn't seem very useful, and maybe we should come up with a different solution for these use cases in the future.
like making some librdb.so out of rdb.c, and add some feature in redis-cli that can convert an RDB file into command stream and send it to some target.

After this discussion, I guess i don't mind closing this issue and leaving that thing as is. but i must say i don't see a bright future for this "feature", the use case for it is better served in other ways.

@oranagra
Copy link
Member Author

regarding your last post about proto-max-bulk-len and client-query-buffer-limit and the issue with a huge RESTORE command. that's a known issue and the roadmap has other solution for it.
RESP3 has a chunked feature that should allow sending an RDB payload to RESTORE command in a cut-through way (without buffering to collect the full size in advance), and redis should be able to process the RESTORE command in the background in a non-blocking way. (maybe a feature for redis 8).
meanwhile, indeed the only way around that is to convert the rdb file to primitive commands, which is what many tools do.

@chenyang8094
Copy link
Collaborator

Wow, great to see this feature in RESP3.
It is indeed feasible to let these replay tools convert RDB into original commands, but for some complex data structures such as redis stream or other redis modules, these tools are powerless or very difficult. These problems are all I encountered in maintaining RedisShake.
So I'm just saying what I know, lest you miss something.

@oranagra
Copy link
Member Author

i maintained a similar tool in Redis Labs, i'm well aware of the limitations of redis and the complications of converting RDB file to RESTORE commands.
I don't think this justifies rewriteAppendOnlyFileRio in redis, and i do have a plan to solve these in different ways (RESP3 chunks, as well as librdb.so), but that's still far ahead.

@ShooterIT
Copy link
Collaborator

disallow BGREWRITEAOF command when appendonly is off?

I wish we don't break this change, it is charismatic for redis to provide many methods to use freely.

IIRC, some users actually use this feature 😂, AOF is easy to inject to another running redis server, such as by nc , but RDB is needed to parse, enabling AOF may cause performance loss, so they execute BGREWRITEAOF when needed.

I also agree AOF is a convenient method to backup, actually we ever used this way instead of RDB since RDB didn't support incrementally fsync before, calling fsync at the last causes much heavy disk load which influences redis main thread and other writing disk processes.

@oranagra
Copy link
Member Author

ok... i'm ok with dropping this change from 7.0.
But as i noted in my previous messages, i don't see a bright future for this feature.
I do think that other recent changes we made (preamble, and multi-part) are already breaking it in some way.
and i do think that this mechanism will be completely removed from redis in some future version, while other mechanisms will be added to cover these use cases in a better way.
@redis/core-team it was something we agreed on a call, so FYI on dropping it.

@oranagra oranagra added state:to-be-closed requesting the core team to close the issue and removed 7.0-must-have labels Jan 14, 2022
@oranagra oranagra moved this from To Do to Backlog in 7.0 Jan 14, 2022
@oranagra oranagra removed the state:to-be-closed requesting the core team to close the issue label Jan 14, 2022
@oranagra oranagra removed this from Backlog in 7.0 Jan 31, 2022
@oranagra oranagra added this to the Next major backlog milestone Jan 31, 2022
@oranagra oranagra changed the title Additional AOFRW cleanups for redis 7.0 (After multipart AOF) Additional AOFRW deprecation cleanups Jan 31, 2022
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

5 participants