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

add dense_set.SetExpiryTime in preparation for fieldexpire #3780

Merged
merged 3 commits into from
Oct 1, 2024

Conversation

NegatioN
Copy link
Contributor

@NegatioN NegatioN commented Sep 24, 2024

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?

@NegatioN NegatioN force-pushed the core-updates branch 2 times, most recently from 72c0eed to 125add6 Compare September 24, 2024 12:52
@romange
Copy link
Collaborator

romange commented Sep 24, 2024

Thank you @NegatioN .
Seems that you need to rebase your branch - there are conflicts there.

@romange romange added hacktoberfest Participates in Hacktoberfest hacktoberfest-accepted labels Sep 24, 2024
@NegatioN NegatioN marked this pull request as ready for review September 25, 2024 08:08
@NegatioN
Copy link
Contributor Author

Conflicts resolved @romange :)

Copy link
Contributor

@kostasrim kostasrim left a 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.cc Outdated Show resolved Hide resolved
src/core/dense_set.cc Outdated Show resolved Hide resolved
src/core/string_set.cc Outdated Show resolved Hide resolved
if (HasExpiry()) {
owner_->ObjUpdateExpireTime(curr_entry_->GetObject(), ttl_sec);
} else {
owner_->ObjReplace(curr_entry_->GetObject(), ttl_sec);
Copy link
Contributor

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()

Copy link
Contributor Author

@NegatioN NegatioN Sep 26, 2024

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?

Copy link
Contributor

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/dense_set.h Outdated Show resolved Hide resolved
@@ -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) {
Copy link
Contributor

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/dense_set.h Outdated Show resolved Hide resolved
@@ -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
Copy link
Contributor

@kostasrim kostasrim Sep 26, 2024

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)

Copy link
Contributor Author

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 :)

Copy link
Contributor

@kostasrim kostasrim Sep 27, 2024

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)

Copy link
Contributor Author

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_set.cc Outdated Show resolved Hide resolved
absl::little_endian::Store32(valptr + 8, at);
}

void StringMap::ObjReplace(const void* obj, uint32_t ttl_sec) {
Copy link
Collaborator

@romange romange Sep 26, 2024

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.

  1. What you really want is AddExpiry semantics on the EXISTING object that DOES NOT have expiry.
  2. 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.
  3. 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.
  4. Once you do that, you can swap the pointers inside DensePtr and update DensePtr tag bits to add the meta information about TTL.

Copy link
Collaborator

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.

Copy link
Collaborator

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

Copy link
Contributor

@kostasrim kostasrim Sep 27, 2024

Choose a reason for hiding this comment

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

Two things:

  1. 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.
  2. clone + swap is almost identical to AddOrReplace when the second behaves as a Replace.

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

Copy link
Contributor Author

@NegatioN NegatioN Sep 27, 2024

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.

Copy link
Contributor Author

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());
}

Copy link
Contributor

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 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, just jamming :)

@@ -187,6 +187,17 @@ class DenseSet {
return curr_entry_->HasTtl() ? owner_->ObjExpireTime(curr_entry_->GetObject()) : UINT32_MAX;
}

void SetExpiryTime(uint32_t ttl_sec) {
Copy link
Collaborator

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

Copy link
Collaborator

@romange romange left a 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

@kostasrim
Copy link
Contributor

@NegatioN Test failing :)

2024-09-28T15:29:40.3732662Z 28: [ RUN      ] StringSetTest.SetFieldExpireNoHasExpiry
2024-09-28T15:29:40.3735090Z 28: F20240928 15:29:40.372570 20977 string_set.cc:134] Check failed: ttl_sec > 0u (0 vs. 0) 
2024-09-28T15:29:40.3753894Z 28: *** Check failure stack trace: ***
2024-09-28T15:29:40.3789973Z 28:     @           0x541d4d  google::LogMessage::Fail()
2024-09-28T15:29:40.3790743Z 28:     @           0x541c94  google::LogMessage::SendToLog()
2024-09-28T15:29:40.3791495Z 28:     @           0x541489  google::LogMessage::Flush()
2024-09-28T15:29:40.3792468Z 28:     @           0x5451a0  google::LogMessageFatal::~LogMessageFatal()
2024-09-28T15:29:40.3797463Z 28:     @           0x435378  dfly::StringSet::MakeSetSds()
2024-09-28T15:29:40.3804486Z 28:     @           0x43527d  dfly::StringSet::ObjectClone()
2024-09-28T15:29:40.3808152Z 28:     @           0x41e7ed  dfly::DenseSet::IteratorBase::SetExpiryTime()
2024-09-28T15:29:40.3816267Z 28:     @           0x418d49  dfly::StringSetTest_SetFieldExpireNoHasExpiry_Test::TestBody()
2024-09-28T15:29:40.3823567Z 28:     @           0x4b3604  testing::internal::HandleSehExceptionsInMethodIfSupported<>()
2024-09-28T15:29:40.3828655Z 28:     @           0x4abe81  testing::internal::HandleExceptionsInMethodIfSupported<>()
2024-09-28T15:29:40.3833348Z 28:     @           0x486a94  testing::Test::Run()
2024-09-28T15:29:40.3838515Z 28:     @           0x487563  testing::TestInfo::Run()
2024-09-28T15:29:40.3841551Z 28:     @           0x487f61  testing::TestSuite::Run()
2024-09-28T15:29:40.3849318Z 28:     @           0x497f00  testing::internal::UnitTestImpl::RunAllTests()
2024-09-28T15:29:40.3855626Z 28:     @           0x4b47a7  testing::internal::HandleSehExceptionsInMethodIfSupported<>()
2024-09-28T15:29:40.3859418Z 28:     @           0x4ad195  testing::internal::HandleExceptionsInMethodIfSupported<>()
2024-09-28T15:29:40.3867055Z 28:     @           0x4964fc  testing::UnitTest::Run()

@NegatioN
Copy link
Contributor Author

@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.

@NegatioN
Copy link
Contributor Author

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.
@NegatioN
Copy link
Contributor Author

NegatioN commented Oct 1, 2024

Not to nag, but as soon as this is merged, I can prepare the next PR for adding Fieldexpire and Hexpire 🙂

@kostasrim
Copy link
Contributor

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 ?

@NegatioN
Copy link
Contributor Author

NegatioN commented Oct 1, 2024

@kostasrim Sorry about that, it seems like I misunderstood the significance of DCHECK_GT. Zero is currently a valid input for MakeSetSds (when cloning a object with unset TTL), but I had a check there that demanded it to be GT(0).

Please try rerunning the suite.

@romange
Copy link
Collaborator

romange commented Oct 1, 2024

Yeah, I suggest you build in debug mode locally to catch these things

@romange romange enabled auto-merge (squash) October 1, 2024 08:27
@NegatioN
Copy link
Contributor Author

NegatioN commented Oct 1, 2024

@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 ninja $task, ninja src/all and ctest -V -L DFLY
This is kinda my first contribution to a c++ project, so the tooling is a bit unfamiliar.

@romange romange merged commit 2ab480e into dragonflydb:main Oct 1, 2024
9 checks passed
@NegatioN NegatioN deleted the core-updates branch October 1, 2024 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest Participates in Hacktoberfest hacktoberfest-accepted
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants