Skip to content

Commit

Permalink
feat: momento leaderboard client (#438)
Browse files Browse the repository at this point in the history
Adds client functionality for the Momento Leaderboard API.
  • Loading branch information
brayniac authored Feb 11, 2025
1 parent dde80e2 commit 1223adc
Show file tree
Hide file tree
Showing 28 changed files with 1,766 additions and 22 deletions.
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;

/// 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 {
/// 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

0 comments on commit 1223adc

Please sign in to comment.