Skip to content

Feat: Add adaptive input message batching - #3484

Open
FangzuoZhang wants to merge 3 commits into
apache:masterfrom
FangzuoZhang:feature/adaptive-input-message-batching
Open

Feat: Add adaptive input message batching#3484
FangzuoZhang wants to merge 3 commits into
apache:masterfrom
FangzuoZhang:feature/adaptive-input-message-batching

Conversation

@FangzuoZhang

@FangzuoZhang FangzuoZhang commented Aug 24, 2026

Copy link
Copy Markdown

What problem does this PR solve?

Issue Number: N/A

Related work:

Problem Summary:

InputMessenger currently schedules each parsed input message in a separate
bthread. For short message handlers and bursty traffic on a single connection,
bthread creation and scheduling may account for a significant portion of the
request-processing overhead.

This PR introduces optional input message batching. Messages parsed from the
same socket can be processed sequentially in one bthread, reducing scheduling
overhead while preserving the existing behavior by default.

What is changed and the side effects?

Design

Parsed messages
  m1  m2  m3  ...  mN
           |
           v
+--------------------------+
| Batch Size Controller    |
| fixed or adaptive (EMA)  |
| size: 1 / 2 / 4 / 8 / 16|
+------------+-------------+
             |
             v
+--------------------------+
| InputMessageBatch        |
| [m1, m2, ... , mk]       |
+------------+-------------+
             |
             v
      One bthread
             |
             v
  m1 -> m2 -> ... -> mk
  processed sequentially

Changed:

  • Add the experimental input_message_batch_process_size gflag:
    • 0 or 1: preserve the original one-message-per-bthread behavior.
    • Values greater than 1: use a fixed batch size.
    • -1: adaptively select a batch size from 1, 2, 4, 8, and 16.
    • Values smaller than -1 are rejected.
  • Add InputMessageBatch to own and process messages in their original order.
  • Add per-socket adaptive state based on an exponentially weighted moving
    average of messages parsed from each read.
  • Increase the adaptive batch size gradually and decrease it more quickly when
    the observed burst size drops.
  • Reset adaptive history after switching away from adaptive mode.
  • Support batch scheduling in TCP, RDMA, and UBShm/UBRing transports.
  • Preserve the existing last-message scheduling optimization.
  • Keep progressive-read messages on the individual-message path.
  • Disable batching when user code runs in coroutine mode.
  • Fall back to synchronous processing if batch allocation or bthread creation
    fails.

Side effects:

  • Performance effects:

    The default value is 0, so existing deployments retain the original
    scheduling behavior.

    When batching is enabled, it reduces bthread creation and scheduling overhead
    for bursty workloads. A larger fixed batch may increase the time that later
    messages wait behind earlier handlers. Adaptive mode limits the maximum batch
    size to 16 and decreases the batch size quickly when the observed burst size
    drops.

  • Breaking backward compatibility:

    There is no change to the public RPC protocol or default runtime behavior.

    The internal Transport interface gains a QueueMessages virtual method.
    Downstream custom transport implementations derived directly from
    Transport must implement this method.

Performance test

The RDMA performance example was used with a single connection and multiple
outstanding requests on that connection. Each attachment size was tested three
times.

  • Baseline: input_message_batch_process_size=0
  • Optimized: input_message_batch_process_size=-1
  • queue_depth must be greater than 1 to produce message bursts on the same
    connection.
  • Latency values are in microseconds.
  • CPU utilization may exceed 100% because it represents multi-core process CPU
    usage.
  • QPS improvement is calculated against the average QPS of the unoptimized
    baseline.

Average results

The following results were measured on a Kunpeng 950 server.
Each attachment size was tested three times, and the reported values are the
arithmetic averages of those runs.

  • Baseline: input_message_batch_process_size=0
  • Optimized: input_message_batch_process_size=-1
  • Latency values are in microseconds.
  • CPU utilization may exceed 100% because it represents multi-core process CPU
    usage.
  • QPS improvement is calculated against the average QPS of the baseline.
Attachment Avg Latency P90 P99 Baseline QPS Batched QPS Avg Server CPU Client CPU QPS Improvement
0 bytes 351.00 543.00 810.00 2684.100 2886.265 1336.00% 3950.67% +7.53%
256 bytes 398.00 610.67 927.00 2377.408 2545.978 1315.00% 4798.00% +7.09%
1 KB 438.33 683.67 1044.67 2174.254 2320.362 1322.67% 3732.67% +6.72%
4 KB 660.67 1135.67 2123.33 1486.802 1544.824 1270.00% 2577.33% +3.90%
8 KB 1031.00 2310.00 5289.33 1049.357 1104.763 1177.00% 1993.33% +5.28%
100 KB 7388.67 12362.00 22406.67 134.602 138.385 925.33% 988.00% +2.81%

Check List:

@FangzuoZhang FangzuoZhang changed the title Add adaptive input message batching Feat: Add adaptive input message batching Aug 24, 2026
@chenBright
chenBright requested a lite review from Copilot August 24, 2026 14:19

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 PR adds an optional (experimental) input-message batching path to reduce bthread creation/scheduling overhead under bursty traffic by processing multiple parsed messages from the same socket sequentially in a single bthread, while preserving the existing behavior by default.

Changes:

  • Introduces -input_message_batch_process_size (0/1 disabled, fixed >1, adaptive -1) and implements InputMessageBatch + batch scheduling in InputMessenger.
  • Extends the internal Transport interface with QueueMessages(...) and implements it in TCP/RDMA/UBShm transports.
  • Adds per-socket adaptive batching state (EMA + current batch size) and exposes debug output for the new stats.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/brpc/input_messenger.h Adds InputMessageBatch type and batching-related helper declarations.
src/brpc/input_messenger.cpp Implements batching flag, adaptive sizing logic, and batch enqueueing/flush behavior.
src/brpc/transport.h Adds new pure-virtual QueueMessages API for transports.
src/brpc/tcp_transport.h Declares TCP transport support for QueueMessages.
src/brpc/tcp_transport.cpp Implements batch enqueue via bthread for TCP transport.
src/brpc/rdma_transport.h Declares RDMA transport support for QueueMessages.
src/brpc/rdma_transport.cpp Implements batch enqueue via bthread for RDMA transport.
src/brpc/ubshm_transport.h Declares UBShm transport support for QueueMessages.
src/brpc/ubshm_transport.cpp Implements batch enqueue via bthread for UBShm transport.
src/brpc/socket.h Adds per-socket adaptive batching state fields.
src/brpc/socket.cpp Initializes/resets new per-socket adaptive state; adds debug printing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +254 to +256
if (!batch->get()) {
batch->reset(new (std::nothrow) InputMessageBatch(batch_size));
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The initial vector reservation is now capped at the adaptive
maximum batch size. A larger fixed batch size can still grow dynamically, but
a user-controlled flag can no longer trigger a huge upfront allocation.

Comment thread src/brpc/ubshm_transport.cpp Outdated
Comment on lines +177 to +188
void UBShmTransport::QueueMessages(InputMessageBatch* input_msgs,
int* num_bthread_created, bool last_msg) {
CHECK(!last_msg);
if (!input_msgs || input_msgs->empty()) {
delete input_msgs;
return;
}
if (ubring::FLAGS_ub_disable_bthread) {
input_msgs->Run();
delete input_msgs;
return;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed the unused last_msg parameter from the batch scheduling interface.
The final RDMA/UBRing message continues to use the single-message path.

Comment thread src/brpc/rdma_transport.cpp Outdated
Comment on lines +186 to +197
void RdmaTransport::QueueMessages(InputMessageBatch* input_msgs,
int* num_bthread_created, bool last_msg) {
CHECK(!last_msg || rdma::FLAGS_rdma_use_polling);
if (!input_msgs || input_msgs->empty()) {
delete input_msgs;
return;
}
if (rdma::FLAGS_rdma_disable_bthread) {
input_msgs->Run();
delete input_msgs;
return;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed the unused last_msg parameter from the batch scheduling interface.
The final RDMA/UBRing message continues to use the single-message path.

@wwbmmm
wwbmmm requested a lite review from Copilot August 27, 2026 12:36

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

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment thread src/brpc/input_messenger.h Outdated
public:
InputMessageBatch() {}
explicit InputMessageBatch(size_t capacity);
~InputMessageBatch() noexcept(false);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in commit 95368db.

InputMessageBatch::~InputMessageBatch is now noexcept. Before invoking a handler, Run() clears the corresponding message slot so that an already-delivered message cannot be processed again.

The destructor catches all exceptions raised during handler execution, logs an English error message, and destroys any remaining undelivered messages. The bthread batch entry also contains handler exceptions so that they cannot escape through the bthread callback boundary.

The unit test verifies the nothrow destructor property, injects a handler exception, confirms that the exception does not propagate, and checks that the remaining messages are cleaned up exactly once.

Comment on lines +457 to 463
if (batch_process) {
QueueInputMessageBatch(m, &input_batch, &num_bthread_created);
}
if (m->_socket_mode == SOCKET_MODE_RDMA ||
m->_socket_mode == SOCKET_MODE_UBRING) {
m->_transport->QueueMessage(last_msg, &num_bthread_created, true);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in commit 95368db.

When batching is enabled, the final last_msg is now appended to the current InputMessageBatch before the batch is scheduled. It is therefore processed sequentially with the preceding messages in that batch instead of being scheduled separately.

When batching is disabled, the existing RDMA/UBRing last-message optimization is preserved. Progressive-read messages also remain on the original individual-message path.

The batching_consumes_the_last_message unit test verifies that the final message is consumed by the current batch, processed in parsing order, and no longer remains in InputMessageClosure.

Comment on lines +106 to +125
void TcpTransport::QueueMessages(InputMessageBatch* input_msgs,
int* num_bthread_created) {
if (!input_msgs || input_msgs->empty()) {
delete input_msgs;
return;
}
bthread_t th;
bthread_attr_t tmp =
(FLAGS_usercode_in_pthread ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL) |
BTHREAD_NOSIGNAL;
tmp.keytable_pool = _socket->keytable_pool();
tmp.tag = bthread_self_tag();
if (!FLAGS_usercode_in_coroutine && bthread_start_background(
&th, &tmp, ProcessInputMessageBatch, input_msgs) == 0) {
++*num_bthread_created;
} else {
input_msgs->Run();
delete input_msgs;
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in commit 95368db.

I extracted the common batch scheduling logic into Transport::QueueInputMessageBatch. The shared helper now handles:

  • empty batches;
  • transport-specific synchronous execution;
  • bthread attributes, name, keytable pool, and tag;
  • synchronous fallback when bthread creation fails;
  • num_bthread_created accounting.

TcpTransport, RdmaTransport, and UBShmTransport now only provide their transport-specific synchronous execution condition and delegate the remaining work to the shared helper.

The unit tests cover the empty-batch path, synchronous execution, asynchronous scheduling, and bthread creation accounting.

@wasphin

wasphin commented Aug 28, 2026

Copy link
Copy Markdown
Member

Please also add the corresponding unit tests.

Centralize batch scheduling for TCP, RDMA, and UBShm transports. Make input batch cleanup exception-safe and cover batching behavior with unit tests.
@FangzuoZhang

Copy link
Copy Markdown
Author

Please also add the corresponding unit tests.

Thanks. Corresponding unit tests have been added in commit 95368db.

The tests cover:

  • in-order and exactly-once batch processing;
  • inclusion of the final message in the current batch;
  • unchanged behavior when batching is disabled;
  • unchanged progressive-read behavior;
  • nothrow destruction and handler exception containment;
  • cleanup of remaining messages;
  • shared transport scheduling for empty, synchronous, and asynchronous paths;
  • bthread creation accounting;
  • gflag validation;
  • adaptive batch growth, reduction, sample capping, and socket reset.

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