Add actions to list nodes and retrieve node metrics - #418
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request extends the ModelarDB Arrow Flight protocol and server/client implementations to support node-oriented operations, specifically listing cluster nodes and retrieving per-node resource/storage-engine metrics.
Changes:
- Added protobuf messages and Rust helpers for encoding/decoding cluster node metadata.
- Implemented new Arrow Flight actions
ListNodesandNodeMetricson the server (and added integration tests). - Extended
modelardb_embedded::Clientwith node-related APIs (configuration, flush/kill, list nodes, node metrics), plus supporting error handling.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/modelardb_types/src/flight/protocol.proto | Adds NodeMetadata, ClusterNodes, and NodeMetrics protobuf messages. |
| crates/modelardb_types/src/flight/mod.rs | Adds cluster-node encode/decode helpers and a unit test. |
| crates/modelardb_server/tests/integration_test.rs | Adds integration tests for ListNodes/NodeMetrics and updates action-list expectations. |
| crates/modelardb_server/src/storage/types.rs | Makes remaining_ingested_memory_in_bytes available outside tests. |
| crates/modelardb_server/src/storage/mod.rs | Exposes remaining-memory getters on StorageEngine for metrics reporting. |
| crates/modelardb_server/src/remote/mod.rs | Implements ListNodes and NodeMetrics actions and updates ClusterMode usage. |
| crates/modelardb_server/src/main.rs | Removes local ClusterMode definition and uses cluster::ClusterMode. |
| crates/modelardb_server/src/data_folders.rs | Stores the single-node’s Node metadata inside ClusterMode::SingleNode. |
| crates/modelardb_server/src/context.rs | Implements metric collection via sysinfo and disk-space identification logic. |
| crates/modelardb_server/src/configuration.rs | Updates imports/construction to use the new cluster::ClusterMode. |
| crates/modelardb_server/src/cluster.rs | Defines ClusterMode and adds nodes() helpers for single-node vs cluster. |
| crates/modelardb_server/Cargo.toml | Adds sysinfo dependency for metrics collection. |
| crates/modelardb_embedded/src/operations/client.rs | Adds node-related Client methods and refactors action sending/receiving. |
| crates/modelardb_embedded/src/error.rs | Adds a dedicated Prost decode error variant. |
| crates/modelardb_embedded/Cargo.toml | Adds prost dependency needed for decoding response protobufs. |
| Cargo.lock | Updates lockfile for new dependencies (sysinfo, prost). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| async fn send_action( | ||
| &mut self, | ||
| action_type: &str, | ||
| body: Vec<u8>, |
There was a problem hiding this comment.
Maybe make body parameter &[u8] instead of requiring the caller to create a Vec to pass a body. Although I do not know if it make the conversion into the right format for Action more complex and thus make the code less readable.
| Ok(response.into_inner()) | ||
| } | ||
|
|
||
| /// Returns the URL of the cloud node that the node assigns to execute the SQL in `sql`. If the |
There was a problem hiding this comment.
Is the second node mentioned in the comment not also a cloud node, since we no longer have a manager? If that is the case, should the node not just always return itself if it is not running in a cluster configuration?
| #[derive(Clone)] | ||
| pub(crate) enum ClusterMode { | ||
| SingleNode(Node), | ||
| MultiNode(Box<Cluster>), |
There was a problem hiding this comment.
Is Cluster in Box because clippy warns about its size? Otherwise, I do not understand why the Box. It would probably be good to add a comment describing why Box is used.
| /// Return all nodes currently in the cluster. If the nodes could not be retrieved, return | ||
| /// [`ModelarDbServerError`]. | ||
| pub(crate) async fn nodes(&self) -> Result<Vec<Node>> { | ||
| self.remote_data_folder |
There was a problem hiding this comment.
Since we read the cluster nodes from the data folder, can we get into situations where we return nodes to the caller of nodes() that no longer exist? And if so, is this a problem?
| let mut system = System::new(); | ||
|
|
||
| // Sample the CPU twice, separated by the minimum update interval, since a single refresh | ||
| // reads zero. |
There was a problem hiding this comment.
Unclear what it meant by "...reads zero." as it does not look like system.refresh_cpu_usage() return a value that is used.
| let compressed_reserved_memory_in_bytes = | ||
| configuration_manager.compressed_reserved_memory_in_bytes(); | ||
|
|
||
| let ingested_used_memory_in_bytes = (ingested_reserved_memory_in_bytes as i64 |
There was a problem hiding this comment.
Maybe add a comment describing what this calculation does as it is probably a bit hard to understand for anybody that does not know how the storage engine manages memory.
|
|
||
| let maybe_disk = disks | ||
| .iter() | ||
| .filter(|disk| location.starts_with(&*disk.mount_point().to_string_lossy())) |
There was a problem hiding this comment.
I think I understand the filter() as it seems to remove all of the disks that the data folder is not stored in, however, I dod not understand the purpose of the max_by_key() and or_else() calls.
|
|
||
| let maybe_disk = disks | ||
| .iter() | ||
| .filter(|disk| location.starts_with(&*disk.mount_point().to_string_lossy())) |
There was a problem hiding this comment.
Why dereference the string and then borrow it right after? Why is this needed?
| } | ||
|
|
||
| /// Encode `nodes` into a [`ClusterNodes`](protocol::ClusterNodes) protobuf message and serialize it. | ||
| pub fn encode_and_serialize_cluster_nodes(nodes: Vec<Node>) -> Vec<u8> { |
There was a problem hiding this comment.
If the function immediately converts the Vec to an iterator, why not just make the function take any type that impl iterator or into iterator, depending on what is required?
This PR adds support for two new actions,
ListNodesandNodeMetrics.ListNodeslists all the nodes in the cluster, or the node itself if it is a single node deployment.NodeMetricsreturns a collection of metrics that show general system metrics such as CPU, disk, and memory usage, and ModelarDB specific metrics such as storage engine memory usage.Methods have also been added to
Clientinmodelardb_embeddedto support node related operations on the client. Note that the client still implements theOperationstrait for functionality that is shared betweenDataFolderandClientso these new methods are only extra functionality that only aClientsupports such asFlushNode. Someone using the Rust API forClientnow has access to all Apache Arrow Flight server functionality through a single API, instead of having to useClientfor some things and a manual Apache Arrow Flight client for other things.After the new
Clientwas implemented, it was considered if it could be used elsewhere in the system since it now supports all the Apache Arrow Flight functionality. An issue (#417) has been created to explore this further.