-
Notifications
You must be signed in to change notification settings - Fork 0
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: add support for dynamic rate limit configurations with hot reload #3
base: pr3-base
Are you sure you want to change the base?
Conversation
- Introduce dynamic rate limiting configurations - Incorporate hot reload functionality to update rate limits without a server restart - Allow configuration of hot reload of rate limits in `gateway` server setup
config: Config, | ||
fetch_method: GraphFetchMethod, | ||
otel_tracing: Option<OtelTracing>, | ||
ServerConfig { |
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.
⚡ Suggestion
Ensure that the config_hot_reload
functionality is properly validated and handled in the serve
function to avoid unexpected behavior during runtime. Consider adding checks or logging to confirm that hot reload is functioning as intended.
@@ -64,6 +72,10 @@ impl super::Args for Args { | |||
} | |||
} | |||
|
|||
fn hot_reload(&self) -> bool { |
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.
⚡ Suggestion
Consider implementing the hot_reload
method to actually check for configuration changes instead of returning a static false
. This will make the feature functional and allow for dynamic updates as intended.
@@ -167,7 +158,7 @@ impl RedisRateLimiter { | |||
async fn limit_inner(&self, context: &dyn RateLimiterContext) -> Result<(), Error> { | |||
let Some(key) = context.key() else { return Ok(()) }; | |||
|
|||
let Some(config) = self.subgraph_limits.get(key) else { | |||
let Some(config) = self.limits.borrow().get(key).copied() else { |
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.
⚡ Suggestion
Consider handling the case where self.limits.borrow()
might fail, to avoid potential panics.
} | ||
|
||
impl RateLimitSender { | ||
fn send(&self, data: RateLimitData) -> crate::Result<()> { |
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.
⚡ Suggestion
Consider using expect
instead of unwrap
for error handling in the send
method to provide more context in case of failure.
@@ -20,6 +20,10 @@ pub(crate) trait Args { | |||
|
|||
fn config(&self) -> anyhow::Result<Config>; | |||
|
|||
fn config_path(&self) -> Option<&Path>; |
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.
⚡ Suggestion
Ensure that the config_path
method is implemented in a way that it returns a valid path or None, to avoid potential runtime errors when accessing the configuration.
federated_server::serve(args.listen_address(), config, args.fetch_method()?, otel_tracing).await?; | ||
federated_server::serve(ServerConfig { | ||
listen_addr: args.listen_address(), | ||
config, |
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.
⚡ Suggestion
Ensure that the config
variable is properly initialized and contains valid data before passing it to federated_server::serve
. This will help avoid potential issues during runtime.
@@ -59,7 +62,15 @@ fn main() -> anyhow::Result<()> { | |||
let crate_version = crate_version!(); | |||
tracing::info!(target: GRAFBASE_TARGET, "Grafbase Gateway {crate_version}"); | |||
|
|||
federated_server::serve(args.listen_address(), config, args.fetch_method()?, otel_tracing).await?; | |||
federated_server::serve(ServerConfig { |
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.
⚡ Suggestion
Consider validating the args
parameters before using them to ensure they meet expected formats or constraints, especially for config_path
and listen_addr
. This can prevent runtime errors.
RedisRateLimiter::runtime(global_config, rate_limiting_configs) | ||
match config_path { | ||
Some(path) if config_hot_reload => { | ||
hot_reload::ConfigWatcher::new(path, tx).watch()?; |
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.
⚡ Suggestion
Consider handling the case where hot_reload::ConfigWatcher::new(path, tx).watch()?
fails, to prevent the application from crashing.
let (tx, rx) = mpsc::channel(100); | ||
|
||
match config_path { | ||
Some(path) if config_hot_reload => { |
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.
🐛 Bug
The config_path
and config_hot_reload
variables are checked for Some(path)
and true
, respectively, but there is no handling for the case where config_path
is None
or config_hot_reload
is false
. This could lead to unexpected behavior if the configuration is not set correctly.
Some(path) if config_hot_reload => { | |
if config_hot_reload { return Err(crate::Error::InternalError("Hot reload is enabled but config_path is not set".into())); } |
@@ -40,6 +43,8 @@ pub(crate) struct GatewayConfig { | |||
pub rate_limit: Option<RateLimitConfig>, | |||
pub timeout: Option<Duration>, | |||
pub entity_caching: EntityCachingConfig, | |||
pub config_hot_reload: bool, | |||
pub config_path: Option<PathBuf>, |
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.
⚡ Suggestion
Ensure that the config_path
is validated before being used to avoid potential runtime errors if the path is invalid.
✨
Description by Cal
PR Description
This PR introduces dynamic rate limiting configurations with hot reload functionality, allowing updates to rate limits without server restarts. It includes changes to various files to support this feature, including the addition of a new
hot_reload
module.Diagrams of code changes
Key Issues
None
Files Changed
File: /Cargo.lock
Added `notify` dependency for file watching.File: /cli/crates/federated-dev/src/dev/gateway_nanny.rs
Updated `new_gateway` function to include a channel for rate limit updates.File: /engine/crates/integration-tests/src/federation/builder/test_runtime.rs
Modified `TestRuntime` to include a channel for rate limit updates.File: /engine/crates/runtime-local/src/rate_limiting/in_memory/key_based.rs
Refactored `InMemoryRateLimiter` to support dynamic updates via a channel.File: /engine/crates/runtime-local/src/rate_limiting/redis.rs
Updated `RedisRateLimiter` to accept dynamic rate limit configurations.File: /engine/crates/runtime/src/rate_limiting.rs
Changed `KeyedRateLimitConfig` to use `String` instead of `&str`.File: /gateway/crates/federated-server/src/config/hot_reload.rs
Added a new module for watching configuration files and reloading rate limits.File: /gateway/crates/federated-server/src/config/rate_limit.rs
Updated `GraphRateLimit` struct to derive `Copy`.File: /gateway/crates/federated-server/src/server.rs
Modified `serve` function to handle hot reload configuration.File: /gateway/crates/gateway-binary/src/args.rs
Added methods to handle configuration path and hot reload flag.File: /gateway/crates/gateway-binary/src/main.rs
Updated main function to pass server configuration including hot reload settings.Description
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
Type of change