Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Subscriptions out of DB Watcher #32540

Merged
merged 56 commits into from
Aug 21, 2024

Conversation

ricardogarim
Copy link
Contributor

@ricardogarim ricardogarim commented Jun 3, 2024

As per the updates mentioned in PROJ-7, SCA-13 and ADR #74, this pull request focuses on relocating Subscriptions model out of DB Watcher service.

Context

This modification enhances RocketChat's app by allowing it to directly call listeners through the api.broadcast global function, bypassing the need for MongoDB Change Stream data propagation.

This change offers better control over user notifications via Web Sockets, enabling more precise management of use-cases. Instead of notifying every database change, we can now send an api.broadcast call only when necessary, reducing overall network messages. Additionally, this contributes to the future removal of the DB Watcher deployment, optimizing resource utilization.

Proposed changes

Key changes include:

  • Conditionally incorporating Subscriptions entity import within DB watchers on application startup based on the dbWatchersDisabled flag.
  • Enabling support for the following use cases to directly trigger watch.subscriptions listener event, subject to the dbWatchersDisabled flag.
Updated Use Cases
Use Case Route/Trigger Notes
removeRolesByUserId applyRoomRolesToUser; Roles raw models removeUserRoles
addRolesByUserId applyRoomRolesToUser; Roles raw models addUserRoles
setAsReadByRoomIdAndUserId post route 'subscriptions.read'; afterSaveMessage callback
updateAllRoomTypesByRoomId updateRoomType
removeByRoomId deleteRoom server functions (add api route too); LivechatClass.closeRoom; RocketChatRoomAdapter.removeDirectMessageRoom
incUnreadForRoomIdExcludingUserIds updateUsersSubscriptions
setAlertForRoomIdExcludingUserId updateUsersSubscriptions
setOpenForRoomIdExcludingUserId updateUsersSubscriptions
updateNameAndFnameByRoomId RocketChatRoomAdapter updateRoomName and updateDisplayRoomName
setGroupE2EKey method meteor 'e2e.updateGroupKey'; handleSuggestedGroupKey
setGroupE2ESuggestedKey method meteor 'e2e.updateGroupKey'
unsetGroupE2ESuggestedKey handleSuggestedGroupKey
setOnHoldByRoomId OmnichannelEE > placeRoomOnHold
unsetOnHoldByRoomId onCloseLivechat; omnichannelEE.resumeRoomOnHold
updateUnreadAlertById meteor method saveNotificationSettings
updateNotificationsPrefById meteor method saveNotificationSettings
updateHideMentionStatusById meteor method saveNotificationSettings
updateDisableNotificationsById meteor method saveNotificationSettings
clearAudioNotificationValueById meteor method saveNotificationSettings saveAudioNotificationValue
updateAudioNotificationValueById meteor method saveNotificationSettings saveAudioNotificationValue
updateMuteGroupMentions meteor method saveNotificationSettings
updateHideUnreadStatusById meteor method saveNotificationSettings
updateAutoTranslateLanguageById meteor method 'autoTranslate.saveSettings'
updateAutoTranslateById meteor method 'autoTranslate.saveSettings'
removeByVisitorToken LivechatClass cleanGuestHistory
changeDepartmentByRoomId livechat helper updateChatDepartment
updateAllAutoTranslateLanguagesByUserId saveUserPreferences
disableAutoTranslateByRoomId saveRoomEncrypted
resetUserE2EKey resetUserE2EEncriptionKey
setAsUnreadByRoomIdAndUserId meteor method unreadMessages
archiveByRoomId ImportDataConverter.archiveRoomById; archiveRoom function
unarchiveByRoomId unarchiveRoom function
updateNameAndAlertByRoomId updateRoomName
setCustomFieldsDirectMessagesByUserId
setFavoriteByRoomIdAndUserId meteor method toggleFavorite
hideByRoomIdAndUserId hideRoomMethod
updateNameAndFnameById updateGroupDMsName
setUserUsernameByUserId updateUsernameReferences
updateFnameByRoomId updateFName
updateDisplayNameByRoomId registerContact; LivechatClass.saveRoomInfo
updateDirectNameAndFnameByName updateUsernameReferences
incGroupMentionsAndUnreadForRoomIdExcludingUserId incGroupMentions
unsetBlockedByRoomId unblockUser
setLastReplyForRoomIdAndUserIds updateThreadUsersSubscriptions
updateCustomFieldsByRoomId saveRoomCustomFields
setOpenForRoomIdAndUserIds reply; updateThreadUsersSubscriptions
setAlertForRoomIdAndUserIds reply; updateThreadUsersSubscriptions
updateTypeByRoomId saveRoomType
setBlockedByRoomId meteor method blockUser
incUserMentionsAndUnreadForRoomIdAndUserIds updateUsersSubscriptions
ignoreUser meteor method ignoreUser
addRoleById meteor methods addRoomLeader, addRoomModerator, and addRoomOwner
removeRoleById removeRoomLeader, removeRoomModerator, and removeRoomOwner
setArchivedByUsername deactivate > setArchivedByUsername; CROWD.syncDataToUser > setArchivedByUsername; meteor method setUserActiveStatus; ldapeemanager > disableMissingUsers and updateExistingUsers; ImportDataConverter > updateUser
updateUserHighlights saveUserPreferences
updateNotificationUserPreferences saveUserPreferences
clearNotificationUserPreferences saveUserPreferences
removeByUserId deleteUser
createWithRoomAndUser
createWithRoomAndManyUsers
removeByRoomIdAndUserId
removeByRoomIds
addUnreadThreadByRoomIdAndUserIds
removeUnreadThreadByRoomIdAndUserId
openByRoomIdAndUserId meteor method openRoom
removeUnreadThreadsByRoomId

Steps to test or reproduce

  1. Start RocketChat's application with the DISABLE_DB_WATCHERS environment variable set to true.
  2. Perform HTTP requests or simulate use cases listed in the above table.

Further comments

To maintain consistency and avoid potential regressions, event names and signatures have been kept unchanged on both the client and app sides. This decision streamlines efforts and mitigates the risk of unintended consequences.

Copy link
Contributor

dionisio-bot bot commented Jun 3, 2024

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

Copy link

changeset-bot bot commented Jun 3, 2024

⚠️ No Changeset found

Latest commit: 3af6943

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

@ricardogarim ricardogarim marked this pull request as ready for review June 3, 2024 11:42
@ricardogarim ricardogarim requested review from a team as code owners June 3, 2024 11:42
Copy link

codecov bot commented Jun 3, 2024

Codecov Report

Attention: Patch coverage is 23.07692% with 60 lines in your changes missing coverage. Please review.

Project coverage is 59.40%. Comparing base (dd37ea1) to head (3af6943).
Report is 3 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #32540      +/-   ##
===========================================
- Coverage    59.43%   59.40%   -0.04%     
===========================================
  Files         2547     2547              
  Lines        63265    63310      +45     
  Branches     14237    14248      +11     
===========================================
+ Hits         37604    37611       +7     
- Misses       22941    22980      +39     
+ Partials      2720     2719       -1     
Flag Coverage Δ
unit 75.84% <23.07%> (-0.20%) ⬇️

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

Copy link
Member

@debdutdeb debdutdeb left a comment

Choose a reason for hiding this comment

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

not adding same comment for all, but my first look :)

ricardogarim and others added 6 commits June 4, 2024 21:59
- Replaced asynchronous function calls with synchronous calls followed by an if statement to check for modifiedCount before invoking the listener.
Copy link
Member

@sampaiodiego sampaiodiego left a comment

Choose a reason for hiding this comment

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

there are some inline comments... I've found these broken behaviors while testing:

  • when sending a message in a channel, other members' subscription were not being marked as unread.. weirdly enough, if a message has a mention to someone, it works as expected
  • change a channel to a group (from public to private) was not reflected in the UI

other than that, I'd ask to not use .then() when it is not needed, I've found it in a few different places.

apps/meteor/app/federation/server/endpoints/dispatch.js Outdated Show resolved Hide resolved
apps/meteor/app/lib/server/functions/deleteUser.ts Outdated Show resolved Hide resolved
apps/meteor/app/livechat/server/lib/Helper.ts Outdated Show resolved Hide resolved
apps/meteor/app/lib/server/lib/notifyListener.ts Outdated Show resolved Hide resolved
apps/meteor/app/lib/server/lib/notifyListener.ts Outdated Show resolved Hide resolved
@ricardogarim ricardogarim force-pushed the refactor/subscriptions-model-out-of-db-watcher branch from 71a3a1a to 0a012a8 Compare July 4, 2024 02:55
sampaiodiego
sampaiodiego previously approved these changes Aug 21, 2024
@sampaiodiego sampaiodiego added this to the 6.12 milestone Aug 21, 2024
@sampaiodiego sampaiodiego added the stat: QA assured Means it has been tested and approved by a company insider label Aug 21, 2024
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Aug 21, 2024
Copy link
Contributor

@KevLehman KevLehman left a comment

Choose a reason for hiding this comment

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

In some places you destructure the returned objects and in others you access them via ., think would be nice to be consistent and stick to one way where this makes sense.

@@ -27,7 +29,10 @@ export const saveRoomEncrypted = async function (rid: string, encrypted: boolean
}

if (encrypted) {
await Subscriptions.disableAutoTranslateByRoomId(rid);
const disableAutoTranslateResponse = await Subscriptions.disableAutoTranslateByRoomId(rid);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const disableAutoTranslateResponse = await Subscriptions.disableAutoTranslateByRoomId(rid);
const { modifiedCount } = await Subscriptions.disableAutoTranslateByRoomId(rid);


// Update customFields of any user's Subscription related with this rid
await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields);
const updateCustomFields = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const updateCustomFields = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields);
const { modifiedCount } = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields);


if (removedUnreadThreadsResponse.modifiedCount) {
for await (const id of subscriptionIds) {
await notifyOnSubscriptionChangedById(id);
Copy link
Contributor

Choose a reason for hiding this comment

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

Most of other calls are void promises, is there a reason to await for them here?


if (Object.keys(insertedIds).length) {
for await (const insertedId of Object.values(insertedIds)) {
await notifyOnSubscriptionChangedById(insertedId, 'inserted');
Copy link
Contributor

Choose a reason for hiding this comment

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

Same, any reason for await this one?


void notifyOnRoomChangedById(rid, 'removed');
(await Rooms.removeById(rid)).deletedCount && void notifyOnRoomChangedById(rid, 'removed');
Copy link
Contributor

Choose a reason for hiding this comment

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

Other places are using the if (deletedCount), think we should keep the same standard here.

Messages.removeByRoomIds(rids),
ReadReceipts.removeByRoomIds(rids),
Rooms.removeByIds(rids),
// no bulk deletion for files
...rids.map((rid) => FileUpload.removeFilesByRoomId(rid)),
Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO, this change is not related to the watcher changes and should be in its own PR

): Promise<void> => {
const subscriptions = Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields });

for await (const subscription of subscriptions) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for await (const subscription of subscriptions) {
for (const subscription of subscriptions) {

Copy link
Member

Choose a reason for hiding this comment

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

this is because of the AsyncIterator.. but in fact, we don't need to use it

projection: subscriptionFields,
});

for await (const subscription of subscriptions) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for await (const subscription of subscriptions) {
for (const subscription of subscriptions) {

sampaiodiego
sampaiodiego previously approved these changes Aug 21, 2024
KevLehman
KevLehman previously approved these changes Aug 21, 2024
const subscriptionsLeft = await Subscriptions.countByRoomId(roomId);
if (subscriptionsLeft) {
await Subscriptions.removeByRoomId(roomId);
const { deletedCount } = await Subscriptions.removeByRoomId(roomId, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks man!

@sampaiodiego sampaiodiego dismissed stale reviews from KevLehman and themself via 3af6943 August 21, 2024 22:22
@ggazzo ggazzo merged commit 150c7b1 into develop Aug 21, 2024
46 of 49 checks passed
@ggazzo ggazzo deleted the refactor/subscriptions-model-out-of-db-watcher branch August 21, 2024 23:59
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.

5 participants