Skip to content
Snippets Groups Projects
Commit ac8c66f5 authored by syuilo's avatar syuilo
Browse files

perf(server): refactor and performance improvements

parent 22b56ac6
No related branches found
No related tags found
No related merge requests found
Showing
with 84 additions and 62 deletions
export class Cache<T> {
private cache: Map<string | null, { date: number; value: T; }>;
public cache: Map<string | null, { date: number; value: T; }>;
private lifetime: number;
constructor(lifetime: Cache<never>['lifetime']) {
......
......@@ -234,3 +234,9 @@ export interface ILocalUser extends User {
export interface IRemoteUser extends User {
host: string;
}
export type CacheableLocalUser = ILocalUser;
export type CacheableRemoteUser = IRemoteUser;
export type CacheableUser = CacheableLocalUser | CacheableRemoteUser;
......@@ -15,6 +15,8 @@ import DbResolver from '@/remote/activitypub/db-resolver.js';
import { resolvePerson } from '@/remote/activitypub/models/person.js';
import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js';
import { StatusError } from '@/misc/fetch.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { UserPublickey } from '@/models/entities/user-publickey.js';
const logger = new Logger('inbox');
......@@ -42,11 +44,13 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
return `Old keyId is no longer supported. ${keyIdLower}`;
}
// TDOO: キャッシュ
const dbResolver = new DbResolver();
// HTTP-Signature keyIdを元にDBから取得
let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
let authUser: {
user: CacheableRemoteUser;
key: UserPublickey | null;
} | null = await dbResolver.getAuthUserFromKeyId(signature.keyId);
// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
if (authUser == null) {
......
......@@ -3,26 +3,26 @@ import Resolver from './resolver.js';
import { resolvePerson } from './models/person.js';
import { unique, concat } from '@/prelude/array.js';
import promiseLimit from 'promise-limit';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { User, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
type Visibility = 'public' | 'home' | 'followers' | 'specified';
type AudienceInfo = {
visibility: Visibility,
mentionedUsers: User[],
visibleUsers: User[],
mentionedUsers: CacheableUser[],
visibleUsers: CacheableUser[],
};
export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
const toGroups = groupingAudience(getApIds(to), actor);
const ccGroups = groupingAudience(getApIds(cc), actor);
const others = unique(concat([toGroups.other, ccGroups.other]));
const limit = promiseLimit<User | null>(2);
const limit = promiseLimit<CacheableUser | null>(2);
const mentionedUsers = (await Promise.all(
others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null)))
)).filter((x): x is User => x != null);
)).filter((x): x is CacheableUser => x != null);
if (toGroups.public.length > 0) {
return {
......@@ -55,7 +55,7 @@ export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApOb
};
}
function groupingAudience(ids: string[], actor: IRemoteUser) {
function groupingAudience(ids: string[], actor: CacheableRemoteUser) {
const groups = {
public: [] as string[],
followers: [] as string[],
......@@ -85,7 +85,7 @@ function isPublic(id: string) {
].includes(id);
}
function isFollowers(id: string, actor: IRemoteUser) {
function isFollowers(id: string, actor: CacheableRemoteUser) {
return (
id === (actor.followersUri || `${actor.uri}/followers`)
);
......
import escapeRegexp from 'escape-regexp';
import config from '@/config/index.js';
import { Note } from '@/models/entities/note.js';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { User, IRemoteUser, CacheableRemoteUser } from '@/models/entities/user.js';
import { UserPublickey } from '@/models/entities/user-publickey.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
import { IObject, getApId } from './type.js';
import { resolvePerson } from './models/person.js';
import escapeRegexp from 'escape-regexp';
import { Cache } from '@/misc/cache.js';
import { userByIdCache } from '@/services/user-cache.js';
const publicKeyCache = new Cache<UserPublickey | null>(Infinity);
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
export default class DbResolver {
constructor() {
......@@ -75,17 +80,24 @@ export default class DbResolver {
/**
* AP KeyId => Misskey User and Key
*/
public async getAuthUserFromKeyId(keyId: string): Promise<AuthUser | null> {
const key = await UserPublickeys.findOne({
keyId,
}, {
relations: ['user'],
});
public async getAuthUserFromKeyId(keyId: string): Promise<{
user: CacheableRemoteUser;
key: UserPublickey;
} | null> {
const key = await publicKeyCache.fetch(keyId, async () => {
const key = await UserPublickeys.findOne({
keyId,
});
if (key == null) return null;
return key;
}, key => key != null);
if (key == null) return null;
return {
user: key.user as IRemoteUser,
user: await userByIdCache.fetch(key.userId, () => Users.findOneOrFail(key.userId)) as CacheableRemoteUser,
key,
};
}
......@@ -93,12 +105,15 @@ export default class DbResolver {
/**
* AP Actor id => Misskey User and Key
*/
public async getAuthUserFromApId(uri: string): Promise<AuthUser | null> {
const user = await resolvePerson(uri) as IRemoteUser;
public async getAuthUserFromApId(uri: string): Promise<{
user: CacheableRemoteUser;
key: UserPublickey | null;
} | null> {
const user = await resolvePerson(uri) as CacheableRemoteUser;
if (user == null) return null;
const key = await UserPublickeys.findOne(user.id);
const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOne(user.id).then(x => x || null), v => v != null); // TODO: typeorm 3.0 にしたら.then(x => x || null)は消せる
return {
user,
......@@ -125,11 +140,6 @@ export default class DbResolver {
}
}
export type AuthUser = {
user: IRemoteUser;
key?: UserPublickey;
};
type UriParseResult = {
/** id in DB (local object only) */
id?: string;
......
......@@ -112,7 +112,7 @@ export default class DeliverManager {
* @param activity Activity
* @param from Followee
*/
export async function deliverToFollowers(actor: ILocalUser, activity: any) {
export async function deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) {
const manager = new DeliverManager(actor, activity);
manager.addFollowersRecipe();
await manager.execute();
......@@ -123,7 +123,7 @@ export async function deliverToFollowers(actor: ILocalUser, activity: any) {
* @param activity Activity
* @param to Target user
*/
export async function deliverToUser(actor: ILocalUser, activity: any, to: IRemoteUser) {
export async function deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) {
const manager = new DeliverManager(actor, activity);
manager.addDirectRecipe(to);
await manager.execute();
......
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import accept from '@/services/following/requests/accept.js';
import { IFollow } from '../../type.js';
import DbResolver from '../../db-resolver.js';
import { relayAccepted } from '@/services/relay.js';
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
const dbResolver = new DbResolver();
......
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import acceptFollow from './follow.js';
import { IAccept, isFollow, getApType } from '../../type.js';
import { apLogger } from '../../logger.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => {
const uri = activity.id || activity;
logger.info(`Accept: ${uri}`);
......
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IAdd } from '../../type.js';
import { resolveNote } from '../../models/note.js';
import { addPinned } from '@/services/i/pin.js';
export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: IAdd): Promise<void> => {
if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor');
}
......
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import announceNote from './note.js';
import { IAnnounce, getApId } from '../../type.js';
import { apLogger } from '../../logger.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> => {
const uri = getApId(activity);
logger.info(`Announce: ${uri}`);
......
import Resolver from '../../resolver.js';
import post from '@/services/note/create.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IAnnounce, getApId } from '../../type.js';
import { fetchNote, resolveNote } from '../../models/note.js';
import { apLogger } from '../../logger.js';
......@@ -15,10 +15,9 @@ const logger = apLogger;
/**
* アナウンスアクティビティを捌きます
*/
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
const uri = getApId(activity);
// アナウンサーが凍結されていたらスキップ
if (actor.isSuspended) {
return;
}
......
import { IBlock } from '../../type.js';
import block from '@/services/blocking/create.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import DbResolver from '../../db-resolver.js';
import { Users } from '@/models/index.js';
export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => {
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
const dbResolver = new DbResolver();
......@@ -17,6 +18,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> =>
return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
}
await block(actor, blockee);
await block(await Users.findOneOrFail(actor.id), blockee);
return `ok`;
};
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import createNote from './note.js';
import { ICreate, getApId, isPost, getApType } from '../../type.js';
import { apLogger } from '../../logger.js';
......@@ -7,7 +7,7 @@ import { toArray, concat, unique } from '@/prelude/array.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: ICreate): Promise<void> => {
const uri = getApId(activity);
logger.info(`Create: ${uri}`);
......
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { createNote, fetchNote } from '../../models/note.js';
import { getApId, IObject, ICreate } from '../../type.js';
import { getApLock } from '@/misc/app-lock.js';
......@@ -9,7 +9,7 @@ import { StatusError } from '@/misc/fetch.js';
/**
* 投稿作成アクティビティを捌きます
*/
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
const uri = getApId(note);
if (typeof note === 'object') {
......
import { apLogger } from '../../logger.js';
import { createDeleteAccountJob } from '@/queue/index.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { Users } from '@/models/index.js';
const logger = apLogger;
export async function deleteActor(actor: IRemoteUser, uri: string): Promise<string> {
export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> {
logger.info(`Deleting the Actor: ${uri}`);
if (actor.uri !== uri) {
return `skip: delete actor ${actor.uri} !== ${uri}`;
}
if (actor.isDeleted) {
const user = await Users.findOneOrFail(actor.id);
if (user.isDeleted) {
logger.info(`skip: already deleted`);
}
......
import deleteNote from './note.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js';
import { toSingle } from '@/prelude/array.js';
import { deleteActor } from './actor.js';
......@@ -7,7 +7,7 @@ import { deleteActor } from './actor.js';
/**
* 削除アクティビティを捌きます
*/
export default async (actor: IRemoteUser, activity: IDelete): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<string> => {
if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor');
}
......
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import deleteNode from '@/services/note/delete.js';
import { apLogger } from '../../logger.js';
import DbResolver from '../../db-resolver.js';
......@@ -7,7 +7,7 @@ import { deleteMessage } from '@/services/messages/delete.js';
const logger = apLogger;
export default async function(actor: IRemoteUser, uri: string): Promise<string> {
export default async function(actor: CacheableRemoteUser, uri: string): Promise<string> {
logger.info(`Deleting the Note: ${uri}`);
const unlock = await getApLock(uri);
......
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import config from '@/config/index.js';
import { IFlag, getApIds } from '../../type.js';
import { AbuseUserReports, Users } from '@/models/index.js';
import { In } from 'typeorm';
import { genId } from '@/misc/gen-id.js';
export default async (actor: IRemoteUser, activity: IFlag): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFlag): Promise<string> => {
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
const uris = getApIds(activity.object);
......
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import follow from '@/services/following/create.js';
import { IFollow } from '../type.js';
import DbResolver from '../db-resolver.js';
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
const dbResolver = new DbResolver();
const followee = await dbResolver.getUserFromApId(activity.object);
......
import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag } from '../type.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import create from './create/index.js';
import performDeleteActivity from './delete/index.js';
import performUpdateActivity from './update/index.js';
......@@ -17,8 +17,9 @@ import flag from './flag/index.js';
import { apLogger } from '../logger.js';
import Resolver from '../resolver.js';
import { toArray } from '@/prelude/array.js';
import { Users } from '@/models/index.js';
export async function performActivity(actor: IRemoteUser, activity: IObject) {
export async function performActivity(actor: CacheableRemoteUser, activity: IObject) {
if (isCollectionOrOrderedCollection(activity)) {
const resolver = new Resolver();
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
......@@ -36,7 +37,7 @@ export async function performActivity(actor: IRemoteUser, activity: IObject) {
}
}
async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> {
async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> {
if (actor.isSuspended) return;
if (isCreate(activity)) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment