-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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(flags++): added quota limiting to the new flags service and in doing so refactored out a bunch of common stuff #29228
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR refactors the codebase to implement quota limiting in the feature flags service by extracting common functionality into shared modules.
- Moved Redis client implementation from service-specific modules to
rust/common/redis
for consistent usage across services - Relocated rate limiting functionality from capture service to
rust/common/limiters
with three submodules: overflow, redis, and token_dropper - Added support for feature flag request limiting by introducing a
FeatureFlags
variant to theQuotaResource
enum - Updated import paths across multiple files to use the new common modules instead of service-specific implementations
- Integrated the shared
RedisLimiter
into the feature flags service router and server components for quota management
30 file(s) reviewed, 8 comment(s)
Edit PR Review Bot Settings | Greptile
rust/common/limiters/Cargo.toml
Outdated
common-redis = { path = "../redis" } | ||
governor = { workspace = true } | ||
metrics = { workspace = true } | ||
rand = { workspace = true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: There's an extra space after rand = { workspace = true}
between 'true' and the closing brace. This should be rand = { workspace = true }
for consistency with other workspace dependencies.
rand = { workspace = true} | |
rand = { workspace = true } |
rust/common/redis/Cargo.toml
Outdated
"cluster", | ||
"cluster-async", | ||
] } | ||
serde-pickle = { version = "1.1.1"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: The serde-pickle
dependency is missing a space between the version and closing brace, unlike the other dependencies. This is a minor inconsistency in formatting.
rust/common/redis/Cargo.toml
Outdated
] } | ||
serde-pickle = { version = "1.1.1"} | ||
thiserror = { workspace = true } | ||
tokio = { workspace = true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: The file is missing a newline at the end, which is typically recommended for text files in Unix-like systems.
tokio = { workspace = true } | |
tokio = { workspace = true } |
/// Implements functionality from both feature-flags and capture Redis clients | ||
#[async_trait] | ||
pub trait Client { | ||
async fn zrangebyscore(&self, k: String, min: String, max: String) -> Result<Vec<String>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: The return type for zrangebyscore is inconsistent with other methods - it uses anyhow::Result while other methods use Result<T, CustomRedisError>. This inconsistency could lead to confusion and different error handling patterns.
async fn zrangebyscore(&self, k: String, min: String, max: String) -> Result<Vec<String>>; | |
async fn zrangebyscore(&self, k: String, min: String, max: String) -> Result<Vec<String>, CustomRedisError>; |
if match &fut { | ||
Ok(v) => v.is_empty(), | ||
Err(_) => false, | ||
} { | ||
return Err(CustomRedisError::NotFound); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: This error handling logic for empty values is only applied to the get method, but not to other methods like hget. Consider applying consistent error handling for empty values across all methods.
rust/common/redis/src/lib.rs
Outdated
async fn zrangebyscore(&self, key: String, _min: String, _max: String) -> Result<Vec<String>> { | ||
match self.zrangebyscore_ret.get(&key) { | ||
Some(val) => Ok(val.clone()), | ||
None => Err(anyhow::anyhow!("Not found")), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: The mock implementation of zrangebyscore returns anyhow::Error with a string message, while the real implementation returns various Redis errors. This inconsistency could cause tests to pass that would fail in production.
async fn zrangebyscore(&self, key: String, _min: String, _max: String) -> Result<Vec<String>> { | |
match self.zrangebyscore_ret.get(&key) { | |
Some(val) => Ok(val.clone()), | |
None => Err(anyhow::anyhow!("Not found")), | |
} | |
async fn zrangebyscore(&self, key: String, _min: String, _max: String) -> Result<Vec<String>> { | |
match self.zrangebyscore_ret.get(&key) { | |
Some(val) => Ok(val.clone()), | |
None => Err(CustomRedisError::NotFound.into()), | |
} |
async fn set(&self, key: String, _value: String) -> Result<(), CustomRedisError> { | ||
match self.set_ret.get(&key) { | ||
Some(result) => result | ||
.clone() | ||
.map_err(|e| CustomRedisError::Other(e.to_string())), | ||
None => Err(CustomRedisError::NotFound), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: The mock set implementation ignores the value parameter, which means tests won't catch bugs related to serialization of specific values.
rust/feature-flags/src/server.rs
Outdated
let billing_limiter = RedisLimiter::new( | ||
Duration::seconds(5), | ||
redis_client.clone(), | ||
QUOTA_LIMITER_CACHE_KEY.to_string(), | ||
None, | ||
QuotaResource::FeatureFlags, | ||
) | ||
.expect("failed to create billing limiter"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Using .expect() here will cause the service to panic if Redis is unavailable. Consider handling this error more gracefully, similar to how other client initialization errors are handled above.
let billing_limiter = RedisLimiter::new( | |
Duration::seconds(5), | |
redis_client.clone(), | |
QUOTA_LIMITER_CACHE_KEY.to_string(), | |
None, | |
QuotaResource::FeatureFlags, | |
) | |
.expect("failed to create billing limiter"); | |
let billing_limiter = match RedisLimiter::new( | |
Duration::seconds(5), | |
redis_client.clone(), | |
QUOTA_LIMITER_CACHE_KEY.to_string(), | |
None, | |
QuotaResource::FeatureFlags, | |
) { | |
Ok(limiter) => limiter, | |
Err(e) => { | |
tracing::error!("Failed to create billing limiter: {}", e); | |
return; | |
} | |
}; |
Problem
TODO – wanna see if this passes CI first.