Stuck all the incoming messages when triggering PutManyLIDMappings
Performance Issue: Long-running PutManyLIDMappings Blocks StoreLIDPNMapping (from groups) due to Lock Contention
Description
I have encountered a significant performance bottleneck in scenarios involving high-volume group messages, which causes message processing delays. The issue stems from the way LID (Local ID) mapping operations handle concurrent access using s.lidCacheLock.
When new group member data is processed, the PutManyLIDMappings function is called with a large number of mappings (often 6,000+ entries). This bulk operation takes a long time to complete because it involves numerous database write operations.
The core problem is that PutManyLIDMappings acquires and holds the s.lidCacheLock (a simple sync.Mutex inside the Store) for the entire duration of the bulk insertion. During this time, any incoming message that triggers handleEncryptedMessage eventually calls the single-entry mapping function, StoreLIDPNMapping.
Since StoreLIDPNMapping attempts to acquire the same lock, it is completely blocked. This means real-time messages cannot be processed until the bulk database insertion of 6,000+ records is finished, leading to significant message delivery and processing delays.
Affected Functions
PutManyLIDMappings(The blocking function)StoreLIDPNMapping(The blocked function, critical for real-time message processing)
Observed Behavior (Detailed Steps)
- Bulk Operation Starts: A group update event triggers
PutManyLIDMappingswithN > 6000data points. - Lock Acquired:
PutManyLIDMappingscallss.lidCacheLock.Lock(). - Long Wait: The function proceeds to insert
Nentries one by one into the store (SQLite/etc.), which is a time-consuming I/O operation. - Real-Time Message Arrives: A new message arrives, triggering
handleEncryptedMessage. - Critical Function Blocked:
handleEncryptedMessagecallsStoreLIDPNMapping, which then attempts to calls.lidCacheLock.Lock(). - Deadlock/Bottleneck: The single, quick
StoreLIDPNMappingoperation waits for the long-runningPutManyLIDMappingsto finish and release the lock, causing a severe delay in processing the incoming message.
Proposed Solutions / Suggestions
To mitigate this contention and improve real-time performance, I suggest exploring the following options:
- Granular Locking (Release During I/O): The
s.lidCacheLockshould be released before the long database write/bulk insert operation and reacquired after it. The lock should only be held for local memory cache updates, not for the duration of the slow database I/O. - Asynchronous Bulk Processing: Move the bulk database insertion logic of
PutManyLIDMappingsinto a separate goroutine or process it asynchronously, allowing immediate return and release of the main thread's lock. - Use Batching/Transaction for Bulk Insert: Ensure the database operations in
PutManyLIDMappingsuse a single transaction for better performance, although this may not solve the locking issue itself, it will reduce the time the lock is held.
Source: tulir/whatsmeow