Modules callbacks for lazy free effort, and unlink#7912
Modules callbacks for lazy free effort, and unlink#7912oranagra merged 2 commits intoredis:unstablefrom chenyang8094:unstable
Conversation
yossigo
left a comment
There was a problem hiding this comment.
Thank you @chenyang8094 for this PR!
There was a problem hiding this comment.
I think this test might be flaky working at a byte level. I would consider allocating more substantial amount of memory in the test module (e.g. 500KB per entry or so) so it the effect would be more observable and robust.
There was a problem hiding this comment.
Yes, I directly refer to'lazyfree.tcl' and change the memory allocation amount to 100000, which has far exceeded LAZYFREE_THRESHOLD. At the same time, because of the'lazyfreed_objects' information, I can be sure that the memory of this module is lazyfreed
|
It's a very useful feature and already worked in our product env. I think is's safe to merge after @yossigo suggestion : ) BTW, this PR contains another change, record counts of object freed in bio, info reply modification needs a major decision too. |
|
@soloestoy Thanks for the clarification! |
Ok, I have updated it |
|
I would like to use this opportunity to add a few more changes in that area. First, in the spirit of #7865, it would be nice if a module can get the compiled version of REDISMODULE_TYPE_METHOD_VERSION so that when registering the callbacks it knows which of them are gonna be used and which will be ignored. Secondly, I know a few complicated modules also keep some global information about their keys (outside redis's dict), while this is a bit of a violation, they get away with it since redis is not able to do anything with that pointer without the module's assistance (not even free it), but now that redis can detach that key from the keyspace in the main thread, and free it in a background thread, i think we need to give the module a little bit more help. What i think would solve it is to add another
I would like to get some feedback from @MeirShpilraien, @swilly22, @guybe7. P.S. i vaguely remember there were a few other things we wanna do by iterating on module data structure (inside its value pointer), which would be nice to add soon too, but i don't recall what. |
|
@oranagra I agree we need the |
|
@MeirShpilraien i meant that the |
|
@oranagra I get the idea of the Regarding the |
|
@MeirShpilraien i'm not sure about combining free_effort and detach. usually redis knows if it's willing to consider doing a lazy free or not, so combining these will force the module to "compute" the free effort even when there's no need. regarding @danni-m FYI - i think you also had trouble with |
|
@oranagra I think all of this calls for a more careful design, and we don't necessarily have to address it all at once or as part of this PR. One issue is exposing the two-step lazy free to modules, so they also get an The issue of the opposite |
|
@yossigo i agree much of what i raised is not directly related and can be handled separately (just felt that this is an opportunity to raise it). The detach / unlink thing though is a bit related, at least in the sense that we should avoid releasing the API in this PR until we finished designing that part too since we might wanna change that API. p.s. even if the additional API doesn't change the API of this PR, i rather not increment REDISMODULE_TYPE_METHOD_VERSION twice. regarding backwards compatibility, if the detach and free effort come in the same version, then i see no problem, old modules that don't implement, will always be freed from the main thread. So what do you suggest? wanna call the core-team to approve it and merge it as is, and then open another PR to add / modify it? |
I can understand what you mean very much. |
|
@chenyang8094 i think that we all agree that |
@yossigo @soloestoy @oranagra @MeirShpilraien I have tried to modify the code according to my own understanding, please review it again, thank you. |
src/module.c
Outdated
There was a problem hiding this comment.
maybe we want to let the module have access to the mt (val) too?
some modules may want to look into some variable they have there, and be forced to do RM_OpenKey for that.
another alternative is maybe pass a RedisModuleKey and then they can get the key name via RM_GetKeyNameFromModuleKey, and value via RM_ModuleTypeGetValue
what do you guys thing?
There was a problem hiding this comment.
Well, If necessary, I'd prefer to just pass val over because it's already there.
There was a problem hiding this comment.
@guybe7 @MeirShpilraien @yossigo what do you think?
i suppose a void* value is consistent with other APIs:
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
typedef void (*RedisModuleTypeUnlinkFunc)(RedisModuleString *key, const void *value);i'm ok with this, but i want to give it a bit more thought so that we don't regret it in the future.
The alternative of providing a RedisModuleKey* will let the user get the key name, value and also other things (like LRU) from it.
There was a problem hiding this comment.
@oranagra I lean towards just passing the value, I don't see any real need to manipulate the key (after all it's getting unlinked) and it's more consistent with the other calls.
oranagra
left a comment
There was a problem hiding this comment.
i'm ok with the current API.
let's wait a day to see if others raise some objections.
|
@chenyang8094 thank you. @redis/core-team please approve:
|
I have used rebase to merge the two commits into one, and re-edited the commit message. Please check again if there are any problems, thank you. |
Yes, the |
src/db.c
Outdated
There was a problem hiding this comment.
i now realize that we're missing a call to moduleNotifyKeyUnlink here (better be done before calling dictSetVal).
which makes me wonder what else are we missing?
src/module.c
Outdated
There was a problem hiding this comment.
I see this is a direct translation from OS, but I think the notion of free_effort is unnecessary and confusing from a module context. I feel like a better callback would be, can_free_async(), which can make it's own decision about whether or not this is worth freeing async.
There was a problem hiding this comment.
@madolson what do you mean by "OS"?
we're trying to match the effort to:
#define LAZYFREE_THRESHOLD 64
i think a name like can_free_async is misleading (it's possible that it "can" but better not do that). if we're seeking a boolean return, maybe a better name would be should_free_async.
but then if we some day let users control this constant via command or config, the modules will need to be able to query it.
i think returning the complexity (how many pointers you have to free, or how many loops you'll need to run) makes sense as a return value.
i suppose we better document that 0 indicate not to be released by a thread.
There was a problem hiding this comment.
Open source? I didn't know what to call none module redis code, redis core maybe?
I still think should free async makes a more sense. The lazy free estimate is an approximation for "should we free this async". A module doesn't need to make the same decision we do.
I also think we should never expose the lazy free effort as a parameter, without some type of conversion in between. You could be more explicit and pass the effort to the module callback function and it can choose if it wants to ignore it. I just think we should have more thoughtful apis. From an outside user it's very hard to understand.
yossigo
left a comment
There was a problem hiding this comment.
@chenyang8094 Thank you not just for this PR, but also for your patience with this process! Getting to an agreement on names and API interfaces is always a challenge (and don't get me wrong on this, it is for all the right reasons).
I didn't get a chance to voice my opinion before. I totally agree with passing the key to unlink, but I don't think it's really necessary for free_effort (although not feeling strongly against it).
@madolson I think you have a valid argument here, but I think that however we turn it the API will still end up with extra coupling. Even if we expose LAZYFREE_THRESHOLD we still have this rigid contract that will be hard to break, and if we just let modules decide we lose all control. So, as none of the options is ideal I'd stick with this one which is simple. We do have to document it properly though.
|
so in my eyes, only thing that's left is a call to |
|
@yossigo i'll buy the simplicity argument. I do think we should say returning 0 should be explicitly documented that it calls the async free function every time. Seem reasonable? |
@oranagra @yossigo Yes, I understand very well, it is very necessary to do things right. @oranagra @yossigo Thanks for your review, I also thought that |
madolson
left a comment
There was a problem hiding this comment.
API lgtm, I did not look at the code much.
|
@chenyang8094 good point about FLUSHDB, and more importantly FLUSHDB ASYNC. @MeirShpilraien WDYT? maybe it should be documented that when the module gets the flushdb event hook it should expect the unexpected? |
|
@oranagra so on flushbd async we will not get the unlink callback and module should use the flush event to handle such case? LGTM but we need to make sure to document it. |
|
@chenyang8094 trying to sum up what's left (64 comments, LOL):
|
Add two optional callbacks to the RedisModuleTypeMethods structure, which is `free_effort` and `unlink`. the `free_effort` callback indicates the effort required to free a module memory. Currently, if the effort exceeds LAZYFREE_THRESHOLD, the module memory may be released asynchronously. the `unlink` callback indicates the key has been removed from the DB by redis, and may soon be freed by a background thread. Add `lazyfreed_objects` info field, which represents the number of objects that have been lazyfreed since redis was started. Add `RM_GetTypeMethodVersion` API, which return the current redis-server runtime value of `REDISMODULE_TYPE_METHOD_VERSION`. You can use that when calling `RM_CreateDataType` to know which fields of RedisModuleTypeMethods are gonna be supported and which will be ignored.
|
merged. |
Add two optional callbacks to the RedisModuleTypeMethods structure, which is `free_effort` and `unlink`. the `free_effort` callback indicates the effort required to free a module memory. Currently, if the effort exceeds LAZYFREE_THRESHOLD, the module memory may be released asynchronously. the `unlink` callback indicates the key has been removed from the DB by redis, and may soon be freed by a background thread. Add `lazyfreed_objects` info field, which represents the number of objects that have been lazyfreed since redis was started. Add `RM_GetTypeMethodVersion` API, which return the current redis-server runtime value of `REDISMODULE_TYPE_METHOD_VERSION`. You can use that when calling `RM_CreateDataType` to know which fields of RedisModuleTypeMethods are gonna be supported and which will be ignored.
Currently, 'dbAsyncDelete' does not specifically deal with the module data type, so the memory allocated in the module can only be freed synchronously. This is unfriendly for modules that will allocate a large amount of memory. In extreme cases, synchronous release of memory is easy to cause RT jitter. Therefore, this PR is to make the memory allocated by the module data type also have the opportunity to be freed asynchronously.
This PR adds an optional API to the RedisModuleTypeMethods structure, which is free_effort (its type is RedisModuleTypeFreeEffortFunc), which indicates the effort required to free a module memory. Currently, if the effort exceeds LAZYFREE_THRESHOLD, the module memory It may be released asynchronously.
At the same time, this PR also added a member'lazyfreed_objects' to INFO Memory (behind the 'lazyfree_pending_objects'
member), which represents the number of objects that have been lazyfree since redis was started, and 'lazyfree_pending_objects' indicates the pressure of the current memory asynchronous free queue. I think the indicator 'lazyfreed_objects' is still very useful in certain situations. For example, using the 'lazyfreed_objects' information in the TCL test corresponding to this PR can confirm that the memory of this module is freed asynchronously, rather than simply judging the memory drop as in 'lazyfree.tcl' . There is also the lazyfree mechanism that uses 'bio_mutex' to synchronize with the BIO thread, which will have some lock overhead, so we usually don’t want too many objects to release memory through lazyfree, and we can provide us with monitoring the growth trend of 'lazyfreed_objects' Good reference information, and if LAZYFREE_THRESHOLD becomes a configurable parameter in the future, the growth rate of 'lazyfreed_objects' can also be controlled by adjusting the value of LAZYFREE_THRESHOLD.