Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo committed Oct 3, 2023
1 parent 0575207 commit 77498f8
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 11 deletions.
17 changes: 17 additions & 0 deletions packages/backend/migration/1696331570827-hibernation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export class Hibernation1696331570827 {
name = 'Hibernation1696331570827'

async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
await queryRunner.query(`ALTER TABLE "user" ADD "isHibernated" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "following" ADD "isFollowerHibernated" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`CREATE INDEX "IDX_ce62b50d882d4e9dee10ad0d2f" ON "following" ("followeeId", "followerHost", "isFollowerHibernated") `);
}

async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_ce62b50d882d4e9dee10ad0d2f"`);
await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "isFollowerHibernated"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isHibernated"`);
await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
}
}
6 changes: 6 additions & 0 deletions packages/backend/src/core/CoreModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { SignupService } from './SignupService.js';
import { WebAuthnService } from './WebAuthnService.js';
import { UserBlockingService } from './UserBlockingService.js';
import { CacheService } from './CacheService.js';
import { UserService } from './UserService.js';
import { UserFollowingService } from './UserFollowingService.js';
import { UserKeypairService } from './UserKeypairService.js';
import { UserListService } from './UserListService.js';
Expand Down Expand Up @@ -173,6 +174,7 @@ const $SignupService: Provider = { provide: 'SignupService', useExisting: Signup
const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService };
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
const $UserService: Provider = { provide: 'UserService', useExisting: UserService };
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
Expand Down Expand Up @@ -303,6 +305,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
WebAuthnService,
UserBlockingService,
CacheService,
UserService,
UserFollowingService,
UserKeypairService,
UserListService,
Expand Down Expand Up @@ -426,6 +429,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$WebAuthnService,
$UserBlockingService,
$CacheService,
$UserService,
$UserFollowingService,
$UserKeypairService,
$UserListService,
Expand Down Expand Up @@ -550,6 +554,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
WebAuthnService,
UserBlockingService,
CacheService,
UserService,
UserFollowingService,
UserKeypairService,
UserListService,
Expand Down Expand Up @@ -672,6 +677,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$WebAuthnService,
$UserBlockingService,
$CacheService,
$UserService,
$UserFollowingService,
$UserKeypairService,
$UserListService,
Expand Down
51 changes: 47 additions & 4 deletions packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { setImmediate } from 'node:timers/promises';
import * as mfm from 'mfm-js';
import { In, DataSource, IsNull } from 'typeorm';
import { In, DataSource, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import RE2 from 're2';
Expand All @@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js';
import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js';
Expand Down Expand Up @@ -829,13 +829,12 @@ export class NoteCreateService implements OnApplicationShutdown {
}
}
} else {
// TODO: 休眠ユーザーを弾く
// TODO: チャンネルフォロー
// TODO: キャッシュ?
const followings = await this.followingsRepository.find({
where: {
followeeId: user.id,
followerHost: IsNull(),
isFollowerHibernated: false,
},
select: ['followerId', 'withReplies'],
});
Expand Down Expand Up @@ -952,11 +951,55 @@ export class NoteCreateService implements OnApplicationShutdown {
}
}
}

if (Math.random() < 0.1) {
process.nextTick(() => {
this.checkHibernation(followings);
});
}
}

redisPipeline.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(Date.now() - (1000 * 60 * 60 * 24 * 50))),
},
select: ['id'],
});

if (hibernatedUsers.length > 0) {
this.usersRepository.update({
id: In(hibernatedUsers.map(x => x.id)),
}, {
isHibernated: true,
});

this.followingsRepository.update({
followerId: In(hibernatedUsers.map(x => x.id)),
}, {
isFollowerHibernated: true,
});
}
}

@bindThis
public dispose(): void {
this.#shutdownController.abort();
Expand Down
53 changes: 53 additions & 0 deletions packages/backend/src/core/UserService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { Inject, Injectable } from '@nestjs/common';
import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';

@Injectable()
export class UserService {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
) {
}

@bindThis
public async updateLastActiveDate(user: MiUser): Promise<void> {
if (user.isHibernated) {
const result = await this.usersRepository.createQueryBuilder().update()
.set({
lastActiveDate: new Date(),
})
.where('id = :id', { id: user.id })
.returning('*')
.execute()
.then((response) => {
return response.raw[0];
});
const wokeUp = result.isHibernated;
if (wokeUp) {
this.usersRepository.update(user.id, {
isHibernated: false,
});
this.followingsRepository.update({
followerId: user.id,
}, {
isFollowerHibernated: false,
});
}
} else {
this.usersRepository.update(user.id, {
lastActiveDate: new Date(),
});
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/models/Following.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { MiUser } from './User.js';

@Entity('following')
@Index(['followerId', 'followeeId'], { unique: true })
@Index(['followeeId', 'followerHost'])
@Index(['followeeId', 'followerHost', 'isFollowerHibernated'])
export class MiFollowing {
@PrimaryColumn(id())
public id: string;
Expand Down Expand Up @@ -46,6 +46,11 @@ export class MiFollowing {
@JoinColumn()
public follower: MiUser | null;

@Column('boolean', {
default: false,
})
public isFollowerHibernated: boolean;

// タイムラインにその人のリプライまで含めるかどうか
@Column('boolean', {
default: false,
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ export class MiUser {
})
public isExplorable: boolean;

@Column('boolean', {
default: false,
})
public isHibernated: boolean;

// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
@Column('boolean', {
default: false,
Expand Down
10 changes: 4 additions & 6 deletions packages/backend/src/server/api/StreamingApiServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { CacheService } from '@/core/CacheService.js';
import { MiLocalUser } from '@/models/User.js';
import { UserService } from '@/core/UserService.js';
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
import MainStreamConnection from './stream/Connection.js';
import { ChannelsService } from './stream/ChannelsService.js';
Expand All @@ -37,6 +38,7 @@ export class StreamingApiServerService {
private authenticateService: AuthenticateService,
private channelsService: ChannelsService,
private notificationService: NotificationService,
private usersService: UserService,
) {
}

Expand Down Expand Up @@ -130,14 +132,10 @@ export class StreamingApiServerService {
this.#connections.set(connection, Date.now());

const userUpdateIntervalId = user ? setInterval(() => {
this.usersRepository.update(user.id, {
lastActiveDate: new Date(),
});
this.usersService.updateLastActiveDate(user);
}, 1000 * 60 * 5) : null;
if (user) {
this.usersRepository.update(user.id, {
lastActiveDate: new Date(),
});
this.usersService.updateLastActiveDate(user);
}

connection.once('close', () => {
Expand Down

0 comments on commit 77498f8

Please sign in to comment.