Skip to content

chore: Implement user caching for publication access#39667

Open
ggazzo wants to merge 5 commits intodevelopfrom
chore/ddp-finds
Open

chore: Implement user caching for publication access#39667
ggazzo wants to merge 5 commits intodevelopfrom
chore/ddp-finds

Conversation

@ggazzo
Copy link
Copy Markdown
Member

@ggazzo ggazzo commented Mar 16, 2026

This pull request introduces a user caching mechanism for publication contexts in the notifications module, aiming to optimize repeated user lookups and improve performance. The main change is the addition of a cache layer for user data (specifically user ID and roles) that is used in permission and authorization checks throughout the notifications streaming system. The cache is automatically invalidated when user data changes, ensuring consistency.

Key changes include:

User Caching Implementation:

  • Added a new utility function getCachedUserForPublication in publication-user-cache.ts to cache user objects (with _id and roles) per publication context for a short period, reducing repeated database queries.
  • Provided an invalidate function to clear the cache for a user when their data changes.

Integration with Notifications Module:

  • Replaced direct usage of this.userId with calls to getCachedUserForPublication throughout notifications.module.ts, ensuring all permission and room access checks use the cached user object. This affects methods like allowRead, allowEmit, and various authorization checks. [1] [2] [3] [4] [5] [6] [7] [8]

Cache Invalidation on User Updates:

  • Hooked into user update events in service.ts to call invalidatePublicationUserCache, ensuring the cache is cleared when a user's data changes. [1] [2]

These changes collectively improve the efficiency of user permission checks in the server's notification streaming logic while maintaining up-to-date authorization data.

Task: ARCH-2056

Summary by CodeRabbit

  • Performance

    • Improved notification delivery by caching user information, reducing repeated lookups and speeding updates.
  • Reliability

    • Strengthened permission and access checks across notification streams for more consistent behavior.
    • Added room context (type and name) to some notification payloads for clearer information.

@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot Bot commented Mar 16, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 16, 2026

⚠️ No Changeset found

Latest commit: 3b1d986

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f565403-1444-4efe-82eb-d88405b3f801

📥 Commits

Reviewing files that changed from the base of the PR and between 6e90307 and d170cf6.

📒 Files selected for processing (1)
  • apps/meteor/server/modules/streamer/publication-user-cache.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📜 Recent review details
⏰ 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

Walkthrough

Adds a per-publication cached user lookup (getCachedUserForPublication) and integrates it into notifications stream handlers to replace direct this.userId checks; also evicts cached publication users on user updates via the Meteor service watch.users handler (invalidatePublicationUserCache).

Changes

Cohort / File(s) Summary
User Cache Infrastructure
apps/meteor/server/modules/streamer/publication-user-cache.ts
New module exporting getCachedUserForPublication(publication) and invalidatePublicationUserCache(userId) — an in-memory per-publication cache returning a { _id, roles } projection or null, with eviction timers.
Notifications Module Authorization Refactor
apps/meteor/server/modules/notifications/notifications.module.ts
Multiple publication/stream handlers now call getCachedUserForPublication(this) up-front, short-circuit when no user, and pass the retrieved user (or its id) into Authorization checks (canReadRoom, canAccessRoom, hasAtLeastOnePermission); some handlers return extra contextual fields (e.g., roomType, roomName).
Cache Invalidation Hook
apps/meteor/server/services/meteor/service.ts
Imports and calls invalidatePublicationUserCache(data.id) at the start of the watch.users handler to evict/refresh cached publication users when user records change.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Publication as Publication/Stream
  participant Cache as PublicationUserCache
  participant Auth as Authorization
  participant DB as UsersDB
  participant Service as MeteorService

  Client->>Publication: subscribe / trigger stream handler
  Publication->>Cache: getCachedUserForPublication(publication)
  alt cache hit
    Cache-->>Publication: user { _id, roles }
  else cache miss
    Cache->>DB: Users.findOneById(userId, projection)
    DB-->>Cache: user { _id, roles } or null
    Cache-->>Publication: user or null
  end
  Publication->>Auth: Authorization checks (canReadRoom / canAccessRoom / perms) with user
  Auth-->>Publication: allow / deny
  Publication-->>Client: publish data or stop

  Note over Service,Cache: On user update
  Service->>Cache: invalidatePublicationUserCache(userId)
  Cache-->>Cache: remove cached entry / clear timer
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

type: chore

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: Implement user caching for publication access' accurately describes the main change: introducing a user cache mechanism for publication contexts to improve authorization performance and reduce database queries.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • ARCH-2056: Request failed with status code 401

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 14.63415% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.84%. Comparing base (981af32) to head (3b1d986).
⚠️ Report is 20 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #39667      +/-   ##
===========================================
- Coverage    69.86%   69.84%   -0.02%     
===========================================
  Files         3298     3299       +1     
  Lines       119347   119372      +25     
  Branches     21530    21521       -9     
===========================================
- Hits         83377    83375       -2     
- Misses       32672    32689      +17     
- Partials      3298     3308      +10     
Flag Coverage Δ
e2e 59.76% <ø> (-0.02%) ⬇️
e2e-api 46.21% <14.63%> (-0.03%) ⬇️
unit 70.61% <ø> (-0.02%) ⬇️

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.

@ggazzo ggazzo marked this pull request as ready for review March 16, 2026 19:25
@ggazzo ggazzo requested a review from a team as a code owner March 16, 2026 19:25
@ggazzo ggazzo added this to the 8.3.0 milestone Mar 16, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/meteor/server/services/meteor/service.ts">

<violation number="1" location="apps/meteor/server/services/meteor/service.ts:82">
P2: This adds every updated user ID to a global Set that is never evicted unless that user reads a cached publication again.</violation>
</file>

<file name="apps/meteor/server/modules/streamer/publication-user-cache.ts">

<violation number="1" location="apps/meteor/server/modules/streamer/publication-user-cache.ts:26">
P1: Clearing the invalidation flag after refreshing one publication leaves other publications for the same user on stale authorization data.

(Based on your team's feedback about questioning concurrency-related behavioral changes.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/meteor/server/modules/streamer/publication-user-cache.ts Outdated
Comment thread apps/meteor/server/services/meteor/service.ts
@ggazzo
Copy link
Copy Markdown
Member Author

ggazzo commented Mar 16, 2026

/jira ARCH-2021

@ggazzo ggazzo modified the milestones: 8.3.0, 8.4.0 Apr 6, 2026
@ggazzo ggazzo modified the milestones: 8.4.0, 8.5.0 Apr 17, 2026
Copy link
Copy Markdown
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/meteor/server/modules/streamer/publication-user-cache.ts`:
- Around line 38-40: invalidate(userId) currently deletes the cache entry but
leaves any previously scheduled eviction timer active; update invalidate to
first clear the active timeout for that user (use clearTimeout on the timer
stored for that user in whatever timers map you maintain—e.g.,
timeoutByUser/evictionTimeouts), remove the timer entry from that map, then
delete the entry from cacheByPublication so a stale callback cannot evict a
newly refreshed cache item; ensure you reference the same timer storage used
when scheduling evictions and remove its entry after clearing.
- Around line 16-34: The cache currently races with in-flight Users.findOneById
calls and can reinsert stale data; to fix, introduce a per-user generation token
or store the pending promise in cacheByPublication so invalidation wins: when
reading, check cacheByPublication.get(userId) and if a pending promise exists
await it; when starting a new Users.findOneById, create and store a pending
entry (with a generation or promise) before awaiting; on invalidation
(watch.users) increment the user's generation or delete the cache entry so any
late resolution is discarded by comparing generation tokens or by replacing the
pending promise check, and only set the final user + timeout into
cacheByPublication if the stored token/promise still matches the one created for
this lookup (use existing symbols cacheByPublication, Users.findOneById,
CACHE_TIMEOUT, CACHE_PROJECTION).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cef27f0e-7ceb-4600-84fc-cf03dce30709

📥 Commits

Reviewing files that changed from the base of the PR and between a3db465 and af59e87.

📒 Files selected for processing (1)
  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📜 Review details
⏰ 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 context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
🧠 Learnings (14)
📓 Common learnings
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.
📚 Learning: 2026-03-09T18:39:21.178Z
Learnt from: Harxhit
Repo: RocketChat/Rocket.Chat PR: 39476
File: apps/meteor/server/methods/addAllUserToRoom.ts:0-0
Timestamp: 2026-03-09T18:39:21.178Z
Learning: In apps/meteor/server/methods/addAllUserToRoom.ts, the implementation uses a single cursor pass (Users.find(userFilter).batchSize(100)) that collects both the full user objects (collectedUsers: IUser[]) and their usernames (usernames: string[]) in one iteration. `beforeAddUserToRoom` is then called once with the full usernames batch (preserving batch-validation semantics), and the subsequent subscription/message processing loop iterates over the same stable `collectedUsers` array — no second DB query is made. This avoids any race condition between validation and processing while preserving the original batch-validation behavior.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-04-30T14:57:42.623Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40185
File: packages/apps/deno-runtime/lib/room.ts:45-45
Timestamp: 2026-04-30T14:57:42.623Z
Learning: In `packages/apps/deno-runtime/lib/room.ts`, the `getUsernames()` method intentionally uses `await` before assigning the result to `this._USERNAMES` (typed as `Promise<Array<string>> | undefined`), storing the resolved `Array<string>` rather than the Promise. This is a deliberate backward-compatibility workaround with the existing API and should not be flagged as a type mismatch or cache-contract violation in future reviews.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-04-18T12:32:53.425Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 38623
File: apps/meteor/app/lib/server/functions/cleanRoomHistory.ts:146-149
Timestamp: 2026-04-18T12:32:53.425Z
Learning: In `apps/meteor/app/lib/server/functions/cleanRoomHistory.ts` (PR `#38623`), the read-receipt cleanup (both `ReadReceipts.removeByMessageIds` and `ReadReceiptsArchive.removeByMessageIds`) is intentionally only performed in the limited prune path (`limit && selectedMessageIds`). The unlimited/delete-all path (`limit === 0`) deliberately skips cleaning up orphaned read receipts in both hot and cold storage — this is by design. Do not flag this as a bug or missing cleanup in future reviews.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-04-29T19:33:29.434Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 40268
File: apps/meteor/client/startup/startup.ts:48-56
Timestamp: 2026-04-29T19:33:29.434Z
Learning: In `apps/meteor/client/startup/startup.ts`, the heuristic `!userIdStore.getState() && localStorage.getItem('Meteor.loginToken') === null` calling `removeLocalUserData()` (which calls `localStorage.clear()`) is intentional. The maintainer (tassoevan) has confirmed this is acceptable even though it fires on fresh first-time visits with no prior login, not just on expired-token resume scenarios. Do not flag this as destructive or suggest narrowing it to specific E2EE keys in future reviews.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-04-29T20:06:34.862Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 40268
File: apps/meteor/client/startup/incomingMessages.ts:21-25
Timestamp: 2026-04-29T20:06:34.862Z
Learning: In `apps/meteor/client/startup/incomingMessages.ts`, the `Messages.state.update` predicate that strips `ignored` from records when `'ignored' in sub` is false (i.e., the subscription update has no `ignored` field) is intentional. Absence of `ignored` in a `subscriptions-changed` event means the user's ignore list is empty/reset, so clearing all existing `ignored` flags on messages for that room is the correct behavior. Do not flag this as an unintentional ignored-state reset on unrelated subscription updates.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-04-10T21:17:22.932Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40096
File: apps/meteor/ee/server/apps/lib/redactor.ts:3-17
Timestamp: 2026-04-10T21:17:22.932Z
Learning: In RocketChat/Rocket.Chat, `X-User-Id` / `x-user-id` headers must NOT be added to redaction paths in apps log redaction (e.g., `apps/meteor/ee/server/apps/lib/redactor.ts`). The maintainer (d-gubert) has confirmed that X-User-Id is an identifier, not a credential — its presence in logs is useful for diagnostics, and `X-Auth-Token` is the only header that constitutes a real secret. Do not suggest redacting X-User-Id in future reviews of this area.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-04-20T17:11:59.452Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40225
File: apps/meteor/ee/server/apps/communication/endpoints/appLogsHandler.ts:55-71
Timestamp: 2026-04-20T17:11:59.452Z
Learning: In `apps/meteor/ee/server/apps/communication/endpoints/appLogsHandler.ts`, the concern about an empty `?appId=` query param bypassing the truthy check and overriding the path `appId` in the `makeAppLogsQuery` spread is not relevant. The AJV query schema (`isAppLogsProps`) validates and rejects invalid/empty `appId` values before the action handler is reached, making the in-handler guard sufficient as-is. Do not flag this pattern as a vulnerability in future reviews of this file.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-03-16T22:56:54.500Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 39677
File: packages/models/src/helpers/omnichannel/agentStatus.ts:10-29
Timestamp: 2026-03-16T22:56:54.500Z
Learning: In `packages/models/src/helpers/omnichannel/agentStatus.ts` (PR `#39677`), the `queryStatusAgentOnline` function intentionally omits the `$or` offline-status guard for non-bot agents when `isLivechatEnabledWhenAgentIdle === true`. This is by design: the setting `Livechat_enabled_when_agent_idle` (`accept_chats_when_agent_idle`) means agents should receive chats even when idle/offline, so the offline filter must be removed in that path. Bots are always status-agnostic and are always included regardless of their online/offline status. Do not flag this as a bug.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2025-12-09T20:01:07.355Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37532
File: ee/packages/federation-matrix/src/FederationMatrix.ts:920-927
Timestamp: 2025-12-09T20:01:07.355Z
Learning: In Rocket.Chat's federation invite handling (ee/packages/federation-matrix/src/FederationMatrix.ts), when a user rejects an invite via federationSDK.rejectInvite(), the subscription cleanup happens automatically through an event-driven flow: Matrix emits a leave event back, which is processed by handleLeave() in ee/packages/federation-matrix/src/events/member.ts, and that function calls Room.performUserRemoval() to clean up the subscription. No explicit cleanup is needed in the reject branch of handleInvite() because the leave event handler takes care of it.
<!-- </add_learning>

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/server/modules/streamer/publication-user-cache.ts

Comment thread apps/meteor/server/modules/streamer/publication-user-cache.ts Outdated
Comment thread apps/meteor/server/modules/streamer/publication-user-cache.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant