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

Mutate API #87

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open

Mutate API #87

wants to merge 32 commits into from

Conversation

rustworthy
Copy link
Collaborator

@rustworthy rustworthy commented Nov 19, 2024

addresses #61

this should also "unblock" #88


This change is Reviewable

Copy link

codecov bot commented Nov 20, 2024

Codecov Report

Attention: Patch coverage is 82.63158% with 33 lines in your changes missing coverage. Please review.

Project coverage is 68.7%. Comparing base (6ff7d60) to head (29f0259).
Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
src/proto/client/mutation.rs 62.6% 31 Missing ⚠️
src/worker/mod.rs 81.8% 2 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
src/proto/client/mod.rs 90.0% <ø> (+2.9%) ⬆️
src/proto/mod.rs 72.7% <ø> (ø)
src/proto/single/cmd.rs 97.9% <100.0%> (+0.4%) ⬆️
src/proto/single/mod.rs 99.2% <100.0%> (+5.0%) ⬆️
src/proto/single/mutation.rs 100.0% <100.0%> (ø)
src/proto/single/utils.rs 99.1% <100.0%> (+<0.1%) ⬆️
src/worker/mod.rs 84.0% <81.8%> (-1.1%) ⬇️
src/proto/client/mutation.rs 62.6% <62.6%> (ø)

... and 2 files with indirect coverage changes

@rustworthy rustworthy changed the title [WIP] Mutate API Mutate API Nov 23, 2024
@rustworthy rustworthy requested a review from jonhoo November 23, 2024 09:19
Copy link
Owner

@jonhoo jonhoo left a comment

Choose a reason for hiding this comment

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

Haven't gotten all the way through this, but racked up a few comments so figured I'd leave them with you!

src/lib.rs Outdated
Comment on lines 108 to 111
pub use crate::proto::{
Client, Connection, DataSnapshot, FaktoryState, Job, JobBuilder, JobId, Reconnect,
ServerSnapshot, WorkerId,
Client, Connection, DataSnapshot, FaktoryState, Job, JobBuilder, JobId, MutationFilter,
MutationFilterBuilder, MutationTarget, Reconnect, ServerSnapshot, WorkerId,
};
Copy link
Owner

Choose a reason for hiding this comment

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

This is starting to be a lot of top-level exports. How do you feel about starting to group some of them the way we've done with the pub mod ent below? Like pub mod mutate for example? Then we can also rename them to avoid the Mutation name prefix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added namespace and - indeed - prefix no longer needed in the public api

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

though after some refactoring there are only two members left in that namespace :)

Ok(w.write_all(b"INFO\r\n").await?)
}
}
self_to_cmd!(Info);
Copy link
Owner

Choose a reason for hiding this comment

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

Nice, I like the macro expansion here.

pub(crate) struct MutationAction<'a> {
pub(crate) cmd: MutationType,
pub(crate) target: MutationTarget,
#[serde(skip_serializing_if = "filter_is_empty")]
Copy link
Owner

Choose a reason for hiding this comment

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

Is an empty-but-set mutation filter actually == to an unset filter? In some systems, the two have different semantics (usually, unset means "use the default").

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A nil filter and an empty one are treated in the common manner - as if we were matching everything in the targeted queue (they are wildcards effectively). Added a comment.

I see what you mean, like what if the default behavior changes? well, as one put it, where possible, your interface should be intuitive enough that if the user has to guess, they usually guess correctly. Looking at the filter fields (jids, regexp, jobtypes) I am guessing that leaving those empty should be a considered a wildcard or should cause an error encouraging me to be specific. In the same manner, omitting the filter - I am guessing - should be either a wildcard or (yet again) should error back.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

P.S.: to give an example of a broken law of least astonishment: in case of an unset filter, we system would be targeting the jobs whose jobtype is a string literal "general" 😢

Comment on lines 13 to 14
/// This method will immediately move the jobs from the targeted set (see [`MutationTarget`])
/// to their queues. This will apply to the jobs satisfying the [`filter`](crate::MutationFilter).
Copy link
Owner

Choose a reason for hiding this comment

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

I'm having a hard time following what this operation actually does. For example, with MutationTarget::Scheduled, what does it mean for the jobs to "immediately be moved to their queues"? They're already in queues?

Copy link

Choose a reason for hiding this comment

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

The scheduled set is not a queue, it’s time-sorted data structure in Redis. The job is moved from that structure into its queue for a worker to fetch.

Copy link
Collaborator Author

@rustworthy rustworthy Feb 27, 2025

Choose a reason for hiding this comment

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

Actually they are not in queues, rather in dedicated sets where they are "pending" basically.
Updated the docs and also added an example scenario

P.S. Left the draft reply open in the browser tab and did not see Mike's comment when publishing. "Multiversion concurrency control" made my comment look stupid 😓

#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum MutationTarget {
Copy link
Owner

Choose a reason for hiding this comment

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

Is this actually specific to mutations? Feels like a general purpose job set specifier to me.

It's also weird to me that the "mutation target" isn't just another filter, given that seems to be the function it has.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  1. the job sets are only used in mutation API context and also declared in the mutate module in the Go bindings. As per another thread, we decided to remove the Mutation prefix from the constucts in the mutation namespace, so it's now simply Target. But I see what you mean this could be in theory simply JobSet or JobSetKind.

  2. well, this where I was hesitant for a while, but decided to follow the Go bindings way. open to revisiting this place though

/// client.requeue(MutationTarget::Retries, &filter).await.unwrap();
/// # });
/// ```
pub async fn requeue<'a, F>(&mut self, target: MutationTarget, filter: F) -> Result<(), Error>
Copy link
Owner

Choose a reason for hiding this comment

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

The argument name target makes me think that the jobs will be moved to this, but I don't think that's the case. Isn't it more like source or candidate_job_set or something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

At the time of writing I was in the Mutate API flow and my idea was that we were mutating those dedicated sets and they were the mutation target, but when I now revisit this bit I see that it's confusing indeed.

I love source as parameter name, but then I will need to rename the Target to JobSet, otherwise we will get source: Target 😃 in the signature.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

On the second thought, candidate_job_set looks more universal since we also have such methods as Client::discard and Client::kill and Client::clear

pub kind: Option<&'a str>,

/// Ids of jobs to target.
#[serde(skip_serializing_if = "Option::is_none")]
Copy link
Owner

Choose a reason for hiding this comment

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

Should it be send if empty?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No need to. Even though is does not change the outcome, we should be consistent. So I added a utility trait called Empty (only for usage within the crate) so that we can do:

#[serde(skip_serializing_if = "Empty::is_empty")]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also added a few unit tests for Filter and MutationAction for visualization purposes solely

Comment on lines 30 to 33
/// As of Faktory version 1.9.2, if [`MutationFilter::pattern`] and/or [`MutationFilter::kind`]
/// is specified, the values in [`MutationFilter::jids`] will not be taken into account by the
/// server. If you want to filter by `jids`, make sure to leave other fields of the filter empty
/// or use dedicated methods like [`Client::requeue_by_ids`].
Copy link
Owner

Choose a reason for hiding this comment

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

If that's the case, I wonder if we shouldn't derive builder and instead just have specific constructors (for now) for the combinations that do work. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think, this is the right call. If I do not read the docs (which I definitely can) and specify both kind and jids on the builder (thinking I am helping the server find jobs in question faster), I am getting into a situation where all the jobs of that kind in the targeted set are affected. If the server ignored pattern and kind when jids are there (which is a trivial fix as Mike points out here, there would be no harm actually.

So I think the constructors will be:

Filter::by_ids;
Filter::by_kind;
Filter::by_pattern;
Filter::by_kind_and_pattern;

And the fields on that struct should be made private apparently.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

went with Filter::from_<attr> rather than Filter::by_<attr>. the latter is better for a verb I think, like Client::filter_by_ids.

rustworthy and others added 6 commits December 31, 2024 15:01
* Make Failure public

* Update changelog

* Get more info from panicking

* Check different ways a job can fail

* Add test clean up

* Account for re-queued jobs

* Check job id

* Collect all kinds of handler failure

* Add Job::failure_message. Check all error messages

* Add to CHANGELOG.md. Fix clippy warning

* Update tests/real/community.rs

Co-authored-by: Jon Gjengset <[email protected]>

* Update src/proto/single/mod.rs

Co-authored-by: Jon Gjengset <[email protected]>

* Fix docs to Failure

* Nuke Job::failure_message

* Nuke Job::failure_message

* Restore comment in test_panic_and_errors_in_handler test

* Add to the tests docs

* Do not make Failure::kind pub

---------

Co-authored-by: Jon Gjengset <[email protected]>
@rustworthy
Copy link
Collaborator Author

Haven't gotten all the way through this, but racked up a few comments so figured I'd leave them with you!

Sorry it took me a while to iterate on this PR - got some things to do in the wewerewondering project and also was moving countries with my partner and cats (since we implement Send 😄 )

Please have a look at what we got.

@rustworthy rustworthy requested a review from jonhoo February 28, 2025 19:31
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.

3 participants