perf: optimize history sync memory and CPU usage#2333
perf: optimize history sync memory and CPU usage#2333
Conversation
…ryptedContent functions
|
Thanks for opening this pull request and contributing to the project! The next step is for the maintainers to review your changes. If everything looks good, it will be approved and merged into the main branch. In the meantime, anyone in the community is encouraged to test this pull request and provide feedback. ✅ How to confirm it worksIf you’ve tested this PR, please comment below with: This helps us speed up the review and merge process. 📦 To test this PR locally:If you encounter any issues or have feedback, feel free to comment as well. |
purpshell
left a comment
There was a problem hiding this comment.
little risky to be messing with the proto gen stuff.
What if instead of relying on proto related constructs we have our own functions? Is that less technically feasible?
This will cause more breaking changes, won't it? Perhaps there are some libraries and users who expect Long values to be treated as they are currently. |
|
There is already existing tests for the protobuf Long, also I've added more to ensure work in another scenarios |
reduce memory allocations and CPU time during history sync, especially for accounts with large chat histories that cause memory spikes after scanning.
profiling a production instance (6h window, node 20) showed memory peaks up to ~8GB during history sync. the main bottlenecks were protobuf Long-to-string conversion (7.6% CPU), unnecessary buffer copies during media decryption, and redundant object allocations in history processing.
changes
1. replace Long library with native BigInt for protobuf conversion
longToString/longToNumberin WAProto previously used thelonglibrary's pure JS division loops for base-10 conversion. replaced with direct BigInt bitwise conversion ((BigInt(high >>> 0) << 32n) | BigInt(low >>> 0)), which delegates to V8's native C++toString().2. stream-pipe zlib in
downloadHistoryinstead of collecting all encrypted+decrypted chunks into an array, concatenating them, then inflating — pipe the decrypted stream directly through
createInflate(). avoids allocating the full compressed buffer as an intermediate step.3. remove unnecessary
{ ...chat }spread inprocessHistoryMessageeach chat from the protobuf decode was being shallow-copied via spread before pushing to the result array. the original object is already mutated in-place and not referenced elsewhere after the function returns.
4. skip
Buffer.concatin media decryption when no remainderin
downloadEncryptedContent, the transform was always callingBuffer.concat([remainingBytes, chunk])even whenremainingByteswas empty (which is most of the time since chunks arrive AES-aligned). now skips the concat and uses the chunk directly.benchmarks
longToString(Long object -> string)Buffer.concatin media decrypt (per chunk)downloadHistoryinflate (50MB payload)how this impacts history sync
during a full history sync (e.g. 200 chats, 10k messages), every message goes through protobuf decode ->
toJSON()which callslongToStringon every Long field (timestamps, file lengths, etc). the BigInt optimization directly reduces CPU time for this hot path.the streaming inflate and concat skip reduce memory pressure during the download+decompress+decrypt pipeline, which is where the large memory spikes originate.