perf(groups): cache Arc<GroupInfo> to avoid deep-cloning group metadata on warm sends - #710
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR refactors group metadata caching to use ChangesArc-based Group Metadata Reference Counting
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Benchmark Results67 unchanged benchmark(s)
|
Problem
The group metadata cache is a
TypedCache<Jid, GroupInfo>(by value).query_inforuns on every group send and, on a warm hit, returnsOption<GroupInfo>by.clone()(both the moka and the portable backend clone the stored value). So each warm group send deep-clones the entireGroupInfo: theparticipants: Vec<Jid>plus the twoHashMap<CompactString, Jid>LID/PN maps. For a large group that is hundreds ofJids and up to ~2x as manyHashMapentries materialized and dropped per message sent.This mirrors an anti-pattern the codebase already avoids elsewhere:
SenderKeyDeviceCache(Arc<SenderKeyDeviceMap>) andlid_pn_cache(Arc<LidPnEntry>) both cache underArc.group_cachewas the exception caching by value.Change
Cache
Arc<GroupInfo>instead ofGroupInfo. A warmquery_infohit is now a refcount bump rather than a deep copy. This matches WA Web'sWAWebGroupMetadataCollection, which keeps group metadata as a shared model passed by reference rather than copying the participant array per send.query_info/resolve_group_infonow returnArc<GroupInfo>.prepare_group_stanzanow takes&GroupInfoinstead of&mut GroupInfo. It no longer mutates the (now shared) metadata to append our own JID; theforce_skdmdevice-resolution branch already re-ensures self in its resolve list independently, so the push was redundant for the function's own reads.Arconly when self is missing (rare, since the server's participant list already includes us), so the common warm path shares the metadata with no clone. Ordering relative toresolve_skdm_targetsis preserved.GroupInfoat the same relative position as before (after SKDM resolution, before stanza build), soensure_status_participantsbehavior is unchanged.Arc::unwrap_or_clonethen re-insert. These are infrequent and off the hot send path.The persisted group-metadata blob format is unchanged (the owned
GroupInfois serialized before being wrapped inArc).Benchmark
iai-callgrind, cloning a 256-participant LID
GroupInfo(the per-warm-send work that is eliminated) vs cloning theArc:GroupInfo(before)Arcclone (after)~2,550x fewer instructions for the metadata copy per warm send, scaling with group size. Warm group-send latency is server-bound, so the win is allocation/CPU churn, not wall-clock.
Tests
warm_group_cache_hit_shares_arc_not_deep_clone: twogets on the same key return the same allocation (Arc::ptr_eq).ensure_self_in_group_shares_when_present_and_appends_when_absent: when self is already a member the sharedArcpasses through untouched (no clone); when missing, a freshGroupInfois built with self appended.cargo clippy --all-targets -- -D warningsclean.Breaking
query_info,resolve_group_info, andprepare_group_stanzachange signatures (returnArc<GroupInfo>/ take&GroupInfo). Pre-1.0, in line with eliminating clones via signature changes.