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

feat: Store and diststore for GeoRadiusByMember #2350

Merged
merged 14 commits into from
Jan 11, 2024

Conversation

azuredream
Copy link
Contributor

@azuredream azuredream commented Dec 28, 2023

  • Uses Schedule-Execute in GeoRadiusByMember workflow instead of ScheduleSingleHopT()
  • Registers command in WRITE mode instead of READ mode.
  • Write if needed
    Implement georadiusbymember #1548

return cntx->SendError(kWrongTypeErr);
} else if (!result) {
} else if (!member_score) {
cntx->transaction->Conclude();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

member_score is null and fail intermittently as mentioned here: #1548 (comment)

dataset:
geoadd Europe 13.4050 52.5200 Berlin 3.7038 40.4168 Madrid 9.1427 38.7369 Lisbon 2.3522 48.8566 Paris 16.3738 48.2082 Vienna 4.8952 52.3702 Amsterdam 10.7522 59.9139 Oslo 23.7275 37.9838 Athens 19.0402 47.4979 Budapest 6.2603 53.3498 Dublin

Not sure if I have registered this command correctly.

src/server/zset_family.cc Outdated Show resolved Hide resolved

rb->SendLong(smvec.size());
}
if (write_mode) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not think you need this conclude step in the normal flow that does not expect errors. You always know what's your last hop is and that should have Execute(..., true)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The last hop could vary. When STORE is not used, the last hop would be hop 2.
If I can't use conclude in this way, I'll have to add a argument "do_store" for the whole workflow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, after hop 2, every conclude() would have to check do_store. If already called conclude after hop 2, skip and return.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are at most 3 hops
hop1. Getting latitude longitude from member name
hop2. Getting nearby nodes around <latitude ,longitude >
hop3. write to store key if needed

Copy link
Collaborator

Choose a reason for hiding this comment

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

Conclude is also a hop. Seem my previous comment at line 2947

@azuredream
Copy link
Contributor Author

azuredream commented Dec 31, 2023

Now the code works as expected.
Will refine error messages and add more test cases.

@azuredream
Copy link
Contributor Author

azuredream commented Jan 1, 2024

According to Redis' behavior, refined the error messages. Added more test cases.
Ready for review.

@azuredream azuredream marked this pull request as ready for review January 1, 2024 08:43
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.

Thanks! I gave some styling/coding comments. Please take a look

}

OpResult<double> OpScore(const OpArgs& op_args, string_view key, string_view member,
bool use_member_not_found = false) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove the argument use_member_not_found, imho, this is not a good practice to dynamically change function behavior in such way. Instead adapt all the callers of the function to cope with MEMBER_NOTFOUND if needed.

@@ -1609,7 +1612,14 @@ OpResult<unsigned> OpRem(const OpArgs& op_args, string_view key, ArgSlice member
return deleted;
}

OpResult<double> OpScore(const OpArgs& op_args, string_view key, string_view member) {
OpResult<double> OpKeyExisted(const OpArgs& op_args, string_view key) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

seems you never return double value from this function. something is off here.

// False: Using transaction->ScheduleSingleHopT()
OpStatus MembersOfAllNeighbors(ConnectionContext* cntx, string_view key, const GeoHashRadius& n,
const GeoShape& shape_ref, GeoArray* ga, unsigned long limit,
bool write_mode = false) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

again, changing function behavior using write_mode is not a good approach, imho.
Instead, I would break this function into smaller function. For example, factor out the preparation code into a separate function that just returns range_specs.

return false;
vector<OpResult<vector<ScoredArray>>> result_arrays;
if (write_mode) {
auto cb = [&](Transaction* t, EngineShard* shard) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would move the transaction related code to the caller (GeoSearchStoreGeneric). Basically, the division to the functions is sub-optimal imho.

@@ -3042,24 +3148,51 @@ void ZSetFamily::GeoRadiusByMember(CmdArgList args, ConnectionContext* cntx) {
string_view key = ArgS(args, 0);
// member to latlong, set shape.xy
string_view member = ArgS(args, 1);
bool member_score_set = false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Every Dragonfly command has the command parsing part and the execution part. We never start the execution before the parsing is complete and all the arguments are validated.
it seems you start the transaction execution before the parsing is completed. We should avoid it if possible - this just complicates the function flow.

@azuredream
Copy link
Contributor Author

Thanks for reviewing.
I've updated my PR based on your suggestions.

Couldn't fully remove "write_mode", but it's only used in GeoSearchStoreGeneric(). It doesn't change function behavior a lot, just relate with using Execute() or ScheduleSingleHopT()

@azuredream
Copy link
Contributor Author

Hi @romange
Please let me know if there is any question on this PR.
Thanks!

auto cb = [&](Transaction* t, EngineShard* shard) {
return OpKeyExisted(t->GetOpArgs(shard), key);
};
OpResult<void> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not think the implementation of this function is correct. You use ScheduleSingleHop method on the transaction but this method is intended for operations that require only a single hop. Here you call it multiple times - which breaks the atomicity of the whole command and probably incorrect in general. The correct way is to use Schedule() once, and then Execute(..., false) one or more times and finalize with Execute(...., true) or Conclude method in case the transaction finishes prematurely.

// True: Using transaction->Execute()
// False: Using transaction->ScheduleSingleHopT()
// store_key: (Valid if geo_ops.store==GeoStoreType::kNoStore) Store result to store_key
void GeoSearchStoreGeneric(ConnectionContext* cntx, const GeoShape& shape_ref, string_view key,
Copy link
Collaborator

Choose a reason for hiding this comment

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

this function is always called on already scheduled transactions. That means it never should call ScheduleSingleHop because this function is only intended to be used for non-scheduled transactions

@azuredream
Copy link
Contributor Author

Updated.
That simplified the workflow a lot.

This is my first time working on async code. Appreciate your patience!

}
return OpStatus::OK;
};
cntx->transaction->Execute(std::move(store_cb), false);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again, this hop is necessary a last hop, so here you should call Execute(..., true);

}
cntx->transaction->Conclude();
Copy link
Collaborator

Choose a reason for hiding this comment

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

you can move this call into "nostore branch"

Copy link
Collaborator

Choose a reason for hiding this comment

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

In fact you can decide how to call Execute at line 2895 based on whether it's nostore or not, i.e. for nostore option - to conclude it there, and in this case you can eliminate the call to Conclude altogether

// if no matching results, the user gets an empty reply.
if (ga.empty()) {
rb->SendNull();
cntx->transaction->Conclude();
Copy link
Collaborator

Choose a reason for hiding this comment

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

what's the expected behaviour for no matched result together with STORE option? do we have a test for that? are we compliant with redis behavior in this case?

@azuredream
Copy link
Contributor Author

azuredream commented Jan 9, 2024

Updated.

Added one more test case where no matching result in search area. This can only happen in GEOSEARCH FROMLONGLAT because searching by member always has at least one result(the member itself).
Redis returns an empty array in this case, so I updated the code to do the same.

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.

Thank you very much for progressing with this!

@azuredream
Copy link
Contributor Author

You are very welcome.
Please let me know if you need GEOSEARCHSTORE as well.

BTW, if you are planning to implement vector related functions, I'll be glad to help.

@romange romange merged commit d7f4265 into dragonflydb:main Jan 11, 2024
7 checks passed
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.

2 participants