Skip to content

Conversation

cyningsun
Copy link
Contributor

Close #3169

mapleFU
mapleFU previously approved these changes Sep 20, 2025
Copy link
Member

@mapleFU mapleFU left a comment

Choose a reason for hiding this comment

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

General lgtm

Comment on lines 236 to 237
// Explicit construct a rocksdb::Slice to avoid the implicit conversion from
// PinnableSlice to Slice.
Copy link
Member

Choose a reason for hiding this comment

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

Why? I don't understand this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mapleFU thanks for your comment!

If you're referring to the comment (A):
I agree it's overly verbose. It can indeed be cleaned up.

If you're referring to the explicit construction (B):
The explicit construction is mainly for safety - since Decode() calls remove_prefix() internally, it would modify the original pin_values[i] state if passed directly. It's better to create a temporary Slice copy, leaving original data intact, which matches the pattern used elsewhere in the codebase.

What's your take on this? I'd be curious to hear your perspective

Copy link
Member

Choose a reason for hiding this comment

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

The command is ok to me but I think "it would modify the original pin_values[i] state if passed directly" it's more specifically rather than "implicit conversion"?

Copy link
Contributor Author

@cyningsun cyningsun Sep 20, 2025

Choose a reason for hiding this comment

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

  [[nodiscard]] virtual rocksdb::Status Decode(Slice *input);
  [[nodiscard]] rocksdb::Status Decode(Slice input);

Sorry for my previous incorrect explanation. Actually, due to call the second function of Metadata and parameter is passed by value, an implicit conversion occurs, which modifies the copied value rather than pin_values itself. Therefore, the comment should be accurate. The explicit construction has no obvious different with implicit construction, that's what I mean overly verbose

Copy link
Contributor Author

@cyningsun cyningsun Sep 20, 2025

Choose a reason for hiding this comment

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

Comment is moved from caller to function declaration

if (!statuses[i].ok() && !statuses[i].IsNotFound()) return statuses[i];
if (statuses[i].ok()) {
Metadata metadata(kRedisNone, false);
auto s = metadata.Decode(rocksdb::Slice(pin_values[i].data(), pin_values[i].size()));
Copy link
Member

Choose a reason for hiding this comment

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

However, PinnableSlice wouldn't be changed if pass by value?

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, that's correct

if (!statuses[i].ok() && !statuses[i].IsNotFound()) return statuses[i];
if (statuses[i].ok()) {
Metadata metadata(kRedisNone, false);
auto s = metadata.Decode(&rocksdb::Slice(pin_values[i].data(), pin_values[i].size()));
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm why not just

auto s = metadata.Decode(pin_values[i]);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

 // Explicit construct a rocksdb::Slice to avoid the implicit conversion from
// PinnableSlice to Slice.

In fact, it can. The initial implementation is referred to MDel's implementation. That's why there is a annotation. Upon our discussion and analysis, it is indeed not very reasonable. Let me make some modifications

Copy link


private:
// Already internal keys
[[nodiscard]] rocksdb::Status existsInternal(engine::Context &ctx, const std::vector<std::string> &keys, int *ret);
Copy link
Member

Choose a reason for hiding this comment

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

I'm so sorry to comment this again and again. But why not:

[[nodiscard]] rocksdb::Status existsInternal(engine::Context &ctx, const nonstd::span<Slice> &keys, int *ret) {
  // From benchmark, ...
  if (keys.size() == 1) {
     // Get..
  } else {
    // MultiGet
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mapleFU Thanks so much for commenting—please don't be sorry! I really welcome the discussion, and I'm glad we're digging into the details.

You're absolutely right, and in fact, I completely agree with your thinking. The implementation you're proposing is a more conservative and robust approach. If I were working on this within a company codebase, I would likely advocate for the same solution you've outlined, for a few key reasons:

  • In real-world usage, the number of keys for EXISTS maybe smaller than for MGET or MDEL.
  • With a smaller number of keys and a higher in-memory hit rate, the potential performance difference between a single Get and a MultiGet becomes more significant.

The main reason I went with the current implementation are:

However, you've made a very valid point that deserves more thorough testing. Before we merge this, let me run some more benchmarks. I'll specifically focus on scenarios with a higher cache hit rate and very small key lists to better quantify the impact. Once I have those results, we can make a more data-driven decision on whether to keep the current unified implementation or adopt the optimized approach you suggested for existsInternal.

I really appreciate you pushing for the best solution! Let's sync up again after the next round of tests.

@PragmaTwice PragmaTwice changed the title feat: optimize command EXISTS using MultiGet perf: optimize command EXISTS using MultiGet Sep 20, 2025

std::vector<rocksdb::Status> statuses(slice_keys.size());
std::vector<rocksdb::PinnableSlice> pin_values(slice_keys.size());
storage_->MultiGet(ctx, ctx.DefaultMultiGetOptions(), metadata_cf_handle_, slice_keys.size(), slice_keys.data(),
Copy link
Member

Choose a reason for hiding this comment

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

Following your benchmark here #3169 (comment), should we use Get instead if keys.size() is 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @PragmaTwice, we actually just discussed this exact point in another thread. The short answer is that I agree it's a valid optimization. I'll running additional tests for high cache-hit scenarios before deciding whether to implement the conditional Get for a single key.

You can see the full discussion here: #3194 (comment)

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

Successfully merging this pull request may close these issues.

Performance Question: Could EXISTS command benefit from MultiGet optimization?
5 participants