[ISSUE #10527] Reduce auxiliary component allocation via AtomicLong, string caches, StringBuilder reuse, and dirty flag#10528
Conversation
…Long, string caches, StringBuilder reuse, and dirty flag - QueueOffsetOperator: ConcurrentMap<String,Long> -> ConcurrentMap<String,AtomicLong> - BrokerStatsManager: cache buildStatsKey/topicQueueKey/consumerOffset strings, Integer->int params - IndexService: reusable StringBuilder via ThreadLocal - TimerWheel: volatile dirty flag to skip unnecessary flush - AppendMessageResult: constructor with pre-computed fields - Fix BrokerStatsManagerTest: QUEUE_ID Integer->int
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR focuses on reducing allocation/flush overhead in the store layer by introducing cached key builders, adding a “dirty” optimization for timer wheel flushing, and improving queue offset updates with atomic counters.
Changes:
- Add key caching and primitive
intqueueId APIs inBrokerStatsManagerto reduce stats-key allocation. - Optimize
TimerWheel.flush()using adirtyflag and 8-byte aligned comparisons to reduce unnecessary IO/copying. - Replace queue offset maps from
Longvalues toAtomicLongfor safer concurrent increments; add key-building reuse inIndexService.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| store/src/test/java/org/apache/rocketmq/store/stats/BrokerStatsManagerTest.java | Align test constant type (Integer → int) with production API changes. |
| store/src/main/java/org/apache/rocketmq/store/timer/TimerWheel.java | Add dirty gating and faster flush loop to reduce forced writes. |
| store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java | Add multiple caches for stats key strings and switch queueId params to primitive int. |
| store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java | Use AtomicLong to avoid lost updates on concurrent offset increments; add snapshot/convert helpers. |
| store/src/main/java/org/apache/rocketmq/store/index/IndexService.java | Reuse a StringBuilder for key construction to reduce temporary allocations. |
| store/src/main/java/org/apache/rocketmq/store/AppendMessageResult.java | Add an additional constructor overload mirroring existing fields. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Review by github-manager-bot
Summary
This PR reduces auxiliary component allocation in the store layer through five optimization strategies: reusable StringBuilder in IndexService, AtomicLong-based offset tracking in QueueOffsetOperator, string caching in BrokerStatsManager, dirty-flag flush gating in TimerWheel, and a new AppendMessageResult constructor overload. Overall direction is sound — reducing per-message allocation pressure in hot paths.
Findings
-
[Critical]
IndexService.java:52— Thread-safety violation withreusableKeyBuilder.IndexServiceis shared across threads (commitlog dispatch, query, etc.), andbuildKey()is called from bothqueryOffset()(read lock) andbuildIndex()(write lock). A shared mutableStringBuilderwithout synchronization will produce corrupted keys under concurrent access. The JVM may even reordersetLength(0)andappend()calls across threads. This is a data corruption risk — keys will be silently wrong, causing index lookups to miss or return incorrect offsets.- Fix: Either use a
ThreadLocal<StringBuilder>, pass the builder as a parameter from callers that already hold appropriate locks, or revert to the original string concatenation (the JIT will optimizetopic + "#" + keyinto aStringBuilderanyway, and the allocation is short-lived).
- Fix: Either use a
-
[Critical]
TimerWheel.java:58,137,151,303,313,326,332—dirtyflag race condition. The patternif (!dirty) return; ... dirty = false;is a non-atomic check-then-act. Between theif (!dirty)guard and the finaldirty = false, another thread callingsetDirty(true)can have its update lost — the flush proceeds but then resets the flag, losing the knowledge that new data was written. Over time this can cause the timer wheel to skip flushes, leading to data loss on crash.- Fix: Use
AtomicBooleanwithcompareAndSet(true, false)to atomically claim the dirty state, or synchronize the flush method. Alternatively, accept that some flushes may be redundant (safe) by checkingdirtyagain after the flush.
- Fix: Use
-
[Warning]
BrokerStatsManager.java:496— Unbounded cache growth. ThestatsKeyByGroupCacheand similarConcurrentHashMap<String, String[]>caches grow without eviction. In deployments with thousands of topics × groups, this can accumulate significant heap. Consider adding a size cap or using a bounded cache (e.g., Caffeine withmaximumSize). -
[Info]
QueueOffsetOperator.java— The change fromLongtoAtomicLongfor offset values is a good correctness improvement (atomic increment vs. non-atomic read-modify-write). ThegetTopicQueueTable()snapshot copy is appropriate for iteration safety.
Suggestions
- The
IndexServicethread-safety issue is a blocker — please fix before merge. - The
TimerWheeldirty flag issue should also be addressed; considerAtomicBoolean.getAndSet(false)as a minimal fix. - Consider adding a JMH benchmark for the hot paths to quantify the allocation reduction.
Automated review by github-manager-bot
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #10528 +/- ##
=============================================
- Coverage 48.19% 48.07% -0.12%
+ Complexity 13394 13381 -13
=============================================
Files 1377 1377
Lines 100730 100850 +120
Branches 13012 13042 +30
=============================================
- Hits 48542 48487 -55
- Misses 46254 46392 +138
- Partials 5934 5971 +37 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Which Issue(s) This PR Fixes
Fixes #10527
Brief Description
Reduce allocation in auxiliary store components through five independent optimizations:
QueueOffsetOperator— ReplaceConcurrentMap<String, Long>withConcurrentMap<String, AtomicLong>. EliminatesLongboxing on every queue offset update.BrokerStatsManager— CachebuildStatsKey/topicQueueKey/consumerOffsetstring results. ChangeIntegerparameters tointinincQueue*methods to eliminate autoboxing.IndexService— ReuseStringBuildervia member field instead of allocating per-call.TimerWheel— Add volatiledirtyflag to skip flush when no changes occurred since last flush.AppendMessageResult— Add constructor with pre-computed fields to avoid redundant allocation.How Did You Test This Change?
BrokerStatsManagerTesttests pass (includingtestOnTopicDeletedwhich exercises allincQueue*methods).storemodule compiles cleanly on JDK 21.QueueOffsetOperator,BrokerStatsManager,IndexService,TimerWheel, orAppendMessageResult.