Skip to content

Commit 0dcd9df

Browse files
excelle08meta-codesync[bot]
authored andcommitted
Bound issueOutboundFanout per-request concurrency with folly::window(K=32)
Summary: issueOutboundFanout used to issue all ~376 RPCs (at --rpc_fanout_scale=0.10) in a tight loop, queueing them all onto the per-thread MockServicesClient EventBase at once. Even with the io_threads=88 fix spreading dispatcher EBs across the SREventBase pool, each individual request still produced a 376-RPC burst on one EB. Debug histograms showed the consequence: dispatch_per_rpc averaged ~500ms while mock_handler_actual averaged ~4ms — the 496ms gap is pure EB queue depth. Switch issueOutboundFanout to folly::window(executor=srEventBasePool, specs, fn, K=32). window() issues K up-front and refills as each completes, capping per-request EB queue depth at K instead of N. To keep the windowed lambda thread-safe without taking RNG locks, the per-RPC sampling (req_size, resp_size, lat_us) and Silesia-padded request body construction now happens up-front on the dispatcher thread; the lambda only does the dispatchByEnum() call and the histogram bookkeeping. K=32 was picked to be small enough to keep the EB queue shallow (~150ms drain at 5ms/RPC) while still amortizing window's per-call coordination overhead. Differential Revision: D105119221
1 parent 9ed9731 commit 0dcd9df

1 file changed

Lines changed: 62 additions & 25 deletions

File tree

packages/feedsim/third_party/src/workloads/ranking/LeafNodeRank.cc

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -722,18 +722,40 @@ static feedsim::LatencyHistogram g_fanout_total_us;
722722
static feedsim::LatencyHistogram g_dispatch_us;
723723
static feedsim::LatencyHistogram g_sampled_lat_us;
724724

725+
// Per-request bound on how many outbound RPCs are queued onto the
726+
// MockServicesClient EventBase at once. The pre-window code dumped all
727+
// ~376 RPCs (at scale=0.10) onto one EB simultaneously; that produced
728+
// dispatch_per_rpc averages ~500ms even though mock_handler_actual was
729+
// ~4ms — pure EB queue depth. window(K) issues K up-front and then
730+
// refills as each completes, capping per-request EB pressure.
731+
//
732+
// 32 was picked to be small enough to keep the EB queue shallow (each
733+
// in-flight RPC ~5-200ms means the queue completes in 50-300ms) while
734+
// still amortizing the per-RPC overhead of the window machinery.
735+
static constexpr size_t kOutboundFanoutWindow = 32;
736+
737+
namespace {
738+
struct FanoutSpec {
739+
ranking::MethodIdx method;
740+
int32_t lat_us;
741+
std::string req_payload; // pre-built so the windowed lambda
742+
// doesn't need td.rpc_rng/silesia
743+
};
744+
} // namespace
745+
725746
static folly::Future<int> issueOutboundFanout(
726747
ThreadData& td, double scale) {
727748
if (td.mock_client == nullptr || td.rpc_registry == nullptr) {
728749
// Defensive: caller should have checked --rpc_dist_path.
729750
return folly::makeFuture<int>(0);
730751
}
731752

732-
uint64_t fanout_start_us = feedsim::nowUs();
733-
734-
std::vector<folly::Future<int>> futs;
735-
futs.reserve(128);
736-
753+
// Build the spec list up-front on the dispatcher thread. Sampling
754+
// td.rpc_rng / building Silesia-padded request bodies happens here
755+
// (single-threaded), so the windowed lambda can be invoked from any
756+
// SREventBase worker without racing on the per-thread RNG.
757+
std::vector<FanoutSpec> specs;
758+
specs.reserve(128);
737759
for (size_t i = 0; i < ranking::kNumMethods; ++i) {
738760
auto m = static_cast<ranking::MethodIdx>(i);
739761
// Round to the nearest integer call count and skip methods that don't
@@ -762,34 +784,49 @@ static folly::Future<int> issueOutboundFanout(
762784
{
763785
std::lock_guard<std::mutex> lock(td.rpc_rng_mutex);
764786
req_size = req_sampler.sample(td.rpc_rng);
765-
resp_size = static_cast<uint32_t>(resp_sampler.sample(td.rpc_rng));
766-
lat_us = static_cast<int32_t>(lat_sampler.sampleI64(td.rpc_rng));
787+
resp_size =
788+
static_cast<uint32_t>(resp_sampler.sample(td.rpc_rng));
789+
lat_us =
790+
static_cast<int32_t>(lat_sampler.sampleI64(td.rpc_rng));
767791
req = buildFanoutRequest(
768792
req_size, resp_size, td.rpc_silesia, td.rpc_rng);
769793
}
770794
g_sampled_lat_us.record(static_cast<uint64_t>(std::max(0, lat_us)));
771-
772-
uint64_t dispatch_start_us = feedsim::nowUs();
773-
futs.push_back(td.mock_client
774-
->dispatchByEnum(m, req, lat_us)
775-
.via(td.srEventBasePool.get())
776-
.thenValue([dispatch_start_us](std::string&&) {
777-
g_dispatch_us.record(
778-
feedsim::nowUs() - dispatch_start_us);
779-
return 1;
780-
})
781-
.thenError(
782-
folly::tag_t<std::exception>{},
783-
[dispatch_start_us](const std::exception&) {
784-
g_dispatch_us.record(
785-
feedsim::nowUs() - dispatch_start_us);
786-
return 0;
787-
}));
795+
specs.push_back(FanoutSpec{m, lat_us, std::move(req)});
788796
}
789797
}
790798

799+
uint64_t fanout_start_us = feedsim::nowUs();
800+
auto* mock_client = td.mock_client.get();
801+
auto* srEvbPool = td.srEventBasePool.get();
802+
803+
// folly::window(executor, items, fn, K): issues fn(item) for the
804+
// first K items; as each returned Future completes, issues the next
805+
// item's fn(). Bounded concurrency K avoids burst-queueing the
806+
// entire fanout onto one EB at once.
807+
auto futs = folly::window(
808+
srEvbPool,
809+
std::move(specs),
810+
[mock_client, srEvbPool](FanoutSpec spec) {
811+
uint64_t dispatch_start_us = feedsim::nowUs();
812+
return mock_client
813+
->dispatchByEnum(spec.method, spec.req_payload, spec.lat_us)
814+
.via(srEvbPool)
815+
.thenValue([dispatch_start_us](std::string&&) {
816+
g_dispatch_us.record(feedsim::nowUs() - dispatch_start_us);
817+
return 1;
818+
})
819+
.thenError(
820+
folly::tag_t<std::exception>{},
821+
[dispatch_start_us](const std::exception&) {
822+
g_dispatch_us.record(feedsim::nowUs() - dispatch_start_us);
823+
return 0;
824+
});
825+
},
826+
kOutboundFanoutWindow);
827+
791828
return folly::collectAll(std::move(futs))
792-
.via(td.srEventBasePool.get())
829+
.via(srEvbPool)
793830
.thenValue([fanout_start_us](std::vector<folly::Try<int>> results) {
794831
g_fanout_total_us.record(feedsim::nowUs() - fanout_start_us);
795832
int total = 0;

0 commit comments

Comments
 (0)