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

Allow loading RDB with module AUX data even if module is missing. #11056

Conversation

MeirShpilraien
Copy link
Collaborator

@MeirShpilraien MeirShpilraien commented Jul 28, 2022

An issue that we encounter recently is a user that loaded a module that saves AUX data. The user ended up not using the module, but the RDB that was created did contain the module AUX data. The user ended up not been able to load his data without the module (even though he is not using the module).

Notice that even if the module saves nothing on the module AUX save callback, the module AUX load will still be called when loading the RDB and will failed if the module is missing. So the problem can not be solved on the module level and requires changes in Redis.

The Solution

We believe the AUX data is used to save module metadata and it is OK to simply skip it if the module isn't loaded. This is what the PR does, if module is not loaded we just skip its AUX data. To conclude:

  • Module not loaded -> skip module aux data
  • Module loaded -> call the module load callback

Other Solutions to Consider

The other solutions attempt to recognize the case where the module wrote nothing on the AUX save callback and allow loading the RDB (even if the module is missing) in this case only.
Note that we'll need some mechanism to read-head the next module type opcode so we can detect the EOF marker without damaging the parsing.

  1. Skip module AUX data only if no data was actually written to the RDB by the module AUX save callback and the module is not loaded. If the module is loaded we can not skip calling the load callback because otherwise it can be considered a breaking change. So we have to pass the responsibility of not reading any data to the module, but the module can not know that he has nothing to read without reading something :). So we need to give the module an ability to check that there is nothing to read. We can do it with another module API, RM_LoadHasData, that will tell the module if something was written to the AUX field. To conclude:

    • Aux field is empty ->
      • Module not loaded -> skip
      • Module loaded -> call the module load callback, module will need to use RM_LoadHasData to know if there is a data to read.
    • Aux field not empty ->
      • Module not loaded -> fail
      • Module loaded -> call the module load callback.
  2. Break the existing API and decide that if nothing was written on the save callback then we will not call the load callback (even if the module is exists). This can be considered a breaking change but it is easier solution then (1). To conclude:

    • Aux field is empty ->
      • Module not loaded -> skip
      • Module loaded -> skip
    • Aux field not empty ->
      • Module not loaded -> fail
      • Module loaded -> call the module load callback.
  3. Same as 2 but with some datatype flag to avoid breaking change. To conclude:

    • Aux field is empty ->
      • Module not loaded -> skip
      • Module loaded -> check datatype flag to decide whether or not to call the module load callback
    • Aux field not empty ->
      • Module not loaded -> fail
      • Module loaded -> call the module load callback.

We do believe the solution in the PR is good enough but we want to hear other opinions.

An issue that we encounter recently is a user that loaded a module that saves AUX data. The user ended up not using the module, but the RDB that was created did contain the module AUX data. The user ended up not been able to load his data without the module (even though he is not using the module).

Notice that even if the module saves nothing on the module AUX save callback, the module AUX load will still be called when loading the RDB and will failed if the module is missing. So the problem can not be solved on the module level and requires changes in Redis.

## The Solution

We believe the AUX data is used to save module metadata and it is OK to simply skip it if its not there. This is what the PR does, if module is not loaded we just skip its AUX data. To conclude:

* Module not loaded -> skip module aux data
* Module loaded -> call the module load callback

## Other Solutions to Consider

The other solutions attempt to recongnize the case where the module wrote nothing on the AUX save callback and allow loading the RDB (even if the module is missing) in this case only.

1. Skip module AUX data only if **no data was actually written to the RDB by the module AUX save** callback and the **module is not loaded**. If the module is loaded we can not skip calling the load callback because otherwise it can be consdidered a breaking change. So we have to pass the responsibility of **not** reading any data to the module, but the module can not know that he has nothing to read without reading something :). So we need to give the module an ability to check that there is nothing to read. We can do it with another module API, `RM_LoadHasData`, that will tell the module if something was written to the AUX field. To conclude:
    * Aux field is empty ->
      * Module not loaded -> skip
      * Module loaded -> call the module load callback, module will need to use `RM_LoadHasData` to know if there is a data to read.
    * Aux field not empty ->
      * Module not loaded -> fail
      * Module loaded -> call the module load callback.

2. Break the existing API and decide that if nothing was written on the save callback then we will not call the load callback (even if the module is exists). This can be considered a breaking change but its easier solution then (1). To conclude:
    * Aux field is empty ->
      * Module not loaded -> skip
      * Module loaded -> skip
    * Aux field not empty ->
      * Module not loaded -> fail
      * Module loaded -> call the module load callback.

3. Same as 2 but with some datatype flag to avoid breaking change. To conclude:
    * Aux field is empty ->
      * Module not loaded -> skip
      * Module loaded -> check datatype flag to decide whether or not to call the module load callback
    * Aux field not empty ->
      * Module not loaded -> fail
      * Module loaded -> call the module load callback.

We do believe the solution in the PR is good enough but we want to hear other opinions.
Copy link
Member

@oranagra oranagra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@redis/core-team please approve or comment.

src/rdb.c Show resolved Hide resolved
@oranagra oranagra added state:major-decision Requires core team consensus release-notes indication that this issue needs to be mentioned in the release notes labels Jul 28, 2022
@oranagra oranagra added the approval-needed Waiting for core team approval to be merged label Jul 31, 2022
@oranagra
Copy link
Member

oranagra commented Aug 4, 2022

We had a discussion about this with Yossi, we concluded that it's better / safer to let the module that saved this field to declare if loading it is optional or not, so that human error forgetting to load a module on restart or a replica would not lead to loss of actual data (in case there's meaningful data in the aux field even when there is no registered data type, or no keys of that data type).
This requires an RDB format change..

@oranagra oranagra removed release-notes indication that this issue needs to be mentioned in the release notes approval-needed Waiting for core team approval to be merged labels Aug 4, 2022
@madolson
Copy link
Contributor

madolson commented Aug 8, 2022

Sorry for chiming late. I agree that I don't think we should just "skip" module data.

Adding a change where a module can declare if auxiliary data is optional seems like a reasonable mitigation.

@soloestoy
Copy link
Collaborator

soloestoy commented Aug 8, 2022

IMHO, the module "aux" data has no difference with module data, we should skip them both or just exit.

We can expand the original problem in this PR, imagine that a user loaded a module with module type, and did insert some module values, and then this user cannot load data without the module even they wanna discard the module's values and just keep other types of data.

To fix it, I think we can add a new configuration for users to decide whether skip the module aux and value or not if module is missing.

@oranagra
Copy link
Member

oranagra commented Aug 8, 2022

the difference is that for keys, the user can easily delete the module keys before saving the rdb.
for aux it's not that simple, the module can't easily avoid saving it even if it knows it's gonna be empty, and it can't even just return without saving anything (when it'll be impossible for the module to load such an RDB, the callback will be called, but the module has no way to detect that it's empty).

i think the viable solution is either to delay saving the aux field headers until the module actually saved something to the rdb, in which case, if it doesn't save anything, nothing will be present and it'll not get a callback on loading.

or as suggested by Yossi, to let the module declare that the payload is optional and can be skipped.
in which case the module can store a nearly empty payload, which it'll be able to read.

@soloestoy
Copy link
Collaborator

I still prefer a new config, it's easy to understand and use, and can save the time caused by deleting the module's keys (SCAN and DEL?).

or as suggested by Yossi, to let the module declare that the payload is optional and can be skipped.
in which case the module can store a nearly empty payload, which it'll be able to read.

I don't understand, if a module declare the aux filed cannot be skipped, users cannot load RDB without the module.

I'm confused, the problem you wanna fix is loss of data or the restart failure without module?

@oranagra
Copy link
Member

oranagra commented Aug 8, 2022

the problem i wanna solve is a case were someone has an instance with a module which he didn't use, or use it and then stopped using it, and now he wants to restart the instance without that module.
but if the module always saves some global aux field we either need to:

  1. allow the module to avoid saving the aux field.
  2. allow the module to save an empty one which can be somehow implicitly skipped (both if the module is present, or missing)
  3. allow the module to declare this is optional, and can be safely skipped even if not empty.

i don't think we need to solve a similar problem about module keys, users should delete them.
the reason for this discussion is that for aux field there's simply no solution currently (complete dead end), specifically since modules can't avoid saving them, and if they'll choose to save an empty one, they have no way to handle it on load time.

@soloestoy
Copy link
Collaborator

soloestoy commented Aug 8, 2022

the problem i wanna solve is a case were someone has an instance with a module which he didn't use, or use it and then stopped using it, and now he wants to restart the instance without that module.

OK, then I think the approach in this PR is enough, especially considering backward compatibility.

  1. allow the module to declare this is optional, and can be safely skipped even if not empty.

I don't like this method, IIUC module can still save aux field and declare the field must not be skipped, and then the restart will fail.

@oranagra
Copy link
Member

oranagra commented Aug 8, 2022

the problem with the current approach in this PR is that it could be that some modules save their entire data in the aux field, and silently skipping it could cause a real data loss for a user (which could be discovered when it's too late) in the event of human error of forgetting to load the module.

as noted, we could have redis delay saving the aux headers until the module actually saves something, and then if it didn't save anything, it won't get a load callback. but maybe we consider that a breaking change, since maybe some module used these callbacks as hooks to be notified of things, and didn't save anything anyway, so it'll miss the hook.

the other options are to add module APIs:

  1. callback to let the module decide if the aux field should be saved or not (doesn't require RDB format change).
  2. let the module declare that the aux field is optional in which case it can be skipped if the module is missing, even if the data isn't empty (requires rdb format change).

another option is like you said, add a config to let the user decide if it's safe to skip data that's not loadable, and can be applied on keys as well (i don't like it that much, since i don't think it should be the user's decision).

if we do want it to be a user's decision, maybe it should be an external tool (like rdb-cli) that can process the rdb, and trim things from it (could even be keys by some regex pattern).
so we shift the problem to a tool rather than redis.

@soloestoy
Copy link
Collaborator

if we do want it to be a user's decision, maybe it should be an external tool (like rdb-cli) that can process the rdb, and trim things from it (could even be keys by some regex pattern).
so we shift the problem to a tool rather than redis.

vote for this one.

MeirShpilraien added a commit to MeirShpilraien/redis that referenced this pull request Oct 11, 2022
As describe here: redis#11056

The issue is that when saving an RDB with module AUX data,
the module AUX metadata (moduleid, when, ...) is saved to the RDB
even though the module did not saved any actual data. This prevent
loading the RDB in the absence of the module (although there is
no actual data in the RDB that requires the module to be loaded).

On redis#11056, the solution suggested
was the ignore AUX data if the module is not loaded. This solution is
wrong because we have data that we ignore and allow the server to
start without it.

Other solution that was suggested is to provide a new module API
to save optional AUX fields. This solution will solve the problem
but it is probably more then we actually need and we want to avoid
new module API and new RDB versions if possible.

The solution suggested in this PR is that module AUX will be saved
on the RDB only if the module acutally saved something during
`aux_save` function.

For modules that saved some data on the RDB, this PR will make no
difference. We also claim that any valid used case of AUX data must
save some data on `aux_save` function (Even if a module wants an
indication that he has no data to save, it needs to save this
indication as data).

So the only difference this PR makes is for modules that **never**
save data on the `aux_save` function and just used it
to know that a saving operation started/ended. For this purpose
we have server events, using module AUX for this purpose is wrong
and we believe its ok to break it. For such modules, the `aux_load`
function will now **not** be called.

### Technical Details

To avoid saving AUX data on RDB, we change to code to first save
the AUX metadata (moduleid, when, ...) into a temporary buffer.
The buffer is then flushed to the rio at the first time the module
makes a write operation inside the `aux_save` function. If the module
saves nothing, the entire temporary buffer is simply dropped and no
data about this AUX field is saved to the RDB. This make it possible
to load the RDB even in the absence of the module.

Test was added to verify the fix.
MeirShpilraien added a commit to MeirShpilraien/redis that referenced this pull request Oct 11, 2022
As describe here: redis#11056

The issue is that when saving an RDB with module AUX data,
the module AUX metadata (moduleid, when, ...) is saved to the RDB
even though the module did not saved any actual data. This prevent
loading the RDB in the absence of the module (although there is
no actual data in the RDB that requires the module to be loaded).

On redis#11056, the solution suggested
was the ignore AUX data if the module is not loaded. This solution is
wrong because we have data that we ignore and allow the server to
start without it.

Other solution that was suggested is to provide a new module API
to save optional AUX fields. This solution will solve the problem
but it is probably more then we actually need.

The solution suggested in this PR is that module AUX will be saved
on the RDB only if the module acutally saved something during
`aux_save` function.

To support backward compatibility, we introduce `aux_save2` callback
that acts the same as `aux_save` with the tiny change of avoid
saving the aux field if no data was actually saved by the module.
Modules can use the new API to make sure that if they have no data to
save, then it will be possible to load the created RDB even without the module.

To avoid saving AUX data on RDB, we change the code to first save
the AUX metadata (moduleid, when, ...) into a temporary buffer.
The buffer is then flushed to the rio at the first time the module
makes a write operation inside the `aux_save` function. If the module
saves nothing (and `aux_save2` was used), the entire temporary buffer
is simply dropped and no data about this AUX field is saved to the RDB.
This make it possible to load the RDB even in the absence of the module.

Test was added to verify the fix.
@MeirShpilraien
Copy link
Collaborator Author

Replaced by: #11374

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state:major-decision Requires core team consensus
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants