[ISSUE #10533] Implement FastCodesHeader encode/decode for SendMessageRequestHeader/ResponseHeader#10535
Conversation
There was a problem hiding this comment.
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
FastCodesHeaderencode/decode toSendMessageRequestHeader, including a simplifiedparseRequestHeaderthat always decodes as V1. - Update
SendMessageResponseHeaderto encode numeric fields viawriteInt/writeLongand switchqueueId/queueOffsetto primitives. - Adjust
SendMessageRequestHeaderV2imports/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.
3b12c8f to
d6b7a89
Compare
RockteMQ-AI
left a comment
There was a problem hiding this comment.
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, ...)
Theencode()method writes properties with V2 short names ("a"for producerGroup,"b"for topic, etc.). However, this is the V1 header class. ExistingFastCodesHeaderimplementations likePullMessageRequestHeaderandSendMessageResponseHeaderuse long field names in theirencode()methods.Backward compatibility risk: When a new client sends a
SEND_MESSAGErequest to an older broker (that does not have thisFastCodesHeaderimplementation forSendMessageRequestHeader), the broker falls back to reflection-baseddecodeCommandCustomHeader(). 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 aswitchonrequest.getCode()to handleSEND_MESSAGE_V2→ decode as V2 → convert to V1, andSEND_MESSAGE→ decode as V1 directly. The new code always callsrequest.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 viacreateSendMessageRequestHeaderV1(). - 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)indecode()throwsNumberFormatExceptionon malformed input, while reflection-based decode would leave the field null and letcheckFields()report it later. This is unlikely to cause issues in practice but worth noting. - Old path: V2 fields decoded via reflection into
-
[Info] SendMessageResponseHeader.java — Clean optimization.
queueId/queueOffsetchanged to primitives withwriteInt/writeLong. Backward compatible since getter return types are preserved. -
[Info]
bornTimestampLong → long — The dual setter pattern (setBornTimestamp(long)+setBornTimestamp(Long)) correctly handles null-to-zero conversion. Good.
Suggestions
- Most important: Change
encode()to use long field names to maintain wire-format compatibility with older brokers. - Add a comment in
parseRequestHeader()explaining that the V1/V2 dispatch is no longer needed becausedecode()handles both formats. - 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 Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
87a66dc to
d807061
Compare
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)
Send header FastCodesHeader encode/decode eliminates reflection-based |
RockteMQ-AI
left a comment
There was a problem hiding this comment.
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
Theencode()method writes fields with V2 short names ("a"forproducerGroup,"b"fortopic, etc.). If this header is encoded for a V1 request (RequestCode.SEND_MESSAGE), a V1 receiver that uses reflection-baseddecodeCommandCustomHeader()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 theencode()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 guaranteesFastCodesHeader.encode()is only invoked for V2 requests. -
[Info]
setBornTimestampsignature change
The primary setter changed fromsetBornTimestamp(Long)tosetBornTimestamp(long), with theLongversion becoming a null-safe wrapper. This is safe sincebornTimestampis@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
d807061 to
50220ac
Compare
RockteMQ-AI
left a comment
There was a problem hiding this comment.
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 inSendMessageRequestHeaderV2. However,SendMessageRequestHeaderis the V1 header class (used forRequestCode.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 inSendMessageRequestHeaderV2.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 explicitswitch/caserouting:SEND_MESSAGE_V2→ decode as V2, then convert to V1SEND_MESSAGE→ decode as V1 directly
The new code always calls
request.decodeCommandCustomHeader(SendMessageRequestHeader.class), relying on the newdecode()to handle both formats. While thedecode()side handles this correctly (V2 short name → fallback to V1 long name), this removes the explicit V2 → V1 conversion path viaSendMessageRequestHeaderV2.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 primitivelongoverload avoids auto-unboxing NPE risk. Good defensive change. ✅ -
[Info]
decode()dual-format handling
Thedecode()method tries V2 short name first, then falls back to V1 long name. This is correct for backward-compatible decoding. ✅
Suggestions
- 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. - Add a unit test that verifies
encode()output uses the expected key names (V1 long names forSendMessageRequestHeader, V2 short names forSendMessageRequestHeaderV2). - Verify rolling upgrade scenario: new broker → old broker message forwarding with the current encode implementation.
Automated review by github-manager-bot
50220ac to
9b57e35
Compare
…MessageRequestHeader Add hand-written encode()/decode() with V2 short-key support to SendMessageRequestHeader, eliminating reflection-based makeCustomHeaderToNet() on the Send request path.
9b57e35 to
90e3f5f
Compare
…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
left a comment
There was a problem hiding this comment.
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
FastCodesHeaderimplementation remains correct. -
[Info]
parseRequestHeader()simplification to a singledecodeCommandCustomHeader(SendMessageRequestHeader.class)call is correct — the newdecode()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
…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.
Which Issue(s) This PR Fixes
Fixes #10533
Brief Description
Implement
FastCodesHeader.encode()/decode()forSendMessageRequestHeaderandSendMessageResponseHeaderto eliminate reflection-based header serialization on the send message hot path.SendMessageRequestHeader— Implementencode()usingwriteIfNotNullfor 14 fields, anddecode()usinggetAndCheckNotNull. This replaces the reflection-basedmakeCustomHeaderToNet()path which usesField.get()+toString()for every field on every send.SendMessageResponseHeader— Implementencode()anddecode()for 5 response fields.SendMessageRequestHeaderV2— Fixdecode()signature fromMap<String, String>toHashMap<String, String>to match theFastCodesHeaderinterface.How Did You Test This Change?
SendMessageProcessorTesttests pass.remoting+brokermodules compile cleanly on JDK 21.YitianSendMessageRequestHeader extends SendMessageRequestHeader(subclass, unaffected).YitianSendMessageResponseHeader implements FastCodesHeaderwith its ownencode()/decode()(independent implementation, unaffected).writeIfNotNull+writeDecimalIntis 25% faster thanvalue.toString()+writeStrfor encoding;readStrwith single-byte cache is 56% faster for decoding.