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: momento leaderboard client #438

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

Conversation

brayniac
Copy link
Collaborator

Adds client functionality for the Momento Leaderboard API.

The control client isn't necessary/used in the leaderboard client.
The leaderboard client, unlike the cache client, uses a reosurce
oriented pattern. The user calls `leaderboard` on the leaderboard
client to get a leaderboard object for a specific cache- and
leaderboard-name.

They then invoke specific operations on that leaderboard without
needing to pass in the cache- and leaderboard-name.
Refactors the leaderboard to use the method, request, and response
names as per the JS client.
Moves the `Leaderboard` struct and impl into a dedicated mod.
Adds comprehensive integration tests for the leaderboard
client. Applies fixes and refactoring to get tests green.
Ensure the score range doesn't have a nonsensical range, eg (+inf,
-inf).
@malandis malandis requested a review from a team February 5, 2025 00:48
Copy link
Contributor

@kvcache kvcache left a comment

Choose a reason for hiding this comment

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

I ran out of time today about halfway through. I'll give this feedback before continuing to review tomorrow.

src/leaderboard/config/configuration.rs Show resolved Hide resolved
/// Client to work with Momento Leaderboards.
#[derive(Clone, Debug)]
pub struct LeaderboardClient {
data_clients: Vec<SLbClient<InterceptedService<Channel, HeaderInterceptor>>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Momento typically does not use import aliases, and tends to avoid obfuscating abbreviation. Is there a compelling reason to write SLbClient rather than matching the de-facto code style and using the struct's name LeaderboardClient? Alternatively, what does S mean? Can you write a comment explaining this name, or switch to a clearer name?

If you're struggling with the protos squatting on the name, consider aliasing to something clearer like as ProtoLeaderboardClient, or use momento_protos::leaderboard::leaderboard_client as leaderboard_proto; and refer to leaderboard_proto::LeaderboardClient.

(here and everywhere "SLbClient" is defined)

Copy link
Contributor

Choose a reason for hiding this comment

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

I've changed this to qualify from the leaderboard proto mod instead as per your suggestion in 5803c70

src/leaderboard/leaderboard_resource.rs Outdated Show resolved Hide resolved
end_exclusive: u32,
}

impl From<Range<u32>> for RankRange {
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be implemented for RangeInclusive as well?

Copy link
Contributor

@malandis malandis Feb 5, 2025

Choose a reason for hiding this comment

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

Done in d749551. I'm unclear how to convey the unlikely but troubling edge case if a user provides u32::MAX as the end index inclusive and we convert to exclusive. I've just clamped to u32::MAX in my implementation. Any suggestions?

src/leaderboard/leaderboard_resource.rs Outdated Show resolved Hide resolved
Comment on lines +16 to +19
/// Returns the ranked elements in the response.
pub fn elements(&self) -> &[RankedElement] {
&self.elements
}
Copy link
Contributor

Choose a reason for hiding this comment

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

there should be an into_elements(self) as well here to avoid forcing users to clone if they want owned elements.

Copy link
Contributor

@malandis malandis Feb 5, 2025

Choose a reason for hiding this comment

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

Added in e61b87e and d6722e9.

For my benefit, is one or the other preferred/expected/more commonplace? Or should an API have both available? It seems that this depends how many times the caller wants to access the underlying data.

src/leaderboard/messages/data/fetch_by_rank.rs Outdated Show resolved Hide resolved
response
.elements
.iter()
.map(|v| RankedElement {
Copy link
Contributor

Choose a reason for hiding this comment

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

getrank, fetchbyscore, fetchbyrank - is v the same type in all 3? Just impl From<Whatever V Is> for RankedElement and replace this line and the other 2 with .map(|v| v.into()) instead of repeating the same marshaling stanza

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice. Fixed in 305d247.

}

/// Validates the score range.
pub fn validate(&self) -> MomentoResult<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this public? Why is it possible to create an invalid ScoreRange? Shouldn't new() return a MomentoResult<Self>?

Should new just .clamp min and max to f64::MIN and f64::MAX instead of making it an error to write 1.0..?

Copy link
Contributor

Choose a reason for hiding this comment

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

1.0.. not working was an oversight my on part. I've added From<RangeFrom<f64>> and From<RangeTo<f64>> to handle those cases in 0576f7a

Copy link
Contributor

@malandis malandis Feb 5, 2025

Choose a reason for hiding this comment

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

The error cases are when the user passes in min=fp64::POS_INFINITY or max=fp64::NEG_INFINITY. We translate those to the proto Unbounded message. That's why the check val.is_finite() isn't enough before marshalling to Unbounded: if min or max is the wrong infinity, then it's an error.

I can have new return MomentoResult<Self> 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Well maybe not. The top level methods take as input impl Into<ScoreRange>. If we let new return MomentoResult<ScoreRange> then the callers will have to invoke the function with something like client.fetch_by_score((1.0..5.0).into()?), right? Because the From<Range<f64>> implementation is for ScoreRange, not MomentoResult<ScoreRange>. See j1c7c02d

Comment on lines 57 to 61
impl From<Option<ScoreRange>> for ScoreRange {
fn from(val: Option<ScoreRange>) -> Self {
val.unwrap_or_else(ScoreRange::unbounded)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This appears to be unused, and it is unclear to me that unbounded means the same thing as None for a whole score range. If you want to keep it, what's the utility here, and how should this be communicated so it doesn't surprise people that None means Everything?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch -- this was a leftover while I was iterating on the UI. I've removed this in e930128

Previously we used an alias to the leaderboard proto client. We
instead qualify from the proto mod for clarity.
In the configuration we communicated the client timeout as a
"deadline". We refactor to use the name "client_timeout" throughout
the leaderboard code.

More broadly (cache/topics) we will have to rename later.
Since we model the leaderboard resource as an object, we align with
rust naming conventions to use `len` to measure its size.
Adds documentation about the default ordering for fetch by score,
fetch by rank, and get rank.
Renames `MomentoRequest` to `LeaderboardRequest` for clarity. This way
we don't have `MomentoRequest` defined in various places (here and in
the cache client).
Removes helper function and inlines since it's only used in one place.
Implements `From<>` on the proto `RankedElement` for the SDK
`RankedElement` to simplify marshalling code.
@malandis malandis requested a review from kvcache February 6, 2025 22:18
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