Skip to content

Add actions to list nodes and retrieve node metrics - #418

Open
CGodiksen wants to merge 29 commits into
mainfrom
dev/node-operations
Open

Add actions to list nodes and retrieve node metrics#418
CGodiksen wants to merge 29 commits into
mainfrom
dev/node-operations

Conversation

@CGodiksen

Copy link
Copy Markdown
Collaborator

This PR adds support for two new actions, ListNodes and NodeMetrics. ListNodes lists all the nodes in the cluster, or the node itself if it is a single node deployment. NodeMetrics returns 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 Client in modelardb_embedded to support node related operations on the client. Note that the client still implements the Operations trait for functionality that is shared between DataFolder and Client so these new methods are only extra functionality that only a Client supports such as FlushNode. Someone using the Rust API for Client now has access to all Apache Arrow Flight server functionality through a single API, instead of having to use Client for some things and a manual Apache Arrow Flight client for other things.

After the new Client was 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.

@CGodiksen CGodiksen self-assigned this Aug 12, 2026
@CGodiksen
CGodiksen requested a lite review from Copilot August 12, 2026 11:14
@CGodiksen
CGodiksen requested review from skejserjensen and a lite review from Copilot and removed request for Copilot August 12, 2026 12:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ListNodes and NodeMetrics on the server (and added integration tests).
  • Extended modelardb_embedded::Client with 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.

Comment thread crates/modelardb_server/src/context.rs
Comment thread crates/modelardb_embedded/src/operations/client.rs Outdated
Comment thread crates/modelardb_server/tests/integration_test.rs
async fn send_action(
&mut self,
action_type: &str,
body: Vec<u8>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants