ymwneu opened a new issue, #10713:
URL: https://github.com/apache/rocketmq/issues/10713

   ### Before Creating the Enhancement Request
   
   - [x] I have confirmed that this should be classified as an enhancement 
rather than a bug/feature.
   
   
   ### Summary
   
   The current ConsumerFilterManager uses a topic-based index 
(filterDataByTopic) as the sole data structure for managing SQL92 consumer 
filter data. When register(consumerGroup, subList) is called during heartbeat 
processing, it invokes getByGroup(consumerGroup) which performs a full scan 
across all topics and all consumer groups — an O(T × G) operation. In clusters 
with a large number of topics and SQL92 filter consumers, this becomes a 
significant CPU bottleneck. Additionally, there are several thread safety 
issues in the concurrent access patterns.
   
   ### Motivation
   
   During production operations in a large-scale cluster, we observed high CPU 
consumption on the broker caused by ConsumerFilterManager.register(). The root 
cause is the getByGroup() method, which is called on every consumer heartbeat 
to detect unsubscribed topics. It iterates through the entire filterDataByTopic 
map (all topics × all consumer groups per topic) just to collect entries 
belonging to one consumer group. This results in O(T × G) time complexity per 
heartbeat.
   
     For example, with 10,000 topics and 100 SQL92 filter consumer groups, each 
heartbeat triggers a scan of up to 1,000,000 entries. Under high consumer count 
scenarios, this becomes a serious CPU hotspot.
   
     Beyond the performance issue, the existing code also has:
     1. TOCTOU race conditions: Multiple containsKey() + get() sequences on 
ConcurrentHashMap where the entry can be removed between the two calls, leading 
to NullPointerException.
     2. Unconditional BloomFilter generation: bloomFilter.generate() is called 
on every register even when enableCalcFilterBitMap is disabled, wasting CPU 
cycles.
     3. No topic existence validation: Filter data is registered for topics 
that may not exist on the broker, accumulating stale entries.
   
   ### Describe the Solution You'd Like
   
   Introduce a consumerGroup-based index (SubscriptionFilterHandler) alongside 
the existing topic-based index to enable O(1) lookups by consumer group:
   
     1. Add subscriptionFilterData map (ConcurrentMap<String/*consumerGroup*/, 
SubscriptionFilterHandler>): Each SubscriptionFilterHandler holds a 
ConcurrentHashMap<String/*topic*/,ConsumerFilterData> for that consumer group. 
This eliminates the need for full-scan getByGroup() in the hot path.
     2. Dual-index consistency: Both filterDataByTopic (for topic-based queries 
during message dispatch) and subscriptionFilterData (for consumerGroup-based 
operations during heartbeat) are maintained in sync. Writes go through 
subscriptionFilterData first, then update filterDataByTopic.
     3. Conditional BloomFilter generation: Only call bloomFilter.generate() 
when BrokerConfig.isEnableCalcFilterBitMap() is true, consistent with 
CommitLogDispatcherCalcBitMap.
     4. Topic existence check: Validate that the topic exists in 
TopicConfigManager before registering filter data, preventing accumulation of 
stale entries.
     5. Fix TOCTOU race conditions: Replace all containsKey() + get() sequences 
with a single get() assigned to a local variable, then null-check.
     6. Thread safety for SubscriptionFilterHandler: Use ConcurrentHashMap 
instead of HashMap for topicSqlFilterData since it is accessed concurrently.
     7. Rebuild on decode: After deserializing filterDataByTopic from disk, 
rebuild subscriptionFilterData to ensure consistency after broker restart.
   
   ### Describe Alternatives You've Considered
   
   Describe Alternatives You've Considered
   
     1. Cache getByGroup() results: Maintain a separate cache of consumerGroup 
→ topics mapping that is invalidated on register/unregister. This would reduce 
the scan frequency but adds cache invalidation complexity and still requires 
the full scan to rebuild the cache. The dual-index approach is simpler and 
provides O(1) at all times.
     2. Replace filterDataByTopic entirely with a consumerGroup-based 
structure: This would simplify the code but would degrade the topic-based 
lookup path used by CommitLogDispatcherCalcBitMap during message dispatch 
(called on every message), which is performance-critical.
     3. Add synchronization instead of using ConcurrentHashMap: Using 
synchronized blocks would fix thread safety but introduce lock contention on 
the heartbeat path. 
     ConcurrentHashMap provides better concurrent throughput with no contention 
on reads.
   
   ### Additional Context
   
   _No response_


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to