Skip to content

Commit 13df9c7

Browse files
committed
skip redundant user read in expiration loop
1 parent b117918 commit 13df9c7

3 files changed

Lines changed: 79 additions & 35 deletions

File tree

ee/packages/presence/src/Presence.ts

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ import { type ClaimUpdate, processPresence } from './lib/presenceEngine';
1313
const MAX_CONNECTIONS = 200;
1414
const MAX_TIMEOUT_DELAY_MS = 2 ** 31 - 1;
1515

16+
type PresenceUser = Pick<
17+
IUser,
18+
| '_id'
19+
| 'username'
20+
| 'roles'
21+
| 'status'
22+
| 'statusDefault'
23+
| 'statusSource'
24+
| 'statusText'
25+
| 'statusExpiresAt'
26+
| 'statusConnection'
27+
| 'previousState'
28+
>;
29+
1630
export class Presence extends ServiceClass implements IPresence {
1731
protected name = 'presence';
1832

@@ -111,8 +125,8 @@ export class Presence extends ServiceClass implements IPresence {
111125
// TODO: in MS mode every instance runs this independently.
112126
// Add a job-level lock to avoid redundant cross-instance reads.
113127
const expiredCursor = Users.findExpiredStatuses();
114-
for await (const { _id } of expiredCursor) {
115-
await this.updateUserPresence(_id, { type: 'endActive' });
128+
for await (const user of expiredCursor) {
129+
await this.updateUserPresence(user, { type: 'endActive' });
116130
}
117131
}
118132

@@ -338,39 +352,28 @@ export class Presence extends ServiceClass implements IPresence {
338352
* Low-level presence update. Does not reschedule the expiration job.
339353
* Prefer {@link updatePresenceAndReschedule} for public-facing methods.
340354
*/
341-
private async updateUserPresence(uid: string, claimUpdate?: ClaimUpdate): Promise<boolean> {
342-
const user = await Users.findOneById<
343-
Pick<
344-
IUser,
345-
| '_id'
346-
| 'username'
347-
| 'roles'
348-
| 'status'
349-
| 'statusDefault'
350-
| 'statusSource'
351-
| 'statusText'
352-
| 'statusExpiresAt'
353-
| 'statusConnection'
354-
| 'previousState'
355-
>
356-
>(uid, {
357-
projection: {
358-
username: 1,
359-
roles: 1,
360-
status: 1,
361-
statusDefault: 1,
362-
statusSource: 1,
363-
statusText: 1,
364-
statusExpiresAt: 1,
365-
statusConnection: 1,
366-
previousState: 1,
367-
},
368-
});
355+
private async updateUserPresence(uidOrUser: string | PresenceUser, claimUpdate?: ClaimUpdate): Promise<boolean> {
356+
const user =
357+
typeof uidOrUser === 'string'
358+
? await Users.findOneById<PresenceUser>(uidOrUser, {
359+
projection: {
360+
username: 1,
361+
roles: 1,
362+
status: 1,
363+
statusDefault: 1,
364+
statusSource: 1,
365+
statusText: 1,
366+
statusExpiresAt: 1,
367+
statusConnection: 1,
368+
previousState: 1,
369+
},
370+
})
371+
: uidOrUser;
369372
if (!user) {
370373
return false;
371374
}
372375

373-
const userSessions = await UsersSessions.findOneById(uid);
376+
const userSessions = await UsersSessions.findOneById(user._id);
374377
const sessions = userSessions?.connections ?? [];
375378

376379
const result = processPresence(user, sessions, claimUpdate);
@@ -388,7 +391,7 @@ export class Presence extends ServiceClass implements IPresence {
388391
}
389392
: undefined;
390393

391-
const updatedUser = await Users.updatePresenceAndStatus(uid, result.values, result.clear, guard);
394+
const updatedUser = await Users.updatePresenceAndStatus(user._id, result.values, result.clear, guard);
392395
if (updatedUser) {
393396
this.broadcast(updatedUser, user.status);
394397
}

packages/model-typings/src/models/IUsersModel.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,21 @@ export interface IUsersModel extends IBaseModel<IUser> {
172172

173173
updateStatusText(_id: IUser['_id'], statusText: string, options?: UpdateOptions): Promise<UpdateResult>;
174174

175-
findExpiredStatuses(): FindCursor<Pick<IUser, '_id'>>;
175+
findExpiredStatuses(): FindCursor<
176+
Pick<
177+
IUser,
178+
| '_id'
179+
| 'username'
180+
| 'roles'
181+
| 'status'
182+
| 'statusDefault'
183+
| 'statusSource'
184+
| 'statusText'
185+
| 'statusExpiresAt'
186+
| 'statusConnection'
187+
| 'previousState'
188+
>
189+
>;
176190

177191
findNextStatusExpiration(): Promise<Pick<IUser, '_id' | 'statusExpiresAt'> | null>;
178192

packages/models/src/models/Users.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,9 +1098,36 @@ export class UsersRaw extends BaseRaw<IUser, DefaultFields<IUser>> implements IU
10981098
}
10991099

11001100
findExpiredStatuses() {
1101-
return this.find<Pick<IUser, '_id'>>(
1101+
return this.find<
1102+
Pick<
1103+
IUser,
1104+
| '_id'
1105+
| 'username'
1106+
| 'roles'
1107+
| 'status'
1108+
| 'statusDefault'
1109+
| 'statusSource'
1110+
| 'statusText'
1111+
| 'statusExpiresAt'
1112+
| 'statusConnection'
1113+
| 'previousState'
1114+
>
1115+
>(
11021116
{ statusExpiresAt: { $lt: new Date() } },
1103-
{ projection: { _id: 1 }, sort: { statusExpiresAt: 1 } },
1117+
{
1118+
projection: {
1119+
username: 1,
1120+
roles: 1,
1121+
status: 1,
1122+
statusDefault: 1,
1123+
statusSource: 1,
1124+
statusText: 1,
1125+
statusExpiresAt: 1,
1126+
statusConnection: 1,
1127+
previousState: 1,
1128+
},
1129+
sort: { statusExpiresAt: 1 },
1130+
},
11041131
);
11051132
}
11061133

0 commit comments

Comments
 (0)