Skip to content

Commit 6a8ff5d

Browse files
excelle08meta-codesync[bot]
authored andcommitted
Add 5 prod-shaped thrift structs and register new request type IDs (#721)
Summary: Pull Request resolved: #721 Phase 4 programmer-B: introduce production-shaped multifeed aggregator thrift schema and dispatch IDs so the FeedSim leaf node can be exercised by per-method driver traffic in Phase 6. Builds on Phase 4-A (`11e9bc9a3431` — pool rename to ThriftSrv.IO/SREventBase/RANKER/GlobalCPUThread) and Phase 5-A (`31948cd0d579` — mock_services binary). Three changes, all additive (no existing struct/handler is removed — Phase 6 deletes RankingRequest/RankingResponse and the legacy kPageRank/kDLRM type IDs): 1. `if/ranking.thrift` — five new request/response struct pairs sized to the p50 wire targets from `~/feedsim_v2/profiles/rpc_dist.json`: - CreateAndPrimeSessionRequest/Response (379 B / 44 B) - GetStoriesRequest/Response (2.13 MB / 171 KB) — also adds shared helpers GetStoriesResponseStats and RankedStoryInfo - GetAllStoriesRequest/Response (55 B / 1.47 MB) - StreamDataRequest/Response (58 KB / 4 B) plus StreamingUseCase enum - StreamIfrPriorityRankingRequest/Response (949 KB / 4 B) Each request struct mirrors prod field counts and types (including primitive vs container vs binary) per `~/feedsim_v2/docs/phase4_researcher_notes.md` section 3, so CompactProtocol serialization cost is realistic. Bulk wire size lives in named `binary` fields (e.g. `settings_compressed`, `serialized_payload`, `ifr_objects_serialized`) that the Phase 6 driver populates by sampling from the percentile table. 2. `RequestTypes.h` — five new uint32_t constants `0x10..0x14` for the new methods. Existing `kPageRankRequestType` (0x00) and `kDLRMRequestType` (0x01) stay so the in-flight stack keeps working. 3. `LeafNodeRank.cc` — five new shim handler functions and matching `registerQueryCallback` calls: - Heavy methods (`getStoriesUncompressed`, `getAllStories`) deserialize the new struct then route to the existing `DLRMRequestHandler`. Phase 4 CPU profile is unchanged for those. - Light methods (`createAndPrimeSession`, `streamData`, `streamIfrPriorityRanking`) deserialize, then send a small fixed-size response (44 B / 4 B / 4 B) without invoking `DLRMRequestHandler`. Production p50 latencies for these are 3-13 ms with 4-44 B responses, so attributing DLRM CPU to them in Phase 4 testing would distort the profile. Phase 6 replaces these shims with real per-method handlers (session bookkeeping, ack-only paths, IFR scoring). Sizing methodology: targets are p50 wire sizes from `rpc_dist.json`. Computed sizes are CompactProtocol overhead (1 byte per short field tag, 2 bytes for tags >15, varint length + N bytes data for binary, ~1 byte stop) plus the binary field contents the driver supplies: | Method | Target p50 | Size source | |-------------------------------|-----------:|----------------------------------------------------------------------| | CreateAndPrimeSessionRequest | 379 B | ~110 B field overhead + ~270 B `session_init_blob` | | CreateAndPrimeSessionResponse | 44 B | ~7 B field overhead + 32-char hex `session_id` (~36 B) + 4 B status | | GetStoriesRequest | 2.13 MB | ~150 B fixed fields + 5 binary blobs (driver fills to ~2.07 MB total) | | GetStoriesResponse | 171 KB | ~50 B fixed + ~100 stories x ~1.5 KB story_payload + ~10 KB debug | | GetAllStoriesRequest | 55 B | 36 B session_id + 8 B query_id + ~10 B caller_id + ~6 B overhead | | GetAllStoriesResponse | 1.47 MB | ~50 B fixed + ~500-1000 stories x ~1.5 KB + ~10 KB debug | | StreamDataRequest | 58 KB | ~50 B fixed + driver-sampled `serialized_payload` (bimodal in prod) | | StreamDataResponse | 4 B | 1 B field header + 1 B i32 zigzag + 1 B stop = 3-4 B | | StreamIfrPriorityRankingReq | 949 KB | ~80 B fixed + driver-sampled `ifr_objects_serialized` etc. | | StreamIfrPriorityRankingResp | 4 B | identical encoding to StreamDataResponse | Because the binary fields are sampled per-request from the percentile table (in Phase 6), every method can hit not just p50 but the entire prod distribution (p05/p25/p75/p95). The structs themselves carry no binary defaults. Generated `gen-cpp2/ranking_types.h` is regenerated by CMake at fbpkg-install time; the checked-in copy predates `RankingRequest`/`DLRMFeatures`/`StoryBatch` and is also missing those, confirming it is rebuilt out-of-tree. Reviewed By: charles-typ Differential Revision: D103767023
1 parent c614da5 commit 6a8ff5d

3 files changed

Lines changed: 382 additions & 0 deletions

File tree

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

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,147 @@ void PageRankRequestHandler(
10971097
context.sendResponse(buf->data(), buf->length());
10981098
}
10991099

1100+
// ============================================================================
1101+
// Phase 4 shim handlers — exercise the new prod-shaped thrift schema and
1102+
// dispatch path. Heavy methods (getStoriesUncompressed, getAllStories) route
1103+
// to the existing DLRMRequestHandler so QPS/CPU profile is unchanged. Light
1104+
// methods (createAndPrimeSession, streamData, streamIfrPriorityRanking) send
1105+
// a small response without invoking DLRMRequestHandler — production p50
1106+
// latency and response size for these are tiny (4-44 B, 3-13 ms) and we don't
1107+
// want Phase 4 to attribute DLRM CPU to them. Phase 6 replaces these shims
1108+
// with real per-method handlers.
1109+
// ============================================================================
1110+
1111+
namespace {
1112+
1113+
// Helper: send a small fixed-size response after validating the inbound
1114+
// schema. Used by the three "light" methods. The payload bytes are filled
1115+
// with zeros — Phase 6 will populate real fields.
1116+
template <typename ResponseT>
1117+
void sendTinyResponse(feedsim::RequestContext& context, ResponseT&& response) {
1118+
folly::IOBufQueue queue;
1119+
apache::thrift::CompactSerializer::serialize(response, &queue);
1120+
auto buf = queue.move();
1121+
if (buf) {
1122+
// CompactSerializer may chain IOBufs; coalesce so sendResponse sees the
1123+
// full payload instead of just the head segment.
1124+
buf->coalesce();
1125+
context.sendResponse(buf->data(), buf->length());
1126+
} else {
1127+
context.sendResponse(nullptr, 0);
1128+
}
1129+
}
1130+
1131+
} // namespace
1132+
1133+
void CreateAndPrimeSessionRequestHandler(
1134+
int /*thread_id*/,
1135+
feedsim::RequestContext& context,
1136+
std::vector<ThreadData>& /*thread_data*/) {
1137+
ranking::CreateAndPrimeSessionRequest typed_req;
1138+
try {
1139+
folly::IOBuf buf(
1140+
folly::IOBuf::WRAP_BUFFER, context.payload, context.payload_length);
1141+
apache::thrift::CompactSerializer::deserialize(&buf, typed_req);
1142+
} catch (const std::exception& e) {
1143+
std::cerr << "CreateAndPrimeSession: deserialize failed: " << e.what()
1144+
<< std::endl;
1145+
context.sendResponse(nullptr, 0);
1146+
return;
1147+
}
1148+
ranking::CreateAndPrimeSessionResponse resp;
1149+
// 32-char hex placeholder; Phase 6 generates a real session_id.
1150+
resp.session_id() = "00000000000000000000000000000000";
1151+
resp.status_code() = 0;
1152+
sendTinyResponse(context, resp);
1153+
}
1154+
1155+
void GetStoriesUncompressedRequestHandler(
1156+
int thread_id,
1157+
feedsim::RequestContext& context,
1158+
std::vector<ThreadData>& thread_data) {
1159+
ranking::GetStoriesRequest typed_req;
1160+
try {
1161+
folly::IOBuf buf(
1162+
folly::IOBuf::WRAP_BUFFER, context.payload, context.payload_length);
1163+
apache::thrift::CompactSerializer::deserialize(&buf, typed_req);
1164+
} catch (const std::exception& e) {
1165+
std::cerr << "GetStoriesUncompressed: deserialize failed: " << e.what()
1166+
<< std::endl;
1167+
context.sendResponse(nullptr, 0);
1168+
return;
1169+
}
1170+
#ifdef FEEDSIM_USE_DLRM
1171+
DLRMRequestHandler(thread_id, context, thread_data);
1172+
#else
1173+
(void)thread_id;
1174+
(void)thread_data;
1175+
context.sendResponse(nullptr, 0);
1176+
#endif
1177+
}
1178+
1179+
void GetAllStoriesRequestHandler(
1180+
int thread_id,
1181+
feedsim::RequestContext& context,
1182+
std::vector<ThreadData>& thread_data) {
1183+
ranking::GetAllStoriesRequest typed_req;
1184+
try {
1185+
folly::IOBuf buf(
1186+
folly::IOBuf::WRAP_BUFFER, context.payload, context.payload_length);
1187+
apache::thrift::CompactSerializer::deserialize(&buf, typed_req);
1188+
} catch (const std::exception& e) {
1189+
std::cerr << "GetAllStories: deserialize failed: " << e.what() << std::endl;
1190+
context.sendResponse(nullptr, 0);
1191+
return;
1192+
}
1193+
#ifdef FEEDSIM_USE_DLRM
1194+
DLRMRequestHandler(thread_id, context, thread_data);
1195+
#else
1196+
(void)thread_id;
1197+
(void)thread_data;
1198+
context.sendResponse(nullptr, 0);
1199+
#endif
1200+
}
1201+
1202+
void StreamDataRequestHandler(
1203+
int /*thread_id*/,
1204+
feedsim::RequestContext& context,
1205+
std::vector<ThreadData>& /*thread_data*/) {
1206+
ranking::StreamDataRequest typed_req;
1207+
try {
1208+
folly::IOBuf buf(
1209+
folly::IOBuf::WRAP_BUFFER, context.payload, context.payload_length);
1210+
apache::thrift::CompactSerializer::deserialize(&buf, typed_req);
1211+
} catch (const std::exception& e) {
1212+
std::cerr << "StreamData: deserialize failed: " << e.what() << std::endl;
1213+
context.sendResponse(nullptr, 0);
1214+
return;
1215+
}
1216+
ranking::StreamDataResponse resp;
1217+
resp.ack_code() = 0;
1218+
sendTinyResponse(context, resp);
1219+
}
1220+
1221+
void StreamIfrPriorityRankingRequestHandler(
1222+
int /*thread_id*/,
1223+
feedsim::RequestContext& context,
1224+
std::vector<ThreadData>& /*thread_data*/) {
1225+
ranking::StreamIfrPriorityRankingRequest typed_req;
1226+
try {
1227+
folly::IOBuf buf(
1228+
folly::IOBuf::WRAP_BUFFER, context.payload, context.payload_length);
1229+
apache::thrift::CompactSerializer::deserialize(&buf, typed_req);
1230+
} catch (const std::exception& e) {
1231+
std::cerr << "StreamIfrPriorityRanking: deserialize failed: " << e.what()
1232+
<< std::endl;
1233+
context.sendResponse(nullptr, 0);
1234+
return;
1235+
}
1236+
ranking::StreamIfrPriorityRankingResponse resp;
1237+
resp.ack_code() = 0;
1238+
sendTinyResponse(context, resp);
1239+
}
1240+
11001241
int main(int argc, char** argv) {
11011242
if (cmdline_parser(argc, argv, &args) != 0) {
11021243
std::cerr << "cmdline_parser failed" << std::endl;
@@ -1427,6 +1568,43 @@ int main(int argc, char** argv) {
14271568
return DLRMRequestHandler(thread_id, context, thread_data);
14281569
});
14291570
#endif
1571+
1572+
// Phase 4: register the 5 production-shaped inbound methods. Heavy methods
1573+
// (getStoriesUncompressed, getAllStories) route to DLRMRequestHandler so
1574+
// CPU profile is unchanged. Light methods (createAndPrimeSession,
1575+
// streamData, streamIfrPriorityRanking) send a tiny response to match
1576+
// prod p50 (4-44 B, 3-13 ms latency).
1577+
std::cout << "Registering Phase 4 prod-shaped inbound method handlers"
1578+
<< std::endl;
1579+
server.registerQueryCallback(
1580+
ranking::kCreateAndPrimeSessionRequestType,
1581+
[&thread_data](int thread_id, feedsim::RequestContext& context) {
1582+
return CreateAndPrimeSessionRequestHandler(
1583+
thread_id, context, thread_data);
1584+
});
1585+
server.registerQueryCallback(
1586+
ranking::kGetStoriesUncompressedRequestType,
1587+
[&thread_data](int thread_id, feedsim::RequestContext& context) {
1588+
return GetStoriesUncompressedRequestHandler(
1589+
thread_id, context, thread_data);
1590+
});
1591+
server.registerQueryCallback(
1592+
ranking::kGetAllStoriesRequestType,
1593+
[&thread_data](int thread_id, feedsim::RequestContext& context) {
1594+
return GetAllStoriesRequestHandler(thread_id, context, thread_data);
1595+
});
1596+
server.registerQueryCallback(
1597+
ranking::kStreamDataRequestType,
1598+
[&thread_data](int thread_id, feedsim::RequestContext& context) {
1599+
return StreamDataRequestHandler(thread_id, context, thread_data);
1600+
});
1601+
server.registerQueryCallback(
1602+
ranking::kStreamIfrPriorityRankingRequestType,
1603+
[&thread_data](int thread_id, feedsim::RequestContext& context) {
1604+
return StreamIfrPriorityRankingRequestHandler(
1605+
thread_id, context, thread_data);
1606+
});
1607+
14301608
server.setNumThreads(args.threads_arg);
14311609
server.setThreadPinning(args.noaffinity_given == 0u);
14321610
server.setThreadLoadBalancing(args.noloadbalance_given == 0u);

packages/feedsim/third_party/src/workloads/ranking/RequestTypes.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,27 @@
1515
#ifndef REQUEST_TYPES_H
1616
#define REQUEST_TYPES_H
1717

18+
#include <cstdint>
19+
1820
namespace ranking {
1921

22+
// Legacy request types — Phase 6 deletes these.
2023
// Request type for PageRank/DLRM with server-side feature generation
2124
static const int kPageRankRequestType = 0;
2225

2326
// Request type for DLRM with client-side feature generation (Phase 7)
2427
static const int kDLRMRequestType = 1;
2528

29+
// Phase 4: production-shaped multifeed aggregator inbound methods. Type
30+
// IDs 0x10..0x14 are dispatched by FeedSimServer::registerQueryCallback
31+
// to the per-method shim handlers in LeafNodeRank.cc. See Phase 4
32+
// researcher notes section 4 for the dispatch design.
33+
constexpr uint32_t kCreateAndPrimeSessionRequestType = 0x10;
34+
constexpr uint32_t kGetStoriesUncompressedRequestType = 0x11;
35+
constexpr uint32_t kGetAllStoriesRequestType = 0x12;
36+
constexpr uint32_t kStreamDataRequestType = 0x13;
37+
constexpr uint32_t kStreamIfrPriorityRankingRequestType = 0x14;
38+
2639
} // namespace ranking
2740

2841
#endif // REQUEST_TYPES_H

0 commit comments

Comments
 (0)