Skip to content

Commit 4021c50

Browse files
author
yicheng
committed
Move the pending resource load pull off the GCS main io_context - #65024
Signed-off-by: yicheng <yicheng@anyscale.com>
1 parent 3fb63d9 commit 4021c50

8 files changed

Lines changed: 339 additions & 26 deletions

src/ray/gcs/BUILD.bazel

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ ray_cc_library(
9191
],
9292
)
9393

94+
ray_cc_library(
95+
name = "gcs_resource_load_puller",
96+
srcs = ["gcs_resource_load_puller.cc"],
97+
hdrs = ["gcs_resource_load_puller.h"],
98+
deps = [
99+
"//src/ray/asio:instrumented_io_context",
100+
"//src/ray/common:id",
101+
"//src/ray/protobuf:gcs_cc_proto",
102+
"//src/ray/raylet_rpc_client:raylet_client_pool",
103+
"//src/ray/util:logging",
104+
"@com_google_absl//absl/container:flat_hash_set",
105+
],
106+
)
107+
94108
ray_cc_library(
95109
name = "gcs_resource_manager",
96110
srcs = ["gcs_resource_manager.cc"],
@@ -238,6 +252,7 @@ ray_cc_library(
238252
name = "gcs_server_io_context_policy",
239253
hdrs = ["gcs_server_io_context_policy.h"],
240254
deps = [
255+
":gcs_resource_load_puller",
241256
":gcs_task_manager",
242257
"//src/ray/observability:ray_event_recorder",
243258
"//src/ray/pubsub:gcs_publisher",
@@ -427,6 +442,7 @@ ray_cc_library(
427442
":gcs_placement_group_manager",
428443
":gcs_placement_group_scheduler",
429444
":gcs_pubsub_handler",
445+
":gcs_resource_load_puller",
430446
":gcs_resource_manager",
431447
":gcs_runtime_env_handler",
432448
":gcs_server_io_context_policy",
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2026 The Ray Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "ray/gcs/gcs_resource_load_puller.h"
16+
17+
#include <utility>
18+
#include <vector>
19+
20+
#include "ray/util/logging.h"
21+
22+
namespace ray {
23+
namespace gcs {
24+
25+
GcsResourceLoadPuller::GcsResourceLoadPuller(
26+
instrumented_io_context &pull_io_context,
27+
instrumented_io_context &main_io_context,
28+
rpc::RayletClientPool &raylet_client_pool,
29+
std::function<void(rpc::ResourcesData)> apply_on_main)
30+
: pull_io_context_(pull_io_context),
31+
main_io_context_(main_io_context),
32+
raylet_client_pool_(raylet_client_pool),
33+
apply_on_main_(std::move(apply_on_main)) {}
34+
35+
void GcsResourceLoadPuller::Pull(std::vector<rpc::Address> raylet_addresses) {
36+
RAY_CHECK(pull_io_context_.get_executor().running_in_this_thread());
37+
absl::flat_hash_set<NodeID> current_node_ids;
38+
current_node_ids.reserve(raylet_addresses.size());
39+
for (const auto &address : raylet_addresses) {
40+
current_node_ids.insert(NodeID::FromBinary(address.node_id()));
41+
}
42+
for (const auto &node_id : pulled_node_ids_) {
43+
if (!current_node_ids.contains(node_id)) {
44+
raylet_client_pool_.Disconnect(node_id);
45+
}
46+
}
47+
pulled_node_ids_ = std::move(current_node_ids);
48+
49+
for (const auto &address : raylet_addresses) {
50+
auto raylet_client = raylet_client_pool_.GetOrConnectByAddress(address);
51+
raylet_client->GetResourceLoad(
52+
[this](const Status &status, rpc::GetResourceLoadReply &&reply) {
53+
if (!status.ok()) {
54+
RAY_LOG_EVERY_N(WARNING, 10)
55+
<< "Failed to get the resource load: " << status.ToString();
56+
return;
57+
}
58+
main_io_context_.post(
59+
[this, resources = std::move(*reply.mutable_resources())]() mutable {
60+
apply_on_main_(std::move(resources));
61+
},
62+
"GcsResourceLoadPuller.apply");
63+
});
64+
}
65+
}
66+
67+
} // namespace gcs
68+
} // namespace ray
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2026 The Ray Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <functional>
18+
#include <vector>
19+
20+
#include "absl/container/flat_hash_set.h"
21+
#include "ray/asio/instrumented_io_context.h"
22+
#include "ray/common/id.h"
23+
#include "ray/raylet_rpc_client/raylet_client_pool.h"
24+
#include "src/ray/protobuf/gcs.pb.h"
25+
26+
namespace ray {
27+
namespace gcs {
28+
29+
/// Pulls every raylet's pending resource requests (autoscaler bookkeeping) on
30+
/// a dedicated io_context, and posts each reply back to the main io_context,
31+
/// where the consumers live.
32+
class GcsResourceLoadPuller {
33+
public:
34+
GcsResourceLoadPuller(instrumented_io_context &pull_io_context,
35+
instrumented_io_context &main_io_context,
36+
rpc::RayletClientPool &raylet_client_pool,
37+
std::function<void(rpc::ResourcesData)> apply_on_main);
38+
39+
void Pull(std::vector<rpc::Address> raylet_addresses);
40+
41+
private:
42+
instrumented_io_context &pull_io_context_;
43+
instrumented_io_context &main_io_context_;
44+
rpc::RayletClientPool &raylet_client_pool_;
45+
std::function<void(rpc::ResourcesData)> apply_on_main_;
46+
/// Node ids from the last Pull(), diffed against the next snapshot to remove
47+
/// dead raylets' pooled clients.
48+
absl::flat_hash_set<NodeID> pulled_node_ids_;
49+
};
50+
51+
} // namespace gcs
52+
} // namespace ray

src/ray/gcs/gcs_server.cc

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ GcsServer::GcsServer(const ray::gcs::GcsServerConfig &config,
131131
});
132132
});
133133
}),
134+
resource_load_pull_client_call_manager_(
135+
io_context_provider_.GetIOContext<GcsResourceLoadPuller>(),
136+
/*record_stats=*/true,
137+
config.node_ip_address,
138+
ClusterID::Nil(),
139+
/*num_threads=*/1),
140+
resource_load_pull_raylet_client_pool_([this](const rpc::Address &addr) {
141+
// GetResourceLoad is not retryable, so the unavailable-timeout callback
142+
// can never fire; the puller's snapshot diff evicts instead.
143+
return std::make_shared<ray::rpc::RayletClient>(
144+
addr,
145+
this->resource_load_pull_client_call_manager_,
146+
/*raylet_unavailable_timeout_callback=*/[]() {});
147+
}),
134148
event_aggregator_client_call_manager_(
135149
io_context_provider_.GetIOContext<observability::RayEventRecorder>(),
136150
/*record_stats=*/true,
@@ -154,6 +168,8 @@ GcsServer::GcsServer(const ray::gcs::GcsServerConfig &config,
154168
io_context_provider_.GetIOContext<pubsub::GcsPublisher>())),
155169
observability_pubsub_periodical_runner_(PeriodicalRunner::Create(
156170
io_context_provider_.GetIOContext<pubsub::ObservabilityPublisher>())),
171+
resource_load_pull_periodical_runner_(PeriodicalRunner::Create(
172+
io_context_provider_.GetIOContext<GcsResourceLoadPuller>())),
157173
periodical_runner_(
158174
PeriodicalRunner::Create(io_context_provider_.GetDefaultIOContext())),
159175
is_started_(false),
@@ -500,35 +516,35 @@ void GcsServer::InitGcsResourceManager(const GcsInitData &gcs_init_data) {
500516
*gcs_resource_manager_,
501517
RayConfig::instance().gcs_max_active_rpcs_per_handler()));
502518

503-
periodical_runner_->RunFnPeriodically(
519+
resource_load_puller_ = std::make_unique<GcsResourceLoadPuller>(
520+
io_context_provider_.GetIOContext<GcsResourceLoadPuller>(),
521+
io_context_provider_.GetDefaultIOContext(),
522+
resource_load_pull_raylet_client_pool_,
523+
/*apply_on_main=*/
524+
[this](rpc::ResourcesData resources) {
525+
// TODO(vitsai): Remove duplicate reporting to GcsResourceManager
526+
// after verifying that non-autoscaler paths are taken care of.
527+
// Currently, GcsResourceManager aggregates reporting from different
528+
// sources at different intervals, leading to an obviously inconsistent
529+
// view.
530+
//
531+
// Once autoscaler is completely moved to the new mode of consistent
532+
// per-node reporting, remove this if it is not needed anymore.
533+
gcs_resource_manager_->UpdateResourceLoads(resources);
534+
gcs_autoscaler_state_manager_->UpdateResourceLoadAndUsage(std::move(resources));
535+
});
536+
resource_load_pull_periodical_runner_->RunFnPeriodically(
504537
[this] {
505-
for (const auto &alive_node : gcs_node_manager_->GetAllAliveNodes()) {
506-
auto remote_address = rpc::RayletClientPool::GenerateRayletAddress(
538+
const auto alive_nodes = gcs_node_manager_->GetAllAliveNodes();
539+
std::vector<rpc::Address> raylet_addresses;
540+
raylet_addresses.reserve(alive_nodes.size());
541+
for (const auto &alive_node : alive_nodes) {
542+
raylet_addresses.push_back(rpc::RayletClientPool::GenerateRayletAddress(
507543
alive_node.first,
508544
alive_node.second->node_manager_address(),
509-
alive_node.second->node_manager_port());
510-
auto raylet_client = raylet_client_pool_.GetOrConnectByAddress(remote_address);
511-
512-
// GetResourceLoad will also get usage. Historically it didn't.
513-
raylet_client->GetResourceLoad([this](auto &status, auto &&load_and_usage) {
514-
if (status.ok()) {
515-
// TODO(vitsai): Remove duplicate reporting to GcsResourceManager
516-
// after verifying that non-autoscaler paths are taken care of.
517-
// Currently, GcsResourceManager aggregates reporting from different
518-
// sources at different intervals, leading to an obviously inconsistent
519-
// view.
520-
//
521-
// Once autoscaler is completely moved to the new mode of consistent
522-
// per-node reporting, remove this if it is not needed anymore.
523-
gcs_resource_manager_->UpdateResourceLoads(load_and_usage.resources());
524-
gcs_autoscaler_state_manager_->UpdateResourceLoadAndUsage(
525-
std::move(*load_and_usage.mutable_resources()));
526-
} else {
527-
RAY_LOG_EVERY_N(WARNING, 10)
528-
<< "Failed to get the resource load: " << status.ToString();
529-
}
530-
});
545+
alive_node.second->node_manager_port()));
531546
}
547+
resource_load_puller_->Pull(std::move(raylet_addresses));
532548
},
533549
RayConfig::instance().gcs_pull_resource_loads_period_milliseconds(),
534550
"RayletLoadPulled");

src/ray/gcs/gcs_server.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,14 @@ class GcsServer {
260260
rpc::ClientCallManager client_call_manager_;
261261
rpc::RayletClientPool raylet_client_pool_;
262262
rpc::CoreWorkerClientPool worker_client_pool_;
263+
rpc::ClientCallManager resource_load_pull_client_call_manager_;
264+
rpc::RayletClientPool resource_load_pull_raylet_client_pool_;
263265
std::shared_ptr<ClusterResourceScheduler> cluster_resource_scheduler_;
264266
std::unique_ptr<gcs::GcsTableStorage> gcs_table_storage_;
265267
/// gcs_resource_manager_ depends on cluster_lease_manager_.
266268
std::unique_ptr<GcsResourceManager> gcs_resource_manager_;
267269
std::unique_ptr<GcsAutoscalerStateManager> gcs_autoscaler_state_manager_;
270+
std::unique_ptr<GcsResourceLoadPuller> resource_load_puller_;
268271
/// A publisher for publishing gcs messages (control-plane pubsub channels).
269272
std::unique_ptr<pubsub::GcsPublisher> gcs_publisher_;
270273
/// Publisher for observability pubsub (logs, errors, dashboard resource JSON).
@@ -304,6 +307,8 @@ class GcsServer {
304307
/// gRPC based pubsub's periodical runner.
305308
std::shared_ptr<PeriodicalRunner> pubsub_periodical_runner_;
306309
std::shared_ptr<PeriodicalRunner> observability_pubsub_periodical_runner_;
310+
/// The resource load pull's periodical runner.
311+
std::shared_ptr<PeriodicalRunner> resource_load_pull_periodical_runner_;
307312
/// The runner to run function periodically.
308313
std::shared_ptr<PeriodicalRunner> periodical_runner_;
309314
/// GCS service state flag, which is used for unit tests.

src/ray/gcs/gcs_server_io_context_policy.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "ray/gcs/gcs_kv_manager.h"
2323
#include "ray/gcs/gcs_node_manager.h"
24+
#include "ray/gcs/gcs_resource_load_puller.h"
2425
#include "ray/gcs/gcs_task_manager.h"
2526
#include "ray/observability/ray_event_recorder.h"
2627
#include "ray/pubsub/gcs_publisher.h"
@@ -64,6 +65,8 @@ struct GcsServerIOContextPolicy {
6465
return IndexOf("internal_kv_io_context");
6566
} else if constexpr (std::is_same_v<T, GcsNodeManager>) {
6667
return IndexOf("node_manager_io_context");
68+
} else if constexpr (std::is_same_v<T, GcsResourceLoadPuller>) {
69+
return IndexOf("resource_load_pull_io_context");
6770
} else {
6871
// default io context
6972
return -1;
@@ -74,7 +77,7 @@ struct GcsServerIOContextPolicy {
7477
// and a complete set of those returned from GetDedicatedIOContextIndex. Or you
7578
// can get runtime crashes when accessing a missing name, or get leaks by
7679
// creating unused threads.
77-
constexpr static std::array<IOContextMetadata, 7> kAllDedicatedIOContexts{{
80+
constexpr static std::array<IOContextMetadata, 8> kAllDedicatedIOContexts{{
7881
// task_io_context only runs GcsTaskManager, which ingests and serves
7982
// task-state events (observability) and drops events under load by design.
8083
// It is not on the GCS control plane, so a backlog here (e.g. under a
@@ -99,6 +102,9 @@ struct GcsServerIOContextPolicy {
99102
{"node_manager_io_context",
100103
/*enable_lag_probe=*/true,
101104
/*used_for_health_check=*/true},
105+
{"resource_load_pull_io_context",
106+
/*enable_lag_probe=*/true,
107+
/*used_for_health_check=*/false},
102108
}};
103109

104110
// Returns int (not size_t) to match GetDedicatedIOContextIndex's return type and

src/ray/gcs/tests/BUILD.bazel

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
load("//bazel:ray.bzl", "ray_cc_library", "ray_cc_test")
22

3+
ray_cc_test(
4+
name = "gcs_resource_load_puller_test",
5+
size = "small",
6+
srcs = ["gcs_resource_load_puller_test.cc"],
7+
tags = ["team:core"],
8+
visibility = ["//visibility:private"],
9+
deps = [
10+
"//src/ray/asio:instrumented_io_context",
11+
"//src/ray/common:test_utils",
12+
"//src/ray/gcs:gcs_resource_load_puller",
13+
"//src/ray/raylet_rpc_client:fake_raylet_client",
14+
"//src/ray/raylet_rpc_client:raylet_client_pool",
15+
"@com_google_googletest//:gtest_main",
16+
],
17+
)
18+
319
ray_cc_test(
420
name = "gcs_function_manager_test",
521
srcs = ["gcs_function_manager_test.cc"],

0 commit comments

Comments
 (0)