move user hibernation into HibernateUsersProcessorService.ts
This commit is contained in:
parent
7050583b86
commit
4a341a53d0
6 changed files with 93 additions and 88 deletions
|
|
@ -1037,55 +1037,13 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (Math.random() < 0.1) {
|
||||
process.nextTick(() => {
|
||||
this.checkHibernation(followings);
|
||||
});
|
||||
}
|
||||
// checkHibernation moved to HibernateUsersProcessorService
|
||||
}
|
||||
|
||||
r.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async checkHibernation(followings: MiFollowing[]) {
|
||||
if (followings.length === 0) return;
|
||||
|
||||
const shuffle = (array: MiFollowing[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
// ランダムに最大1000件サンプリング
|
||||
const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000));
|
||||
|
||||
const hibernatedUsers = await this.usersRepository.find({
|
||||
where: {
|
||||
id: In(samples.map(x => x.followerId)),
|
||||
lastActiveDate: LessThan(new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 50))),
|
||||
},
|
||||
select: ['id'],
|
||||
});
|
||||
|
||||
if (hibernatedUsers.length > 0) {
|
||||
await Promise.all([
|
||||
this.usersRepository.update({
|
||||
id: In(hibernatedUsers.map(x => x.id)),
|
||||
}, {
|
||||
isHibernated: true,
|
||||
}),
|
||||
this.followingsRepository.update({
|
||||
followerId: In(hibernatedUsers.map(x => x.id)),
|
||||
}, {
|
||||
isFollowerHibernated: true,
|
||||
}),
|
||||
this.cacheService.hibernatedUserCache.setMany(hibernatedUsers.map(x => [x.id, true])),
|
||||
]);
|
||||
}
|
||||
}
|
||||
// checkHibernation moved to HibernateUsersProcessorService
|
||||
|
||||
public checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
|
||||
if (prohibitedWords == null) {
|
||||
|
|
|
|||
|
|
@ -909,55 +909,13 @@ export class NoteEditService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (Math.random() < 0.1) {
|
||||
process.nextTick(() => {
|
||||
this.checkHibernation(followings);
|
||||
});
|
||||
}
|
||||
// checkHibernation moved to HibernateUsersProcessorService
|
||||
}
|
||||
|
||||
r.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async checkHibernation(followings: MiFollowing[]) {
|
||||
if (followings.length === 0) return;
|
||||
|
||||
const shuffle = (array: MiFollowing[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
// ランダムに最大1000件サンプリング
|
||||
const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000));
|
||||
|
||||
const hibernatedUsers = await this.usersRepository.find({
|
||||
where: {
|
||||
id: In(samples.map(x => x.followerId)),
|
||||
lastActiveDate: LessThan(new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 50))),
|
||||
},
|
||||
select: ['id'],
|
||||
});
|
||||
|
||||
if (hibernatedUsers.length > 0) {
|
||||
await Promise.all([
|
||||
this.usersRepository.update({
|
||||
id: In(hibernatedUsers.map(x => x.id)),
|
||||
}, {
|
||||
isHibernated: true,
|
||||
}),
|
||||
this.followingsRepository.update({
|
||||
followerId: In(hibernatedUsers.map(x => x.id)),
|
||||
}, {
|
||||
isFollowerHibernated: true,
|
||||
}),
|
||||
this.cacheService.hibernatedUserCache.setMany(hibernatedUsers.map(x => [x.id, true])),
|
||||
]);
|
||||
}
|
||||
}
|
||||
// checkHibernation moved to HibernateUsersProcessorService
|
||||
|
||||
@bindThis
|
||||
private collapseNotesCount(oldValue: number, newValue: number) {
|
||||
|
|
|
|||
|
|
@ -177,6 +177,19 @@ export class QueueService implements OnModuleInit {
|
|||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
await this.systemQueue.upsertJobScheduler(
|
||||
'hibernateUsers-scheduler',
|
||||
{ pattern: '40 1 * * *' },
|
||||
{
|
||||
name: 'hibernateUsers',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
// Slot '50 1 * * *' is available for future work
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import { ExportFavoritesProcessorService } from './processors/ExportFavoritesPro
|
|||
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
|
||||
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
|
||||
import { CleanupApLogsProcessorService } from './processors/CleanupApLogsProcessorService.js';
|
||||
import { HibernateUsersProcessorService } from './processors/HibernateUsersProcessorService.js';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -91,6 +92,7 @@ import { CleanupApLogsProcessorService } from './processors/CleanupApLogsProcess
|
|||
QueueProcessorService,
|
||||
ScheduleNotePostProcessorService,
|
||||
CleanupApLogsProcessorService,
|
||||
HibernateUsersProcessorService,
|
||||
],
|
||||
exports: [
|
||||
QueueProcessorService,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import { QueueLoggerService } from './QueueLoggerService.js';
|
|||
import { QUEUE, baseWorkerOptions } from './const.js';
|
||||
import { ImportNotesProcessorService } from './processors/ImportNotesProcessorService.js';
|
||||
import { CleanupApLogsProcessorService } from './processors/CleanupApLogsProcessorService.js';
|
||||
import { HibernateUsersProcessorService } from './processors/HibernateUsersProcessorService.js';
|
||||
|
||||
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
|
||||
function httpRelatedBackoff(attemptsMade: number) {
|
||||
|
|
@ -138,6 +139,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
private scheduleNotePostProcessorService: ScheduleNotePostProcessorService,
|
||||
private readonly timeService: TimeService,
|
||||
private readonly cleanupApLogsProcessorService: CleanupApLogsProcessorService,
|
||||
private readonly hibernateUsersProcessorService: HibernateUsersProcessorService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger;
|
||||
|
||||
|
|
@ -159,6 +161,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process();
|
||||
case 'clean': return this.cleanProcessorService.process();
|
||||
case 'cleanupApLogs': return this.cleanupApLogsProcessorService.process();
|
||||
case 'hibernateUsers': return this.hibernateUsersProcessorService.process();
|
||||
default: throw new Error(`unrecognized job type ${job.name} for system`);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In, LessThan } from 'typeorm';
|
||||
import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { renderInlineError } from '@/misc/render-inline-error.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
@Injectable()
|
||||
export class HibernateUsersProcessorService {
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private readonly usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.followingsRepository)
|
||||
private readonly followingsRepository: FollowingsRepository,
|
||||
|
||||
private readonly cacheService: CacheService,
|
||||
|
||||
queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
this.logger = queueLoggerService.logger.createSubLogger('hibernate-users');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process() {
|
||||
try {
|
||||
let totalHibernated = 0;
|
||||
|
||||
// Any users last active *before* this date should be hibernated
|
||||
const hibernationThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 50));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
while (true) {
|
||||
// Work in batches of 100
|
||||
const page = await this.usersRepository.find({
|
||||
where: { isHibernated: false, lastActiveDate: LessThan(hibernationThreshold) },
|
||||
select: { id: true },
|
||||
take: 100,
|
||||
}) as { id: string }[];
|
||||
const ids = page.map(u => u.id);
|
||||
|
||||
// Stop when we get them all
|
||||
if (ids.length < 1) break;
|
||||
|
||||
await this.usersRepository.update({ id: In(ids) }, { isHibernated: true });
|
||||
await this.followingsRepository.update({ followerId: In(ids) }, { isFollowerHibernated: true });
|
||||
await this.cacheService.hibernatedUserCache.refreshMany(ids);
|
||||
|
||||
totalHibernated += ids.length;
|
||||
}
|
||||
|
||||
if (totalHibernated > 0) {
|
||||
this.logger.info(`Hibernated ${totalHibernated} inactive users`);
|
||||
} else {
|
||||
this.logger.debug('Skipping hibernation: nothing to do');
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(`Error hibernating users: ${renderInlineError(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue