-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement cluster health monitoring and chunk replication #1
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,132 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use crate::handlers::AppState; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::time::{SystemTime, UNIX_EPOCH}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub async fn health_check_loop(state: &AppState) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut registry = state.node_registry.write().await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let now = SystemTime::now() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .duration_since(UNIX_EPOCH) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .as_millis() as u64; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (node_id, record) in registry.nodes.iter_mut() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let is_healthy = check_node_health(&record.address).await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let was_online = record.is_online; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| record.is_online = is_healthy; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if is_healthy { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| record.last_seen_ms = now; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if was_online != is_healthy { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if is_healthy { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tracing::info!("Node {} is now ONLINE", node_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tracing::warn!("Node {} is now OFFLINE", node_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+23
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub async fn health_check_loop(state: &AppState) { | |
| let mut registry = state.node_registry.write().await; | |
| let now = SystemTime::now() | |
| .duration_since(UNIX_EPOCH) | |
| .unwrap() | |
| .as_millis() as u64; | |
| for (node_id, record) in registry.nodes.iter_mut() { | |
| let is_healthy = check_node_health(&record.address).await; | |
| let was_online = record.is_online; | |
| record.is_online = is_healthy; | |
| if is_healthy { | |
| record.last_seen_ms = now; | |
| } | |
| if was_online != is_healthy { | |
| if is_healthy { | |
| tracing::info!("Node {} is now ONLINE", node_id); | |
| } else { | |
| tracing::warn!("Node {} is now OFFLINE", node_id); | |
| } | |
| use tokio::task::JoinSet; | |
| pub async fn health_check_loop(state: &AppState) { | |
| // Collect node IDs and their addresses under a short-lived read lock | |
| let nodes: Vec<(String, String)> = { | |
| let registry = state.node_registry.read().await; | |
| registry | |
| .nodes | |
| .iter() | |
| .map(|(node_id, record)| (node_id.clone(), record.address.clone())) | |
| .collect() | |
| }; | |
| // Perform health checks concurrently without holding the registry lock | |
| let mut tasks: JoinSet<(String, bool)> = JoinSet::new(); | |
| for (node_id, address) in nodes.into_iter() { | |
| tasks.spawn(async move { | |
| let is_healthy = check_node_health(&address).await; | |
| (node_id, is_healthy) | |
| }); | |
| } | |
| let mut results: Vec<(String, bool)> = Vec::new(); | |
| while let Some(join_result) = tasks.join_next().await { | |
| if let Ok((node_id, is_healthy)) = join_result { | |
| results.push((node_id, is_healthy)); | |
| } | |
| } | |
| // Update registry state with health check results under a write lock | |
| let mut registry = state.node_registry.write().await; | |
| let now = SystemTime::now() | |
| .duration_since(UNIX_EPOCH) | |
| .unwrap() | |
| .as_millis() as u64; | |
| for (node_id, is_healthy) in results.into_iter() { | |
| if let Some(record) = registry.nodes.get_mut(&node_id) { | |
| let was_online = record.is_online; | |
| record.is_online = is_healthy; | |
| if is_healthy { | |
| record.last_seen_ms = now; | |
| } | |
| if was_online != is_healthy { | |
| if is_healthy { | |
| tracing::info!("Node {} is now ONLINE", node_id); | |
| } else { | |
| tracing::warn!("Node {} is now OFFLINE", node_id); | |
| } | |
| } |
Copilot
AI
Jan 16, 2026
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.
The online_nodes.contains(addr) check on line 63 performs a linear search for each address. Since this is inside a loop over all chunks and their addresses, this creates O(n*m) complexity. Convert online_nodes to a HashSet for O(1) lookups.
Copilot
AI
Jan 16, 2026
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.
The replication target of 2 replicas is hardcoded in the replication loop. This should be configurable (e.g., from the original replication_factor used during chunk allocation) to support different replication requirements across different chunks or use cases.
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.
replicate_chunk doesn’t validate the source response; an error (e.g., 404/500) could be read and uploaded. Consider calling .error_for_status()? on the source response before .bytes().
| let data = client.get(format!("{}/api/v1/chunks/{}", src, cid)) | |
| .send() | |
| .await? | |
| .bytes() | |
| .await?; | |
| let data = client.get(format!("{}/api/v1/chunks/{}", src, cid)) | |
| .send() | |
| .await? | |
| .error_for_status()? | |
| .bytes() | |
| .await?; |
🚀 Want me to fix this? Reply ex: "fix it for me".
Copilot
AI
Jan 16, 2026
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.
The replicate_chunk function creates a new HTTP client for each replication. Creating a client has overhead (connection pools, DNS resolution, etc.). Consider reusing a shared client instance across replication operations for better performance.
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.
Holding the
node_registrywrite lock acrossawaits inhealth_check_loopcan block readers/writers and cause contention. Consider snapshotting node IDs/addresses under a read lock, drop the lock, run health checks, then re-acquire a write lock to apply updates.