Skip to content

Conversation

@cardoso
Copy link
Member

@cardoso cardoso commented Nov 5, 2025

Root Cause

The useUnread hook.

Explanation

  1. Events are fired for each subscription, even if the changes weren’t relevant for their unread state.
  2. Additionally, an unused useEffect dependency causes it to run multiple times.

Proposed changes (including videos or screenshots)

This PR addresses the performance issues of the useUnread hook.

Explanation

  • Replaced reduce with a regular loop to cut closure overhead.
  • Events are now fired only for subscriptions that changed unread-related fields.
  • The useEffect dependency (unread) which caused multiple runs was removed.

Issue(s)

Closes #36643
Closes #37331

Closes SUP-862

Steps to test or reproduce

Test Scenario

  • 600+ channels.
  • Spend 1 or 2 minutes sending messages in multiple channels randomly.

Profiling

Before: >400ms, increases with the number of channels After: ~16ms, constant with the number of channels
image image

To create a bunch of channels, I ran a playwright script similar to this (rate limit must be disabled):

test('create multiple public channels', async () => {
	const length = 160;
	const stepTimeout = 3000;
	test.setTimeout(length * stepTimeout);
	const steps = Array.from({ length }, (_, k) => {
		const channelName =  `${faker.word.adjective()}-${faker.word.noun()}-${faker.number.int({ min: 1000, max: 9999 })}`;
		return [k, channelName] as const;
	}).map(([k, channelName]) => {
		return [`create channel: ${k}`, async () => {
			await poHomeChannel.sidenav.createPublicChannel(channelName);
		}] as const;
	});

	for await (const [description, step] of steps.values()) {
		await test.step(description, async () => {
			await step();
		}, { timeout: stepTimeout });
	}
});

I had also changed the createPublicChannel helper to add another user.

Further comments

I'm omitting the test for now since we want to backport this, but I may add them in another PR:

https://gist.github.com/cardoso/7f4cfc461c9bbd7a519cadbd0632490f

A few of these will fail without these optimizations and all will pass with the optimizations.

Summary by CodeRabbit

  • Bug Fixes
    • Resolved client slowdown issue for users with a large number of channels.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Nov 5, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Nov 5, 2025

🦋 Changeset detected

Latest commit: 5e22651

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/meteor Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/ui-voip Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Walkthrough

A changelog entry documents a patch fix for "@rocket.chat/meteor" addressing client slowdown for users with many channels. The useUnread hook is refactored to use per-subscription state snapshots and selective emission, replacing reduce-based aggregation with explicit loops for performance optimization.

Changes

Cohort / File(s) Summary
Changelog Entry
\.changeset/quiet-cars-smile\.md
Patch release changelog documenting client slowdown fix for users with large number of channels.
Hook Optimization
apps/meteor/client/views/root/hooks/loggedIn/useUnread\.ts
Refactored unread state tracking with per-subscription snapshots (prevSubsRef, UnreadData type), replaced reduce-based aggregation with explicit loop, adopted selective emission strategy, and cleaned up effect dependencies to improve performance.

Sequence Diagram(s)

sequenceDiagram
    participant subscription as Subscriptions
    participant hook as useUnread Hook
    participant state as Per-Subscription State

    loop For each subscription
        subscription->>hook: subscription data change
        hook->>state: check unread/alert/unreadAlert fields
        alt Field changed?
            state->>hook: emit unread-changed-by-subscription
        else No change
            state->>hook: skip emission
        end
    end

    hook->>hook: explicit loop: accumulate unreadCount
    hook->>hook: compute global badgeIndicator
    hook->>hook: cap unreadCount to 999+
    hook->>hook: set unread state (count or badge)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Single file with focused optimization logic—no cross-file dependencies or API changes
  • New state tracking mechanism (prevSubsRef) and UnreadData type are straightforward
  • Refactored aggregation from reduce to explicit loop is a direct translation; verify correctness of badge indicator logic and dependency array cleanup
  • Minor changelog addition requires no review effort

Areas for attention:

  • Verify the selective emission strategy correctly identifies all field changes (unread, alert, unreadAlert)
  • Confirm badgeIndicator computation handles edge cases when multiple subscriptions have alert conditions
  • Review dependency array cleanup to ensure effect re-runs appropriately when subscriptions or preferences change

Poem

🐰 A hop, a skip—unread state takes flight,
Per-subscription snapshots gleaming bright.
No more reduce, just loops so clean,
Performance soars where slowness had been! ✨
Large channels? No problem—we're lightning-fast,
Channel lists handled at long, long last!

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: high cpu usage with large amount of channels' directly addresses the main performance issue described in the linked issue SUP-862, clearly summarizing the primary fix.
Linked Issues check ✅ Passed The code changes in useUnread.ts implement optimizations to reduce CPU usage and improve performance with large channel lists by switching from global delta emission to selective per-subscription tracking, addressing the core objective of SUP-862.
Out of Scope Changes check ✅ Passed All changes are focused on optimizing unread state tracking in useUnread.ts, which is directly related to fixing performance issues with large channel lists; the changelog entry documents the fix appropriately.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/SUP-862

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Nov 5, 2025

Codecov Report

❌ Patch coverage is 94.73684% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 68.09%. Comparing base (b4cf623) to head (5e22651).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #37395      +/-   ##
===========================================
- Coverage    68.10%   68.09%   -0.02%     
===========================================
  Files         3364     3364              
  Lines       115755   115760       +5     
  Branches     20910    20909       -1     
===========================================
- Hits         78838    78828      -10     
- Misses       34220    34234      +14     
- Partials      2697     2698       +1     
Flag Coverage Δ
e2e 57.45% <94.73%> (+0.01%) ⬆️
unit 72.12% <ø> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cardoso cardoso added this to the 7.13.0 milestone Nov 6, 2025
@cardoso cardoso marked this pull request as ready for review November 7, 2025 14:34
@cardoso cardoso requested a review from a team as a code owner November 7, 2025 14:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cd0f72f and 51ff479.

📒 Files selected for processing (2)
  • .changeset/quiet-cars-smile.md (1 hunks)
  • apps/meteor/client/views/root/hooks/loggedIn/useUnread.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: apps/meteor/ee/server/startup/federation.ts:39-74
Timestamp: 2025-11-05T20:53:57.761Z
Learning: In Rocket.Chat (apps/meteor/app/settings/server/CachedSettings.ts), the settings.watchMultiple() method immediately invokes its callback with current values if all requested settings exist in the store, then continues watching for subsequent changes. It does not wait for a setting to change before the first invocation.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (6)
.changeset/quiet-cars-smile.md (1)

1-5: LGTM!

The changelog entry clearly documents the patch fix for client slowdown with large channel counts.

apps/meteor/client/views/root/hooks/loggedIn/useUnread.ts (5)

3-3: LGTM!

The addition of useRef and the UnreadData type alias properly support the new per-subscription snapshot mechanism.

Also applies to: 11-12


23-27: Excellent documentation and optimization strategy.

The comment clearly explains the performance problem (O(N) scaling with subscription count) and the solution (selective event emission). Using a Map for O(1) lookups is the right choice.


29-32: LGTM!

Variables are properly typed and initialized. The nextSnapshot pattern enables atomic state updates.


53-61: LGTM!

The snapshot swap enables efficient per-subscription change detection, and the unread state prioritization logic (count → badge → clear) is correct. The 999+ cap is appropriate for UI display.


64-64: LGTM!

The dependency array is correct. All listed dependencies are necessary for the effect, and removing unread (which is the output, not an input) prevents unnecessary re-renders.

@tassoevan tassoevan added the stat: QA assured Means it has been tested and approved by a company insider label Nov 11, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Nov 11, 2025
@kodiakhq kodiakhq bot merged commit e7be28b into develop Nov 11, 2025
49 checks passed
@kodiakhq kodiakhq bot deleted the fix/SUP-862 branch November 11, 2025 13:18
@scuciatto
Copy link
Member

/patch

@scuciatto
Copy link
Member

/backport 7.11.2

@scuciatto
Copy link
Member

/backport 7.10.5

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Nov 14, 2025

Pull request #37520 added to Project: "Patch 7.12.2"

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Nov 14, 2025

Pull request #37521 added to Project: "Patch 7.11.2"

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Nov 14, 2025

Pull request #37522 added to Project: "Patch 7.10.5"

@RocketChat RocketChat deleted a comment from dionisio-bot bot Nov 14, 2025
@RocketChat RocketChat deleted a comment from dionisio-bot bot Nov 14, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 18, 2025
@coderabbitai coderabbitai bot mentioned this pull request Dec 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

High load in browser on 7.10.x with many channels/1:1 conversations High number of channels in the user end causes client to slow down excessively

4 participants