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

Merged
merged 33 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
11f7be4
feat: momento leaderboard client
brayniac Jan 8, 2025
116a5bb
Merge branch 'main' into leaderboard
brayniac Jan 25, 2025
3ad3ac6
cleanup
brayniac Jan 25, 2025
68a19c7
docs fixes
brayniac Jan 25, 2025
a77d839
refactor: remove control client
malandis Jan 31, 2025
342bae5
refactor: use resource-oriented pattern
malandis Feb 1, 2025
a43f39d
refactor: use standard naming
malandis Feb 1, 2025
91120d0
refactor: move `Leaderboard` to separate module
malandis Feb 1, 2025
3825134
test: test leaderboard client
malandis Feb 4, 2025
50e6e64
chore: simplify score range for unbounded case
malandis Feb 4, 2025
b503cba
chore: validate score range
malandis Feb 5, 2025
3694822
fix: clippy
malandis Feb 5, 2025
6b42e52
chore: remove stale comments
malandis Feb 5, 2025
5803c70
chore: replace `SLbClient` type alias with mod alias
malandis Feb 5, 2025
ada9c97
refactor: rename "deadline" to "client_timeout"
malandis Feb 5, 2025
4f32b66
refactor: rename `Leaderboard::length` to `Leaderboard::len`
malandis Feb 5, 2025
c5800e6
docs: clarify default ordering and meaning
malandis Feb 5, 2025
f31495c
refactor: return &str from accessors
malandis Feb 5, 2025
2e155bd
docs: clarify `MomentoRequest` documentation
malandis Feb 5, 2025
87dc81a
refactor: MomentoRequest -> LeaderboardRequest
malandis Feb 5, 2025
b2a03a9
chore: remove map_and_collect helper function
malandis Feb 5, 2025
e930128
chore: remove unused ScoreRange converter
malandis Feb 5, 2025
e61b87e
chore: add `into_elements` on `FetchResponse`
malandis Feb 5, 2025
d6722e9
chore: add `into_elements` on `GetRankResponse`
malandis Feb 5, 2025
305d247
chore: simplify `RankedElement` conversation from proto with `From`
malandis Feb 5, 2025
cf2604b
fix: clippy
malandis Feb 5, 2025
d749551
chore: add `From<RangeInclusive<>>` impl for `RankRange`
malandis Feb 5, 2025
0576f7a
chore: impl `From<RangeFrom<>>`, `From<RangeTo<>>` for `ScoreRange`
malandis Feb 5, 2025
d9dbe3f
chore: more range converters
malandis Feb 5, 2025
1c7c02d
refactor: make score range validator pub crate
malandis Feb 5, 2025
67da8e0
refactor: remove `IntoIds` in favor of more general `IntoIterator`
malandis Feb 6, 2025
981b5b7
refactor: use more general type for `upsert` argument
malandis Feb 6, 2025
bae8959
Apply suggestions from code review
malandis Feb 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ doc = false


[dependencies]
momento-protos = { version = "0.119.4" }
momento-protos = { version = "0.120.0" }
log = "0.4"
hyper = { version = "0.14" }
h2 = { version = "0.3" }
Expand Down
88 changes: 88 additions & 0 deletions src/leaderboard/config/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use std::time::Duration;

use crate::config::transport_strategy::TransportStrategy;
malandis marked this conversation as resolved.
Show resolved Hide resolved

/// Configuration for a Momento leaderboard client.
///
/// Static, versioned configurations are provided for different environments:
/// ```
/// use momento::leaderboard::configurations;
///
/// /// Use laptop for local development
/// let developer_config = configurations::Laptop::latest();
///
/// /// Use in_region for a typical server environment
/// let server_config = configurations::InRegion::latest();
/// ```
/// If you have specific requirements, configurations can also be constructed manually:
/// ```
/// use std::time::Duration;
/// use momento::leaderboard::Configuration;
/// use momento::config::grpc_configuration::{GrpcConfiguration, GrpcConfigurationBuilder};
/// use momento::config::transport_strategy::TransportStrategy;
///
/// let config = Configuration::builder()
/// .transport_strategy(
/// TransportStrategy::builder()
/// .grpc_configuration(
/// GrpcConfiguration::builder()
/// .deadline(Duration::from_millis(1000))
/// )
/// );

#[derive(Clone, Debug)]
pub struct Configuration {
malandis marked this conversation as resolved.
Show resolved Hide resolved
/// Low-level options for network interactions with Momento.
pub(crate) transport_strategy: TransportStrategy,
}

impl Configuration {
/// First level of constructing a LeaderboardClient configuration. Must provide a [TransportStrategy] to continue.
pub fn builder() -> ConfigurationBuilder<NeedsTransportStrategy> {
ConfigurationBuilder(NeedsTransportStrategy(()))
}

/// Returns the duration the client will wait before terminating an RPC with a DeadlineExceeded error.
pub fn client_timeout(&self) -> Duration {
self.transport_strategy.grpc_configuration.deadline
}
}

/// The initial state of the ConfigurationBuilder.
pub struct ConfigurationBuilder<State>(State);

/// The state of the ConfigurationBuilder when it is waiting for a transport strategy.
pub struct NeedsTransportStrategy(());

/// The state of the ConfigurationBuilder when it is ready to build a Configuration.
pub struct ReadyToBuild {
transport_strategy: TransportStrategy,
}

impl ConfigurationBuilder<NeedsTransportStrategy> {
/// Sets the transport strategy for the Configuration and returns
/// the ConfigurationBuilder in the ReadyToBuild state.
pub fn transport_strategy(
self,
transport_strategy: impl Into<TransportStrategy>,
) -> ConfigurationBuilder<ReadyToBuild> {
ConfigurationBuilder(ReadyToBuild {
transport_strategy: transport_strategy.into(),
})
}
}

impl ConfigurationBuilder<ReadyToBuild> {
/// Constructs the Configuration with the given transport strategy.
pub fn build(self) -> Configuration {
Configuration {
transport_strategy: self.0.transport_strategy,
}
}
}

impl From<ConfigurationBuilder<ReadyToBuild>> for Configuration {
fn from(builder: ConfigurationBuilder<ReadyToBuild>) -> Configuration {
builder.build()
}
}
138 changes: 138 additions & 0 deletions src/leaderboard/config/configurations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::time::Duration;

use crate::config::grpc_configuration::GrpcConfiguration;
use crate::config::transport_strategy::TransportStrategy;
use crate::leaderboard::Configuration;

/// Provides defaults suitable for a medium-to-high-latency dev environment. Permissive timeouts
/// and relaxed latency and throughput targets.
pub struct Laptop {}

impl Laptop {
/// Returns the latest prebuilt configuration.
/// This is the recommended configuration for most users.
///
/// NOTE: this config may change in future releases to take advantage of improvements
/// we identify for default configurations.
#[allow(dead_code)]
pub fn latest() -> impl Into<Configuration> {
Laptop::v1()
}

/// Returns the v1 prebuilt configuration.
///
/// Versioning the prebuilt configurations allows users to opt-in to changes in the default
/// configurations. This is useful for users who want to ensure that their application's
/// behavior does not change unexpectedly.
pub fn v1() -> impl Into<Configuration> {
Configuration::builder().transport_strategy(
TransportStrategy::builder().grpc_configuration(
GrpcConfiguration::builder()
.deadline(Duration::from_millis(15000))
.enable_keep_alives_with_defaults(),
),
)
}
}

/// Provides defaults suitable for an environment where your client is running in the same
/// region as the Momento service. It has more aggressive timeouts than the laptop config.
pub struct InRegion {}

impl InRegion {
/// Returns the latest prebuilt configuration.
/// This is the recommended configuration for most users.
///
/// NOTE: this config may change in future releases to take advantage of improvements
/// we identify for default configurations.
#[allow(dead_code)]
pub fn latest() -> impl Into<Configuration> {
InRegion::v1()
}

/// Returns the v1 prebuilt configuration.
///
/// Versioning the prebuilt configurations allows users to opt-in to changes in the default
/// configurations. This is useful for users who want to ensure that their application's
/// behavior does not change unexpectedly.
#[allow(dead_code)]
pub fn v1() -> impl Into<Configuration> {
Configuration::builder().transport_strategy(
TransportStrategy::builder().grpc_configuration(
GrpcConfiguration::builder()
.deadline(Duration::from_millis(1100))
.enable_keep_alives_with_defaults(),
),
)
}
}

/// This config prioritizes keeping p99.9 latencies as low as possible, potentially sacrificing
/// some throughput to achieve this. Use this config if low latency is more important in
/// your application than availability.
pub struct LowLatency {}

impl LowLatency {
/// Returns the latest prebuilt configuration.
/// This is the recommended configuration for most users.
///
/// NOTE: this config may change in future releases to take advantage of improvements
/// we identify for default configurations.
#[allow(dead_code)]
pub fn latest() -> impl Into<Configuration> {
LowLatency::v1()
}

/// Returns the v1 prebuilt configuration.
///
/// Versioning the prebuilt configurations allows users to opt-in to changes in the default
/// configurations. This is useful for users who want to ensure that their application's
/// behavior does not change unexpectedly.
pub fn v1() -> impl Into<Configuration> {
Configuration::builder().transport_strategy(
TransportStrategy::builder().grpc_configuration(
GrpcConfiguration::builder()
.deadline(Duration::from_millis(500))
.enable_keep_alives_with_defaults(),
),
)
}
}

/// This config optimizes for lambda environments.
///
/// In addition to the in region settings of [InRegion], this
/// disables keep-alives.
///
/// NOTE: keep-alives are very important for long-lived server environments where there may be periods of time
/// when the connection is idle. However, they are very problematic for lambda environments where the lambda
/// runtime is continuously frozen and unfrozen, because the lambda may be frozen before the "ACK" is received
/// from the server. This can cause the keep-alive to timeout even though the connection is completely healthy.
/// Therefore, keep-alives should be disabled in lambda and similar environments.
pub struct Lambda {}

impl Lambda {
/// Latest recommended config for a typical lambda environment.
///
/// NOTE: this config may change in future releases to take advantage of improvements
/// we identify for default configurations.
#[allow(dead_code)]
pub fn latest() -> impl Into<Configuration> {
Lambda::v1()
}

/// Returns the v1 prebuilt configuration.
///
/// Versioning the prebuilt configurations allows users to opt-in to changes in the default
/// configurations. This is useful for users who want to ensure that their application's
/// behavior does not change unexpectedly.
pub fn v1() -> impl Into<Configuration> {
Configuration::builder().transport_strategy(
TransportStrategy::builder().grpc_configuration(
GrpcConfiguration::builder()
.deadline(Duration::from_millis(1100))
.num_channels(1),
),
)
}
}
4 changes: 4 additions & 0 deletions src/leaderboard/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// Configuration for the Momento leaderboard client.
pub mod configuration;
/// Pre-built configurations for the Momento leaderboard client.
pub mod configurations;
51 changes: 51 additions & 0 deletions src/leaderboard/leaderboard_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::grpc::header_interceptor::HeaderInterceptor;
use crate::leaderboard::leaderboard_client_builder::{
LeaderboardClientBuilder, NeedsConfiguration,
};
use crate::leaderboard::Configuration;
use crate::leaderboard::Leaderboard;

use momento_protos::leaderboard::leaderboard_client as leaderboard_proto;
use tonic::codegen::InterceptedService;
use tonic::transport::Channel;

/// Client to work with Momento Leaderboards.
#[derive(Clone, Debug)]
pub struct LeaderboardClient {
data_clients:
Vec<leaderboard_proto::LeaderboardClient<InterceptedService<Channel, HeaderInterceptor>>>,
configuration: Configuration,
}

impl LeaderboardClient {
/// Returns a builder to construct a `LeaderboardClient`.
pub fn builder() -> LeaderboardClientBuilder<NeedsConfiguration> {
LeaderboardClientBuilder(NeedsConfiguration {})
}

pub(crate) fn new(
data_clients: Vec<
leaderboard_proto::LeaderboardClient<InterceptedService<Channel, HeaderInterceptor>>,
>,
configuration: Configuration,
) -> Self {
Self {
data_clients,
configuration,
}
}

/// Returns a `Leaderboard` client to work with a specific leaderboard.
pub fn leaderboard(
&self,
cache_name: impl Into<String>,
leaderboard_name: impl Into<String>,
) -> Leaderboard {
Leaderboard::new(
self.data_clients.clone(),
self.configuration.client_timeout(),
cache_name,
leaderboard_name,
)
}
}
Loading