CRITICAL: Gateway calling LookupTopicBrokers on EVERY fetch to translate Kafka partition IDs to SeaweedFS partition ranges! Problem (from CPU profile): - getActualPartitionAssignment: 13.52% CPU (1.71s out of 12.65s) - Called bc.client.LookupTopicBrokers on line 228 for EVERY fetch - With 250 fetches/sec, this means 250 LookupTopicBrokers calls/sec! - No caching at all - same overhead as broker had before optimization Root Cause: Gateway needs to translate Kafka partition IDs (0, 1, 2...) to SeaweedFS partition ranges (0-341, 342-682, etc.) for every fetch request. This translation requires calling LookupTopicBrokers to get partition assignments. Without caching, every fetch request triggered: 1. gRPC call to broker (LookupTopicBrokers) 2. Broker reads from its cache (fast now after broker optimization) 3. gRPC response back to gateway 4. Gateway computes partition range mapping The gRPC round-trip overhead was consuming 13.5% CPU even though broker cache was fast! Solution: Added partitionAssignmentCache to BrokerClient: Changes to types.go: - Added partitionAssignmentCacheEntry struct (assignments + expiresAt) - Added cache fields to BrokerClient: * partitionAssignmentCache map[string]*partitionAssignmentCacheEntry * partitionAssignmentCacheMu sync.RWMutex * partitionAssignmentCacheTTL time.Duration Changes to broker_client.go: - Initialize partitionAssignmentCache in NewBrokerClientWithFilerAccessor - Set partitionAssignmentCacheTTL to 30 seconds (same as broker) Changes to broker_client_publish.go: - Added "time" import - Modified getActualPartitionAssignment() to check cache first: * Cache HIT: Use cached assignments (fast ✅) * Cache MISS: Call LookupTopicBrokers, cache result for 30s - Extracted findPartitionInAssignments() helper function * Contains range calculation and partition matching logic * Reused for both cached and fresh lookups Cache Behavior: - First fetch: Cache MISS -> LookupTopicBrokers (~2ms) -> cache for 30s - Next 7500 fetches in 30s: Cache HIT -> immediate return (~0.01ms) - Cache automatically expires after 30s, re-validates on next fetch Performance Impact: With 250 fetches/sec and 5 topics: - Before: 250 LookupTopicBrokers/sec = 500ms CPU overhead - After: 0.17 LookupTopicBrokers/sec (5 topics / 30s TTL) - Reduction: 99.93% fewer gRPC calls Expected CPU Reduction: - Before: 12.65s total, 1.71s in getActualPartitionAssignment (13.5%) - After: ~11s total (-13.5% = 1.65s saved) - Benefit: 13% lower CPU, more capacity for actual message processing Cache Consistency: - Same 30-second TTL as broker's topic config cache - Partition assignments rarely change (only on topic reconfiguration) - 30-second staleness is acceptable for partition mapping - Gateway will eventually converge with broker's view Testing: - ✅ Compiles successfully - Ready to deploy and measure CPU improvement Priority: CRITICAL - Eliminates major performance bottleneck in gateway fetch path
SeaweedMQ Message Queue on SeaweedFS (WIP, not ready)
What are the use cases it is designed for?
Message queues are like water pipes. Messages flow in the pipes to their destinations.
However, what if a flood comes? Of course, you can increase the number of partitions, add more brokers, restart, and watch the traffic level closely.
Sometimes the flood is expected. For example, backfill some old data in batch, and switch to online messages. You may want to ensure enough brokers to handle the data and reduce them later to cut cost.
SeaweedMQ is designed for use cases that need to:
- Receive and save large number of messages.
- Handle spike traffic automatically.
What is special about SeaweedMQ?
- Separate computation and storage nodes to scale independently.
- Unlimited storage space by adding volume servers.
- Unlimited message brokers to handle incoming messages.
- Offline messages can be operated as normal files.
- Scale up and down with auto split and merge message topics.
- Topics can automatically split into segments when traffic increases, and vice verse.
- Pass messages by reference instead of copying.
- Clients can optionally upload the messages first and just submit the references.
- Drastically reduce the broker load.
- Stateless brokers
- All brokers are equal. One broker is dynamically picked as the leader.
- Add brokers at any time.
- Allow rolling restart brokers or remove brokers at a pace.
Design
How it works?
Brokers are just computation nodes without storage. When a broker starts, it reports itself to masters. Among all the brokers, one of them will be selected as the leader by the masters.
A topic needs to define its partition key on its messages.
Messages for a topic are divided into segments. One segment can cover a range of partitions. A segment can be split into 2 segments, or 2 neighboring segments can be merged back to one segment.
During write time, the client will ask the broker leader for a few brokers to process the segment.
The broker leader will check whether the segment already has assigned the brokers. If not, select a few brokers based on their loads, save the selection into filer, and tell the client.
The client will write the messages for this segment to the selected brokers.
Failover
The broker leader does not contain any state. If it fails, the masters will select a different broker.
For a segment, if any one of the selected brokers is down, the remaining brokers should try to write received messages to the filer, and close the segment to the clients.
Then the clients should start a new segment. The masters should assign other healthy brokers to handle the new segment.
So any brokers can go down without losing data.
Auto Split or Merge
(The idea is learned from Pravega.)
The brokers should report its traffic load to the broker leader periodically.
If any segment has too much load, the broker leader will ask the brokers to tell the client to close current one and create two new segments.
If 2 neighboring segments have the combined load below average load per segment, the broker leader will ask the brokers to tell the client to close this 2 segments and create a new segment.