add lastActiveDate to updateUserQueue

This commit is contained in:
Hazelnoot 2025-06-26 10:25:16 -04:00
parent 1baa9b2f9d
commit 528c0476f9
3 changed files with 33 additions and 42 deletions

View file

@ -12,9 +12,10 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { EnvService } from '@/core/EnvService.js'; import { EnvService } from '@/core/EnvService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { InternalEventService } from '@/core/InternalEventService.js'; import { InternalEventService } from '@/core/InternalEventService.js';
import type { UsersRepository, NotesRepository, AccessTokensRepository, MiAntenna, AntennasRepository } from '@/models/_.js'; import type { UsersRepository, NotesRepository, AccessTokensRepository, MiAntenna, FollowingsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { AntennaService } from '@/core/AntennaService.js'; import { AntennaService } from '@/core/AntennaService.js';
import { CacheService } from '@/core/CacheService.js';
export type UpdateInstanceJob = { export type UpdateInstanceJob = {
latestRequestReceivedAt?: Date, latestRequestReceivedAt?: Date,
@ -30,6 +31,7 @@ export type UpdateInstanceJob = {
export type UpdateUserJob = { export type UpdateUserJob = {
updatedAt?: Date, updatedAt?: Date,
lastActiveDate?: Date,
notesCountDelta?: number, notesCountDelta?: number,
followingCountDelta?: number, followingCountDelta?: number,
followersCountDelta?: number, followersCountDelta?: number,
@ -66,21 +68,22 @@ export class CollapsedQueueService implements OnApplicationShutdown {
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
public readonly usersRepository: UsersRepository, private readonly usersRepository: UsersRepository,
@Inject(DI.notesRepository) @Inject(DI.notesRepository)
public readonly notesRepository: NotesRepository, private readonly notesRepository: NotesRepository,
@Inject(DI.accessTokensRepository) @Inject(DI.accessTokensRepository)
public readonly accessTokensRepository: AccessTokensRepository, private readonly accessTokensRepository: AccessTokensRepository,
@Inject(DI.antennasRepository) @Inject(DI.followingsRepository)
public readonly antennasRepository: AntennasRepository, private readonly followingsRepository: FollowingsRepository,
private readonly federatedInstanceService: FederatedInstanceService, private readonly federatedInstanceService: FederatedInstanceService,
private readonly envService: EnvService, private readonly envService: EnvService,
private readonly internalEventService: InternalEventService, private readonly internalEventService: InternalEventService,
private readonly antennaService: AntennaService, private readonly antennaService: AntennaService,
private readonly cacheService: CacheService,
loggerService: LoggerService, loggerService: LoggerService,
) { ) {
@ -171,20 +174,34 @@ export class CollapsedQueueService implements OnApplicationShutdown {
oneMinuteInterval, oneMinuteInterval,
(oldJob, newJob) => ({ (oldJob, newJob) => ({
updatedAt: maxDate(oldJob.updatedAt, newJob.updatedAt), updatedAt: maxDate(oldJob.updatedAt, newJob.updatedAt),
lastActiveDate: maxDate(oldJob.lastActiveDate, newJob.lastActiveDate),
notesCountDelta: (oldJob.notesCountDelta ?? 0) + (newJob.notesCountDelta ?? 0), notesCountDelta: (oldJob.notesCountDelta ?? 0) + (newJob.notesCountDelta ?? 0),
followingCountDelta: (oldJob.followingCountDelta ?? 0) + (newJob.followingCountDelta ?? 0), followingCountDelta: (oldJob.followingCountDelta ?? 0) + (newJob.followingCountDelta ?? 0),
followersCountDelta: (oldJob.followersCountDelta ?? 0) + (newJob.followersCountDelta ?? 0), followersCountDelta: (oldJob.followersCountDelta ?? 0) + (newJob.followersCountDelta ?? 0),
}), }),
async (id, job) => { async (id, job) => {
// Have to check this because all properties are optional // Have to check this because all properties are optional
if (job.updatedAt || job.notesCountDelta || job.followingCountDelta || job.followersCountDelta) { if (job.updatedAt || job.lastActiveDate || job.notesCountDelta || job.followingCountDelta || job.followersCountDelta) {
// Updating the user should implicitly mark them as active
const lastActiveDate = job.lastActiveDate ?? job.updatedAt;
const isWakingUp = lastActiveDate && (await this.cacheService.findUserById(id)).isHibernated;
// Update user before the hibernation cache, because the latter may refresh from DB
await this.usersRepository.update({ id }, { await this.usersRepository.update({ id }, {
updatedAt: job.updatedAt, updatedAt: job.updatedAt,
lastActiveDate,
isHibernated: isWakingUp ? false : undefined,
notesCount: job.notesCountDelta ? () => `"notesCount" + ${job.notesCountDelta}` : undefined, notesCount: job.notesCountDelta ? () => `"notesCount" + ${job.notesCountDelta}` : undefined,
followingCount: job.followingCountDelta ? () => `"followingCount" + ${job.followingCountDelta}` : undefined, followingCount: job.followingCountDelta ? () => `"followingCount" + ${job.followingCountDelta}` : undefined,
followersCount: job.followersCountDelta ? () => `"followersCount" + ${job.followersCountDelta}` : undefined, followersCount: job.followersCountDelta ? () => `"followersCount" + ${job.followersCountDelta}` : undefined,
}); });
await this.internalEventService.emit('userUpdated', { id }); await this.internalEventService.emit('userUpdated', { id });
// Wake up hibernated users
if (isWakingUp) {
await this.followingsRepository.update({ followerId: id }, { isFollowerHibernated: false });
await this.cacheService.hibernatedUserCache.set(id, false);
}
} }
}, },
{ {
@ -195,6 +212,9 @@ export class CollapsedQueueService implements OnApplicationShutdown {
updatedAt: data.updatedAt != null updatedAt: data.updatedAt != null
? new Date(data.updatedAt) ? new Date(data.updatedAt)
: data.updatedAt, : data.updatedAt,
lastActiveDate: data.lastActiveDate != null
? new Date(data.lastActiveDate)
: data.lastActiveDate,
}), }),
}, },
); );

View file

@ -10,7 +10,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { CacheService } from '@/core/CacheService.js'; import { CollapsedQueueService } from '@/global/CollapsedQueueService.js';
import { TimeService } from '@/global/TimeService.js'; import { TimeService } from '@/global/TimeService.js';
@Injectable() @Injectable()
@ -22,43 +22,14 @@ export class UserService {
private followingsRepository: FollowingsRepository, private followingsRepository: FollowingsRepository,
private systemWebhookService: SystemWebhookService, private systemWebhookService: SystemWebhookService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private readonly cacheService: CacheService, private readonly collapsedQueueService: CollapsedQueueService,
private readonly timeService: TimeService, private readonly timeService: TimeService,
) { ) {
} }
@bindThis @bindThis
public async updateLastActiveDate(user: MiUser): Promise<void> { public async updateLastActiveDate(user: MiUser): Promise<void> {
if (user.isHibernated) { await this.collapsedQueueService.updateUserQueue.enqueue(user.id, { lastActiveDate: this.timeService.date });
const result = await this.usersRepository.createQueryBuilder().update()
.set({
lastActiveDate: this.timeService.date,
})
.where('id = :id', { id: user.id })
.returning('*')
.execute()
.then((response) => {
return response.raw[0];
});
const wokeUp = result.isHibernated;
if (wokeUp) {
await Promise.all([
this.usersRepository.update(user.id, {
isHibernated: false,
}),
this.followingsRepository.update({
followerId: user.id,
}, {
isFollowerHibernated: false,
}),
this.cacheService.hibernatedUserCache.set(user.id, false),
]);
}
} else {
this.usersRepository.update(user.id, {
lastActiveDate: this.timeService.date,
});
}
} }
/** /**

View file

@ -271,11 +271,11 @@ export class StreamingApiServerService implements OnApplicationShutdown {
this.#connections.set(connection, this.timeService.now); this.#connections.set(connection, this.timeService.now);
// TODO use collapsed queue // TODO use collapsed queue
const userUpdateIntervalId = user ? this.timeService.startTimer(() => { const userUpdateIntervalId = user ? this.timeService.startTimer(async () => {
this.usersService.updateLastActiveDate(user); await this.usersService.updateLastActiveDate(user);
}, 1000 * 60 * 5, { repeated: true }) : null; }, 1000 * 60 * 5, { repeated: true }) : null;
if (user) { if (user) {
this.usersService.updateLastActiveDate(user); await this.usersService.updateLastActiveDate(user);
} }
const pong = () => { const pong = () => {
this.#connections.set(connection, this.timeService.now); this.#connections.set(connection, this.timeService.now);