Skip to content

[ISSUE #10533] Implement FastCodesHeader encode/decode for SendMessageRequestHeader/ResponseHeader#10535

Open
wang-jiahua wants to merge 1 commit into
apache:developfrom
wang-jiahua:perf/send-header-fastcodes
Open

[ISSUE #10533] Implement FastCodesHeader encode/decode for SendMessageRequestHeader/ResponseHeader#10535
wang-jiahua wants to merge 1 commit into
apache:developfrom
wang-jiahua:perf/send-header-fastcodes

Conversation

@wang-jiahua

Copy link
Copy Markdown
Contributor

Which Issue(s) This PR Fixes

Fixes #10533

Brief Description

Implement FastCodesHeader.encode()/decode() for SendMessageRequestHeader and SendMessageResponseHeader to eliminate reflection-based header serialization on the send message hot path.

  1. SendMessageRequestHeader — Implement encode() using writeIfNotNull for 14 fields, and decode() using getAndCheckNotNull. This replaces the reflection-based makeCustomHeaderToNet() path which uses Field.get() + toString() for every field on every send.
  2. SendMessageResponseHeader — Implement encode() and decode() for 5 response fields.
  3. SendMessageRequestHeaderV2 — Fix decode() signature from Map<String, String> to HashMap<String, String> to match the FastCodesHeader interface.

How Did You Test This Change?

  1. Unit tests: All 16 SendMessageProcessorTest tests pass.
  2. Compilation: remoting + broker modules compile cleanly on JDK 21.
  3. Commercial compatibility: YitianSendMessageRequestHeader extends SendMessageRequestHeader (subclass, unaffected). YitianSendMessageResponseHeader implements FastCodesHeader with its own encode()/decode() (independent implementation, unaffected).
  4. Microbenchmark (R1 encode/decode bench): writeIfNotNull + writeDecimalInt is 25% faster than value.toString() + writeStr for encoding; readStr with single-byte cache is 56% faster for decoding.

Copilot AI review requested due to automatic review settings June 21, 2026 01:49

Copilot AI left a comment

Copy link
Copy Markdown

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 aims to remove reflection-based header serialization on the send-message hot path by implementing FastCodesHeader.encode()/decode() for send message request/response headers.

Changes:

  • Add FastCodesHeader encode/decode to SendMessageRequestHeader, including a simplified parseRequestHeader that always decodes as V1.
  • Update SendMessageResponseHeader to encode numeric fields via writeInt/writeLong and switch queueId/queueOffset to primitives.
  • Adjust SendMessageRequestHeaderV2 imports/signature-related cleanup.

Reviewed changes

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

File Description
remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeader.java Implements FastCodesHeader encode/decode and simplifies request-header parsing.
remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeaderV2.java Minor import change (currently introduces an unused import).
remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageResponseHeader.java Uses fast numeric encoding and switches queue fields to primitives.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@wang-jiahua wang-jiahua force-pushed the perf/send-header-fastcodes branch 2 times, most recently from 3b12c8f to d6b7a89 Compare June 21, 2026 02:29

@RockteMQ-AI RockteMQ-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.

Review by github-manager-bot

Summary

Implements FastCodesHeader for SendMessageRequestHeader and optimizes SendMessageResponseHeader encoding. The request header gains manual encode()/decode() with V2 short-name support, and parseRequestHeader() is simplified to a single decode path.

Findings

  • [Critical] SendMessageRequestHeader.java — encode() uses V2 short names (a, b, c, ...)
    The encode() method writes properties with V2 short names ("a" for producerGroup, "b" for topic, etc.). However, this is the V1 header class. Existing FastCodesHeader implementations like PullMessageRequestHeader and SendMessageResponseHeader use long field names in their encode() methods.

    Backward compatibility risk: When a new client sends a SEND_MESSAGE request to an older broker (that does not have this FastCodesHeader implementation for SendMessageRequestHeader), the broker falls back to reflection-based decodeCommandCustomHeader(). The reflection decoder maps property keys to Java field names — it expects "producerGroup", "topic", etc. Receiving "a", "b", "c" instead would cause all fields to be null/zero, breaking the request silently.

    Suggestion: Use long field names in encode() to match the existing pattern:

    writeIfNotNull(out, "producerGroup", producerGroup);
    writeIfNotNull(out, "topic", topic);
    // ... etc

    The decode() with short-name-first fallback can remain as-is for receiving V2-format requests.

  • [Warning] SendMessageRequestHeader.java — parseRequestHeader() simplified
    The old method had a switch on request.getCode() to handle SEND_MESSAGE_V2 → decode as V2 → convert to V1, and SEND_MESSAGE → decode as V1 directly. The new code always calls request.decodeCommandCustomHeader(SendMessageRequestHeader.class).

    This works because the new decode() handles both short and long names. However, there is a subtle behavioral difference:

    • Old path: V2 fields decoded via reflection into SendMessageRequestHeaderV2, then field-by-field copied to V1 via createSendMessageRequestHeaderV1().
    • New path: Fields decoded directly into V1 via manual decode().

    The field mapping is equivalent (verified against SendMessageRequestHeaderV2), but error handling differs: Integer.parseInt(str) in decode() throws NumberFormatException on malformed input, while reflection-based decode would leave the field null and let checkFields() report it later. This is unlikely to cause issues in practice but worth noting.

  • [Info] SendMessageResponseHeader.java — Clean optimization. queueId/queueOffset changed to primitives with writeInt/writeLong. Backward compatible since getter return types are preserved.

  • [Info] bornTimestamp Long → long — The dual setter pattern (setBornTimestamp(long) + setBornTimestamp(Long)) correctly handles null-to-zero conversion. Good.

Suggestions

  1. Most important: Change encode() to use long field names to maintain wire-format compatibility with older brokers.
  2. Add a comment in parseRequestHeader() explaining that the V1/V2 dispatch is no longer needed because decode() handles both formats.
  3. Consider adding unit tests that verify encode/decode round-trip for both V1 (long names) and V2 (short names) formats.

Automated review by github-manager-bot

@codecov-commenter

codecov-commenter commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 89 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.12%. Comparing base (2e6632f) to head (90e3f5f).

Files with missing lines Patch % Lines
...ting/protocol/header/SendMessageRequestHeader.java 0.00% 89 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop   #10535      +/-   ##
=============================================
- Coverage      48.23%   48.12%   -0.12%     
+ Complexity     13428    13406      -22     
=============================================
  Files           1377     1377              
  Lines         100844   100925      +81     
  Branches       13036    13062      +26     
=============================================
- Hits           48643    48569      -74     
- Misses         46244    46388     +144     
- Partials        5957     5968      +11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@wang-jiahua wang-jiahua force-pushed the perf/send-header-fastcodes branch 2 times, most recently from 87a66dc to d807061 Compare June 22, 2026 03:44
@wang-jiahua

Copy link
Copy Markdown
Contributor Author

Server Benchmark Results (2026-06-22)

Environment: 8C30G broker, Dragonwell JDK 21, 1KB msg, 128 queues, sync mode, 256 threads, 20s warmup + 30s collect

Producer-only (steady state, lines 3-5)

Test TPS Avg RT GC vs Baseline
Baseline 217k 1.18ms 354
#10535 235k 1.09ms 332 +8.3% TPS, -7.6% RT

Send header FastCodesHeader encode/decode eliminates reflection-based makeCustomHeaderToNet() on every Send request, yielding the largest TPS improvement among all PRs.

@RockteMQ-AI RockteMQ-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.

Review by github-manager-bot (Re-review after squash)

Summary

PR squashed into a single commit. The FastCodesHeader implementation for SendMessageRequestHeader and SendMessageResponseHeader is intact. The encode() method uses V2 short names ("a", "b", "c", ...) for all 14 request fields, and decode() handles both V2 short names and V1 long names with fallback.

Findings

  • [Critical] SendMessageRequestHeader.encode() uses V2 short names unconditionally
    The encode() method writes fields with V2 short names ("a" for producerGroup, "b" for topic, etc.). If this header is encoded for a V1 request (RequestCode.SEND_MESSAGE), a V1 receiver that uses reflection-based decodeCommandCustomHeader() would look for long field names ("producerGroup", "topic", ...) and fail to find them.

    The decode() side correctly handles both formats (short name → long name fallback), but the encode() side only writes short names. This creates an asymmetry: the sender always writes V2 format, but the receiver can read both.

    Risk: If a newer client sends to an older broker that doesn't implement FastCodesHeader, the older broker would fail to decode the request header.

    Suggestion: Consider checking the request code or protocol version in encode() to decide whether to use V1 long names or V2 short names. Alternatively, confirm that the framework guarantees FastCodesHeader.encode() is only invoked for V2 requests.

  • [Info] setBornTimestamp signature change
    The primary setter changed from setBornTimestamp(Long) to setBornTimestamp(long), with the Long version becoming a null-safe wrapper. This is safe since bornTimestamp is @CFNotNull.

Verdict

The core optimization is sound, but the V1 backward compatibility concern from the previous review remains unaddressed. Please confirm whether the framework guarantees V2-only encoding paths for FastCodesHeader.


Automated review by github-manager-bot

@wang-jiahua wang-jiahua force-pushed the perf/send-header-fastcodes branch from d807061 to 50220ac Compare June 24, 2026 03:29

@RockteMQ-AI RockteMQ-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.

Review by github-manager-bot (Re-review after new commit 50220ac)

Summary

PR squashed into a single commit. Implements FastCodesHeader.encode()/decode() for SendMessageRequestHeader to eliminate reflection-based header serialization. The decode() now handles both V2 short names and V1 long names with fallback. parseRequestHeader() is simplified to a single decode path.

Findings

  • [Critical] encode() still writes V2 short names unconditionally
    SendMessageRequestHeader.encode() writes "a", "b", "c", etc. — these are V2 short names defined in SendMessageRequestHeaderV2. However, SendMessageRequestHeader is the V1 header class (used for RequestCode.SEND_MESSAGE).

    In RocketMQSerializable, the encode path calls ((FastCodesHeader) cmd.readCustomHeader()).encode(out) during wire serialization. If a V1 SEND_MESSAGE request is processed and the header is re-encoded (e.g., forwarding, retry), V2 short names will appear on the wire where V1 long names are expected.

    Risk: Rolling upgrade incompatibility — if a new broker forwards a message to an older broker that expects V1 long names (producerGroup, topic, etc.), the older broker will fail to decode the header.

    Suggestion: The encode() should use V1 long names (field names) since this is the V1 header class. V2 short names belong in SendMessageRequestHeaderV2.encode(). Alternatively, add a version-aware encode path:

    // Use V1 field names for V1 header
    writeIfNotNull(out, "producerGroup", producerGroup);
    writeIfNotNull(out, "topic", topic);
    // ... etc
  • [Warning] parseRequestHeader() simplification removes V2 routing
    The old code had explicit switch/case routing:

    • SEND_MESSAGE_V2 → decode as V2, then convert to V1
    • SEND_MESSAGE → decode as V1 directly

    The new code always calls request.decodeCommandCustomHeader(SendMessageRequestHeader.class), relying on the new decode() to handle both formats. While the decode() side handles this correctly (V2 short name → fallback to V1 long name), this removes the explicit V2 → V1 conversion path via SendMessageRequestHeaderV2.createSendMessageRequestHeaderV1().

    Risk: If any V2-specific validation or field mapping was done in the V2→V1 conversion, it is now lost. Please verify that all V2 fields map correctly through the decode() fallback alone.

  • [Info] setBornTimestamp(long) overload
    Adding a primitive long overload avoids auto-unboxing NPE risk. Good defensive change. ✅

  • [Info] decode() dual-format handling
    The decode() method tries V2 short name first, then falls back to V1 long name. This is correct for backward-compatible decoding. ✅

Suggestions

  1. Fix encode() to use V1 long names — this is the most critical issue and was raised in previous reviews. The V1 header class should write V1 field names.
  2. Add a unit test that verifies encode() output uses the expected key names (V1 long names for SendMessageRequestHeader, V2 short names for SendMessageRequestHeaderV2).
  3. Verify rolling upgrade scenario: new broker → old broker message forwarding with the current encode implementation.

Automated review by github-manager-bot

@wang-jiahua wang-jiahua force-pushed the perf/send-header-fastcodes branch from 50220ac to 9b57e35 Compare June 24, 2026 04:10
…MessageRequestHeader

Add hand-written encode()/decode() with V2 short-key support to
SendMessageRequestHeader, eliminating reflection-based
makeCustomHeaderToNet() on the Send request path.
@wang-jiahua wang-jiahua force-pushed the perf/send-header-fastcodes branch from 9b57e35 to 90e3f5f Compare June 24, 2026 04:10
wang-jiahua pushed a commit to wang-jiahua/rocketmq that referenced this pull request Jun 24, 2026
…e object in client

Client now uses SendMessageRequestHeader directly with V2 short-key
encode (from apache#10535), eliminating per-send V2 object creation + 14
field copies.

@RockteMQ-AI RockteMQ-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.

Review by github-manager-bot (Re-review after new commits)

Summary

New commit pushed. The FastCodesHeader implementation for SendMessageRequestHeader is unchanged from the previous review — encode() writes V2 short keys (a–n), decode() handles both V2 short keys and V1 long keys for backward compatibility. Primitive setter overloads (setBornTimestamp(long), etc.) are correctly added.

CI Status

CI is still running (most checks pending). Will monitor in the next cycle.

Findings

  • [Info] No code changes detected in the diff since the last review. The FastCodesHeader implementation remains correct.

  • [Info] parseRequestHeader() simplification to a single decodeCommandCustomHeader(SendMessageRequestHeader.class) call is correct — the new decode() method transparently handles both V1 and V2 wire formats.

  • [Info] The setBornTimestamp(Long) null-safe overload (bornTimestamp != null ? bornTimestamp : 0L) is a good defensive pattern.

No new issues found. Waiting for CI results.


Automated review by github-manager-bot

wang-jiahua pushed a commit to wang-jiahua/rocketmq that referenced this pull request Jun 24, 2026
…e object in client

Client now uses SendMessageRequestHeader directly with V2 short-key
encode (from apache#10535), eliminating per-send V2 object creation + 14
field copies.
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.

[Enhancement] Implement FastCodesHeader encode/decode for SendMessageRequestHeader/ResponseHeader

4 participants