-
Notifications
You must be signed in to change notification settings - Fork 948
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
add dense_set.SetExpiryTime in preparation for fieldexpire #3780
Conversation
72c0eed
to
125add6
Compare
Thank you @NegatioN . |
125add6
to
e11c3b7
Compare
Conflicts resolved @romange :) |
5391b26
to
2b31a65
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @NegatioN ! Thank you for your contribution 🚀
I left some comments which will reduce the size of this PR :)
Let me know if you have any questions
src/core/dense_set.h
Outdated
if (HasExpiry()) { | ||
owner_->ObjUpdateExpireTime(curr_entry_->GetObject(), ttl_sec); | ||
} else { | ||
owner_->ObjReplace(curr_entry_->GetObject(), ttl_sec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
owner_->AddOrUpdate()
and you can delete the function ObjReplace()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm to do owner_->AddOrUpdate()
I would have to expose that as a new virtual function on dense_set
though? And make that function for StringSet
.
Which I can obviously do, as it would remove a code-path on string_map
, but it just seems like your comment assumes AddOrUpdate
already exists, which confuses me a bit.
Or maybe it's because this change relies on your previous suggestion of also moving SetExpireTime
into StringMap
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was not very clear with this -- I wanted SetExpiryTime()
to be a member of the derived class and not a member of the iterator DenseSet::Iterator::
. Based on Roman's comments, my suggestion is to use AddOrUpdate()
here + make it virtual since Roman does not want to implement this in the derived class.
He also suggested a different approach below (see his comment below). I replied there. Let him decide out of the two approaches which one he prefers and we move on. Both of them do not require a lot of changes and they should be pretty quick to implement.
Also @NegatioN again thank you for your contribution and for your patience with this 🙏 🚀
src/core/score_map.cc
Outdated
@@ -128,6 +128,14 @@ uint32_t ScoreMap::ObjExpireTime(const void* obj) const { | |||
return UINT32_MAX; | |||
} | |||
|
|||
void ScoreMap::ObjReplace(const void* obj, uint32_t ttl_sec) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove this :)
src/core/string_set.cc
Outdated
@@ -129,6 +115,29 @@ uint32_t StringSet::ObjExpireTime(const void* str) const { | |||
return absl::little_endian::Load32(ttlptr); | |||
} | |||
|
|||
void StringSet::ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) { | |||
DCHECK_GT(ttl_sec, 0u); // ttl_sec == 0 would mean find and delete immediately |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used by both StringSet
&& StringMap
. Maybe worth extracting this into a common function and call it here to avoid code duplication ? (the only difference I see is the +8
above)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very doable, but where would that common code live? I'm not so familiar with the code-base :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well this just updates the Expiry time of an sds
object. A free standing function / helper that accepts a stride
parameter (the + 8
in the example). Or even a function within DenseSet
. (Although personally I prefer helpers to be defined outside of a class but again that's a preference not a hard requirement)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put this in sds_util::SdsUpdateExpireTime
for now. It might have implications for ObjUpdateExpireTime
too, but I'll defer that change until we're in agreement on all the other points :)
src/core/string_map.cc
Outdated
absl::little_endian::Store32(valptr + 8, at); | ||
} | ||
|
||
void StringMap::ObjReplace(const void* obj, uint32_t ttl_sec) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand what you are trying to do but I think it can be simplified.
- What you really want is
AddExpiry
semantics on the EXISTING object that DOES NOT have expiry. - Which means you already have the iterator to the object, or even stronger - the pointer to the hashtable entry/Link that contain that existing object pointer.
- So you do not need to do the whole AddOrReplaceObj flow again - you already have the position (DensePtr). All you need to do is to "clone" the existing object with TTL attached to it.
- Once you do that, you can swap the pointers inside DensePtr and update DensePtr tag bits to add the meta information about TTL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally you will need to free the previous object pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recently added ObjectClone(const void* obj, bool has_ttl)
that clones the object while keeping its ttl property as is.
You can extend its functionality to support bool add_ttl
as well, so that ObjectClone(existing_obj. has_ttl=false, add_ttl=true)
will clone the object that did not have ttl to the object that has (0) ttl and then you can call ObjUpdateExpireTime
to update it to correct value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two things:
- I do not personally like adding (yet) another boolean in
ObjectClone
. One bool is fine (usually), but at the moment you start adding different bools to an interface things become less and less clear. clone + swap
is almost identical toAddOrReplace
when the second behaves as aReplace
.
Also, notice that the logic of ObjReplace
is again almost identical to the implementation of AddOrUpdate()
. Also, notice that AddOrUpdate
exists in: StringMap
and ScoreMap
(with the former having a slightly different return type). And finally AddOrUpdate
is missing from StringSet
. So why don't we make AddOrUpdate
a virtual
function ? The only thing we need to change is a) the signature of StringMap::AddOrUpdate
to return a pair (just like ScoredMap
-- and it's not called in many places so the change is quick) and implement this in StringSet
. Now, we can get rid ObjReplace
alltogether and just use AddOrUpdate
in SetExpiryTime
That way SetExpiryTime
can become a member of DenseSet
(and not a member of DenseSet::Iterator
) + keep it in the base class to avoid duplicating in the derived classes (bonus points is also generic as you suggested).
@romange WDYT ?
P.s. not a strong opinion, just discussing my thoughts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for feedback from the both of you :)
My 2 cents: It seems like you're seeing the problem from different angles perhaps. One leaning towards code cleanliness, and the other performance.
I think AddOrUpdate
isn't implemented in StringSet
yet, because Update
is less of a thing in a Set
than it would be in StringMap
or ScoreMap
. And by that metric, we would probably want it to be a private method thats somehow shaded to be public for StringMap
and ScoreMap
if we went that route?
Also, the external public signatures can't match with the StringSet
version, since there's no field
+value
. AddOrUpdate(std::string_view field, std::string_view value, uint32_t ttl_sec = UINT32_MAX)
I do appreciate the desire to make the code-path hit as few lines as possible, since we're only supposed to interact with already existing objects in this case. Too many flags in ObjectClone
makes it a bit yucky too though.
You would also need to add an asterix about "Hey, we didn't really give you a real TTL here, so make sure to set it when you get you sds back" upon taking a closer look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is my best suggestion so far, which tries to keep the complexity of additional branching from add_ttl
as low as possible:
void* StringSet::ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const {
sds src = (sds)obj;
string_view sv{src, sdslen(src)};
if(add_ttl){
return (void*)MakeSetSds(sv, 0);
}
uint32_t at = has_ttl ? ObjExpireTime(obj) : UINT32_MAX;
return (void*)MakeSetSds(sv, at);
}
sds StringSet::MakeSetSds(string_view src, uint32_t ttl_sec) const {
DCHECK_GT(ttl_sec, 0u); // ttl_sec == 0 would mean find and delete immediately
if (ttl_sec != UINT32_MAX) {
uint32_t at = time_now() + ttl_sec;
DCHECK_LT(time_now(), at);
sds newsds = AllocImmutableWithTtl(src.size(), at);
if (!src.empty())
memcpy(newsds, src.data(), src.size());
return newsds;
}
return sdsnewlen(src.data(), src.size());
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait for Roman to decide what he likes the most. I do not have a strong preference :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, just jamming :)
00c02a2
to
38ec7aa
Compare
src/core/dense_set.h
Outdated
@@ -187,6 +187,17 @@ class DenseSet { | |||
return curr_entry_->HasTtl() ? owner_->ObjExpireTime(curr_entry_->GetObject()) : UINT32_MAX; | |||
} | |||
|
|||
void SetExpiryTime(uint32_t ttl_sec) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: lets move the implementation to cc file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
a small comment, and we are good to submit, I think
@NegatioN Test failing :)
|
@kostasrim , I think that must be a test-run from a "not-most-recent" commit? String set seems fine when I compile and run the most recent one. |
I also fixed the nit @romange :) I haven't really cleaned up the commits, because I figured this should probably just be squashed. |
This commit is in preparation for adding FIELDEXPIRE and HEXPIRE.
5afeca0
to
7f2dbce
Compare
Not to nag, but as soon as this is merged, I can prepare the next PR for adding Fieldexpire and Hexpire 🙂 |
units are failing can you plz check ? |
@kostasrim Sorry about that, it seems like I misunderstood the significance of Please try rerunning the suite. |
Yeah, I suggest you build in debug mode locally to catch these things |
@kostasrim For future reference, I know this may seem very basic, but how do I actually build this in debug mode? I've mainly used |
Split up the previous PR #3772 to only target the updates in core as a first pass. @romange
I decided to split the
dense_set.SetExpiryTime
into two branching paths. One which updates the ttl directly, and one which replaces the given object, based on if we find a ttl or not.This seems to be needed, since I can't seem to find the ttl-bits on the StringSet sds when receiving the DensePtr. (They get masked away?)
This code is the basis for adding hexpire and fieldexpire: #3027
I left a few questions in the code, and am open to other comments as well.
Edit: I haven't really landed what return types make sense for all of the functions yet, but maybe void makes the most sense for all update methods, and bool for replace methods?