[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
Signed by: zotan
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";
import { db } from "@/db/postgre.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) {
const poll = await Polls.findOneByOrFail({ noteId: note.id });
@ -173,6 +174,7 @@ export const NoteRepository = db.getRepository(Note).extend({
myRenotes: Map<Note["id"], boolean>;
};
},
userCache: PackedUserCache = Users.getFreshPackedUserCache(),
): Promise<Packed<"Note">> {
const opts = Object.assign(
{
@ -221,7 +223,7 @@ export const NoteRepository = db.getRepository(Note).extend({
id: note.id,
createdAt: note.createdAt.toISOString(),
userId: note.userId,
user: Users.pack(note.user ?? note.userId, me, {
user: Users.packCached(note.user ?? note.userId, userCache, me, {
detail: false,
}),
text: text,
@ -265,14 +267,14 @@ export const NoteRepository = db.getRepository(Note).extend({
? this.tryPack(note.reply || note.replyId, me, {
detail: false,
_hint_: options?._hint_,
})
}, userCache)
: undefined,
renote: note.renoteId
? this.pack(note.renote || note.renoteId, me, {
detail: true,
_hint_: options?._hint_,
})
}, userCache)
: undefined,
}
: {}),
@ -309,9 +311,10 @@ export const NoteRepository = db.getRepository(Note).extend({
myRenotes: Map<Note["id"], boolean>;
};
},
userCache: PackedUserCache = Users.getFreshPackedUserCache(),
): Promise<Packed<"Note"> | undefined> {
try {
return await this.pack(src, me, options);
return await this.pack(src, me, options, userCache);
} catch {
return undefined;
}

View file

@ -7,7 +7,6 @@ import type { Packed } from "@/misc/schema.js";
import type { Promiseable } from "@/prelude/await-all.js";
import { awaitAll } from "@/prelude/await-all.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 { Cache } from "@/misc/cache.js";
import { db } from "@/db/postgre.js";
@ -37,6 +36,7 @@ import {
UserSecurityKeys,
} from "../index.js";
import type { Instance } from "../entities/instance.js";
import AsyncLock from "async-lock";
const userInstanceCache = new Cache<Instance | null>(
"userInstance",
@ -59,6 +59,11 @@ type IsMeAndIsUserDetailed<
const ajv = new Ajv();
export type PackedUserCache = {
locks: AsyncLock;
results: IsMeAndIsUserDetailed<any, any>[];
}
const localUsernameSchema = {
type: "string",
pattern: /^\w{1,20}$/.toString().slice(1, -1),
@ -366,6 +371,37 @@ export const UserRepository = db.getRepository(User).extend({
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<
ExpectsMe extends boolean | null = null,
D extends boolean = false,
@ -638,8 +674,9 @@ export const UserRepository = db.getRepository(User).extend({
detail?: D;
includeSecrets?: boolean;
},
cache?: PackedUserCache,
): 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,