[backend] Only pack each user once per request

This commit is contained in:
Laura Hausmann 2023-11-22 19:52:04 +01:00
parent 4e6e22633e
commit 735fd37707
No known key found for this signature in database
GPG Key ID: D044E84C5BE01605
2 changed files with 46 additions and 6 deletions

View File

@ -28,6 +28,7 @@ import {
} from "@/misc/populate-emojis.js"; } from "@/misc/populate-emojis.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import { IdentifiableError } from "@/misc/identifiable-error.js"; import { IdentifiableError } from "@/misc/identifiable-error.js";
import { PackedUserCache } from "@/models/repositories/user.js";
export async function populatePoll(note: Note, meId: User["id"] | null) { export async function populatePoll(note: Note, meId: User["id"] | null) {
const poll = await Polls.findOneByOrFail({ noteId: note.id }); const poll = await Polls.findOneByOrFail({ noteId: note.id });
@ -173,6 +174,7 @@ export const NoteRepository = db.getRepository(Note).extend({
myRenotes: Map<Note["id"], boolean>; myRenotes: Map<Note["id"], boolean>;
}; };
}, },
userCache: PackedUserCache = Users.getFreshPackedUserCache(),
): Promise<Packed<"Note">> { ): Promise<Packed<"Note">> {
const opts = Object.assign( const opts = Object.assign(
{ {
@ -221,7 +223,7 @@ export const NoteRepository = db.getRepository(Note).extend({
id: note.id, id: note.id,
createdAt: note.createdAt.toISOString(), createdAt: note.createdAt.toISOString(),
userId: note.userId, userId: note.userId,
user: Users.pack(note.user ?? note.userId, me, { user: Users.packCached(note.user ?? note.userId, userCache, me, {
detail: false, detail: false,
}), }),
text: text, text: text,
@ -265,14 +267,14 @@ export const NoteRepository = db.getRepository(Note).extend({
? this.tryPack(note.reply || note.replyId, me, { ? this.tryPack(note.reply || note.replyId, me, {
detail: false, detail: false,
_hint_: options?._hint_, _hint_: options?._hint_,
}) }, userCache)
: undefined, : undefined,
renote: note.renoteId renote: note.renoteId
? this.pack(note.renote || note.renoteId, me, { ? this.pack(note.renote || note.renoteId, me, {
detail: true, detail: true,
_hint_: options?._hint_, _hint_: options?._hint_,
}) }, userCache)
: undefined, : undefined,
} }
: {}), : {}),
@ -309,9 +311,10 @@ export const NoteRepository = db.getRepository(Note).extend({
myRenotes: Map<Note["id"], boolean>; myRenotes: Map<Note["id"], boolean>;
}; };
}, },
userCache: PackedUserCache = Users.getFreshPackedUserCache(),
): Promise<Packed<"Note"> | undefined> { ): Promise<Packed<"Note"> | undefined> {
try { try {
return await this.pack(src, me, options); return await this.pack(src, me, options, userCache);
} catch { } catch {
return undefined; return undefined;
} }

View File

@ -7,7 +7,6 @@ import type { Packed } from "@/misc/schema.js";
import type { Promiseable } from "@/prelude/await-all.js"; import type { Promiseable } from "@/prelude/await-all.js";
import { awaitAll } from "@/prelude/await-all.js"; import { awaitAll } from "@/prelude/await-all.js";
import { populateEmojis } from "@/misc/populate-emojis.js"; import { populateEmojis } from "@/misc/populate-emojis.js";
import { getAntennas } from "@/misc/antenna-cache.js";
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from "@/const.js"; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from "@/const.js";
import { Cache } from "@/misc/cache.js"; import { Cache } from "@/misc/cache.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
@ -37,6 +36,7 @@ import {
UserSecurityKeys, UserSecurityKeys,
} from "../index.js"; } from "../index.js";
import type { Instance } from "../entities/instance.js"; import type { Instance } from "../entities/instance.js";
import AsyncLock from "async-lock";
const userInstanceCache = new Cache<Instance | null>( const userInstanceCache = new Cache<Instance | null>(
"userInstance", "userInstance",
@ -59,6 +59,11 @@ type IsMeAndIsUserDetailed<
const ajv = new Ajv(); const ajv = new Ajv();
export type PackedUserCache = {
locks: AsyncLock;
results: IsMeAndIsUserDetailed<any, any>[];
}
const localUsernameSchema = { const localUsernameSchema = {
type: "string", type: "string",
pattern: /^\w{1,20}$/.toString().slice(1, -1), pattern: /^\w{1,20}$/.toString().slice(1, -1),
@ -366,6 +371,37 @@ export const UserRepository = db.getRepository(User).extend({
return `${config.url}/identicon/${userId}`; return `${config.url}/identicon/${userId}`;
}, },
getFreshPackedUserCache(): PackedUserCache {
return {
locks: new AsyncLock(),
results: [],
};
},
async packCached<
ExpectsMe extends boolean | null = null,
D extends boolean = false,
>(
src: User["id"] | User,
cache: PackedUserCache,
me?: { id: User["id"] } | null | undefined,
options?: {
detail?: D;
includeSecrets?: boolean;
isPrivateMode?: boolean;
},
): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
const id = typeof src === "object" ? src.id : src;
return cache.locks.acquire(id, async () => {
const result = cache.results.find(p => p.id === id);
if (result) return result as IsMeAndIsUserDetailed<ExpectsMe, D>
return this.pack<ExpectsMe, D>(src, me, options).then(result => {
cache.results.push(result);
return result;
});
});
},
async pack< async pack<
ExpectsMe extends boolean | null = null, ExpectsMe extends boolean | null = null,
D extends boolean = false, D extends boolean = false,
@ -638,8 +674,9 @@ export const UserRepository = db.getRepository(User).extend({
detail?: D; detail?: D;
includeSecrets?: boolean; includeSecrets?: boolean;
}, },
cache?: PackedUserCache,
): Promise<IsUserDetailed<D>[]> { ): Promise<IsUserDetailed<D>[]> {
return Promise.all(users.map((u) => this.pack(u, me, options))); return Promise.all(users.map((u) => this.packCached(u, cache ?? this.getFreshPackedUserCache(), me, options)));
}, },
isLocalUser, isLocalUser,