Skip to content

Feat raylet sole gcs - #59798

Closed
Tomlord1122 wants to merge 9 commits into
ray-project:masterfrom
Tomlord1122:feat-raylet-sole-gcs
Closed

Tomlord1122 wants to merge 9 commits into
ray-project:masterfrom
Tomlord1122:feat-raylet-sole-gcs

Conversation

@Tomlord1122

@Tomlord1122 Tomlord1122 commented Jan 1, 2026 •

Copy link
Copy Markdown

Description

This PR makes Raylet the sole writer to GCS by removing the legacy logic where the dashboard agent writes its port information directly to GCS's internal KV store.

Background

Previously, the dashboard agent wrote port information to two internal KV keys:

  • DASHBOARD_AGENT_ADDR_NODE_ID_PREFIX:{node_id} → [ip, http_port, grpc_port]
  • DASHBOARD_AGENT_ADDR_IP_PREFIX:{ip} → [node_id, http_port, grpc_port]

After #59613, Raylet already:

  1. Discovers agent ports via persist_port() files written by the dashboard agent
  2. Includes port info in GcsNodeInfo during node registration
  3. Serves as the single source of truth for node metadata in GCS

This PR removes the redundant KV writes and updates all consumers to read from GcsNodeInfo instead.

Changes

5ec0657: Remove dashboard agent direct writes to GCS

  • Removes async_internal_kv_put() calls from dashboard/agent.py
  • Keeps persist_port() calls (Raylet depends on these files)

9b32247: Update StateDataSourceClient to read from GcsNodeInfo

  • get_log_service_stub(): Uses GetAllNodeInfoRequest with node selector
  • ip_to_node_id(): Queries alive nodes, filters by IP address

a7c394f: Update dashboard modules to read from GcsNodeInfo

  • ReporterHead: Updated _get_stub_address_by_node_id() and _get_stub_address_by_ip()
  • JobHead: Updated _fetch_agent_info() to use async_get_all_node_info()
  • NodeHead: Removed KV cleanup on node death (GcsNodeInfo is auto-managed by GCS)

f74a05b: Update tests to verify GcsNodeInfo integration

  • Updated test utilities to wait for metrics_agent_port > 0 instead of KV keys
  • Updated test assertions to use node info fields

Architecture After This Change

image

Related issues

Fixes #59666

Additional information

Files Modified

Category Files
Core logic dashboard/agent.py, util/state/state_manager.py, dashboard/modules/reporter/reporter_head.py, dashboard/modules/job/job_head.py, dashboard/modules/node/node_head.py
Tests _private/test_utils.py, tests/test_state_api.py, tests/test_ray_event_export_task_events.py, dashboard/tests/test_dashboard.py, dashboard/modules/job/tests/test_sdk.py, dashboard/modules/aggregator/tests/test_aggregator_agent.py, dashboard/modules/aggregator/tests/test_ray_actor_events.py

Backward Compatibility

  • The internal KV keys are no longer written or read
  • Constants DASHBOARD_AGENT_ADDR_NODE_ID_PREFIX and DASHBOARD_AGENT_ADDR_IP_PREFIX still exist but are unused
  • No external API changes

Tom Liu (LIU HSIU-CHI) added 4 commits January 1, 2026 22:34
In this commit, we remove the legacy dashboard agent KV writes, ensuring Raylet is the sole writer to GCS.

Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>
Replace internal KV reads with GcsNodeInfo queries.

Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>
Replace internal KV reads with GcsNodeInfo queries across dashboard modules

Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>
…odeInfo

Update test utilities and test files to rely on GcsNodeInfo for agent port information instead of internal KV store

Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors how the dashboard agent's port information is managed, making Raylet the sole writer to GCS. This is a solid architectural improvement that enhances consistency. The changes span core logic and test files, and they are generally well-aligned with the stated goal. My review has identified a potential correctness issue in reporter_head.py where dead nodes are not filtered, which could lead to incorrect behavior. I've also included suggestions to improve code quality by addressing code duplication in tests, fixing a type hint mismatch, and optimizing a test helper for better performance.

Comment on lines +858 to +865
for node_id, node_info in node_info_dict.items():
if node_info.node_manager_address == ip:
http_port = node_info.dashboard_agent_listen_port
grpc_port = node_info.metrics_agent_port
if grpc_port <= 0:
# Agent not started or not available
return None
return node_id, ip, http_port, grpc_port

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.

high

The current implementation of _get_stub_address_by_ip fetches all nodes, including dead ones. This could lead to returning information for a dead node if a new node reuses its IP address. You should filter for ALIVE nodes to ensure correctness.

To fix this, you'll also need to add from ray.core.generated import gcs_pb2 at the top of the file.

Suggested change
for node_id, node_info in node_info_dict.items():
if node_info.node_manager_address == ip:
http_port = node_info.dashboard_agent_listen_port
grpc_port = node_info.metrics_agent_port
if grpc_port <= 0:
# Agent not started or not available
return None
return node_id, ip, http_port, grpc_port
for node_id, node_info in node_info_dict.items():
if node_info.state != gcs_pb2.GcsNodeInfo.GcsNodeState.ALIVE:
continue
if node_info.node_manager_address == ip:
http_port = node_info.dashboard_agent_listen_port
grpc_port = node_info.metrics_agent_port
if grpc_port <= 0:
# Agent not started or not available
return None
return node_id, ip, http_port, grpc_port

Comment on lines +21 to +39
def wait_for_dashboard_agent_available(cluster):
from ray import NodeID
from ray.core.generated import gcs_pb2

gcs_client = GcsClient(address=cluster.address)
node_id = NodeID.from_hex(cluster.head_node.node_id)

def get_dashboard_agent_address():
return gcs_client.internal_kv_get(
f"{dashboard_consts.DASHBOARD_AGENT_ADDR_NODE_ID_PREFIX}{cluster.head_node.node_id}".encode(),
namespace=ray_constants.KV_NAMESPACE_DASHBOARD,
def is_dashboard_agent_ready():
node_info_dict = gcs_client.get_all_node_info(
timeout=dashboard_consts.GCS_RPC_TIMEOUT_SECONDS,
state_filter=gcs_pb2.GcsNodeInfo.GcsNodeState.ALIVE,
)
if node_id not in node_info_dict:
return False
node_info = node_info_dict[node_id]
# Dashboard agent is ready when metrics_agent_port is set (grpc port)
return node_info.metrics_agent_port > 0

wait_for_condition(lambda: get_dashboard_agent_address() is not None)
wait_for_condition(is_dashboard_agent_ready)

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.

medium

This wait_for_dashboard_agent_available function is a duplicate of the one in ray._private.test_utils. To improve maintainability and avoid code duplication, please remove this function and import it from ray._private.test_utils at the top of the file.

ip = node_info.node_manager_address
http_port = node_info.dashboard_agent_listen_port
grpc_port = node_info.metrics_agent_port
return [ip, http_port, grpc_port]

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.

medium

The function _fetch_agent_info is type-hinted to return a Tuple[str, int, int], but it currently returns a list. While this works at runtime, it's better to adhere to the type hint for correctness and clarity. Please change the return value to a tuple.

Suggested change
return [ip, http_port, grpc_port]
return (ip, http_port, grpc_port)

Comment on lines +63 to +76
def get_agent_grpc_port(node_id_hex: str) -> int:
node_id = NodeID.from_hex(node_id_hex)
node_info_dict = gcs_client.get_all_node_info(
timeout=dashboard_consts.GCS_RPC_TIMEOUT_SECONDS,
state_filter=gcs_pb2.GcsNodeInfo.GcsNodeState.ALIVE,
)
if node_id not in node_info_dict:
return -1
return node_info_dict[node_id].metrics_agent_port

wait_for_condition(
lambda: all(
get_dashboard_agent_address(node_id) is not None for node_id in node_ids
)
lambda: all(get_agent_grpc_port(node_id) > 0 for node_id in node_ids)
)
grpc_ports = [
json.loads(get_dashboard_agent_address(node_id))[2] for node_id in node_ids
]
grpc_ports = [get_agent_grpc_port(node_id) for node_id in node_ids]

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.

medium

This implementation is inefficient as it calls get_all_node_info in a loop, once for each node ID within wait_for_condition and then again when constructing grpc_ports. This can be optimized by fetching all node info once and then processing it.

Suggested change
def get_agent_grpc_port(node_id_hex: str) -> int:
node_id = NodeID.from_hex(node_id_hex)
node_info_dict = gcs_client.get_all_node_info(
timeout=dashboard_consts.GCS_RPC_TIMEOUT_SECONDS,
state_filter=gcs_pb2.GcsNodeInfo.GcsNodeState.ALIVE,
)
if node_id not in node_info_dict:
return -1
return node_info_dict[node_id].metrics_agent_port
wait_for_condition(
lambda: all(
get_dashboard_agent_address(node_id) is not None for node_id in node_ids
)
lambda: all(get_agent_grpc_port(node_id) > 0 for node_id in node_ids)
)
grpc_ports = [
json.loads(get_dashboard_agent_address(node_id))[2] for node_id in node_ids
]
grpc_ports = [get_agent_grpc_port(node_id) for node_id in node_ids]
def get_ports():
node_info_dict = gcs_client.get_all_node_info(
timeout=dashboard_consts.GCS_RPC_TIMEOUT_SECONDS,
state_filter=gcs_pb2.GcsNodeInfo.GcsNodeState.ALIVE,
)
ports = []
for node_id_hex in node_ids:
node_id = NodeID.from_hex(node_id_hex)
if node_id not in node_info_dict or node_info_dict[node_id].metrics_agent_port <= 0:
return None
ports.append(node_info_dict[node_id].metrics_agent_port)
return ports
wait_for_condition(lambda: get_ports() is not None)
grpc_ports = get_ports()

Comment thread python/ray/tests/test_state_api.py Resolved
Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>
@Tomlord1122
Tomlord1122 force-pushed the feat-raylet-sole-gcs branch from 12f6435 to dbd7693 Compare January 1, 2026 15:15
Tom Liu (LIU HSIU-CHI) added 3 commits January 1, 2026 23:18
Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>
Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>
Signed-off-by: Tom Liu (LIU HSIU-CHI) <aa123593465@gmail.com>
@Tomlord1122
Tomlord1122 force-pushed the feat-raylet-sole-gcs branch from f4c06e4 to b784f2c Compare January 1, 2026 15:18
@Tomlord1122 Tomlord1122 closed this Jan 1, 2026
@Tomlord1122
Tomlord1122 deleted the feat-raylet-sole-gcs branch January 1, 2026 15:23
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.

[core] Make Raylet the sole writer to GCS (remove dashboard agent writes)

1 participant