summary refs log tree commit diff
diff options
context:
space:
mode:
authorOh64 <contact@oh64.moe>2026-03-27 01:44:49 +0100
committerRory& <root@rory.gay>2026-04-06 14:27:43 +0200
commitd32c75d1644e3d8c0935410cf1e48457cd67b9f8 (patch)
treeb1d84e3f90f1f56167ffaa436b0af6d7e6b459a5
parentCS-CDN changes from worker stuff (diff)
downloadserver-ts-d32c75d1644e3d8c0935410cf1e48457cd67b9f8.tar.xz
Refactor Identify : batch guild queries and parallelise relation fetches
-rw-r--r--src/gateway/opcodes/Identify.ts144
-rw-r--r--src/gateway/opcodes/RequestGuildMembers.ts3
-rw-r--r--src/gateway/util/Utils.ts1
3 files changed, 92 insertions, 56 deletions
diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts

index 111325b0..3aa91b9f 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts
@@ -291,35 +291,33 @@ export async function onIdentify(this: WebSocket, data: Payload) { const userMetaQueryTime = taskSw.getElapsedAndReset(); - const { result: memberGuilds, elapsed: queryGuildsTime } = await timePromise(() => - Promise.all( - members.map((m) => - Guild.findOneOrFail({ - where: { id: m.guild_id }, - select: Object.fromEntries( - getDatabase()! - .getMetadata(Guild) - .columns.map((x) => [x.propertyName, true]), - ), - }), - ), - ), - ); - - const guildIds = memberGuilds.map((g) => g.id); + const memberGuildIds = members.map((m) => m.guild_id); // select relations const [ + { result: memberGuilds, elapsed: queryGuildsTime }, { result: memberGuildChannels, elapsed: queryGuildChannelsTime }, { result: memberGuildEmojis, elapsed: queryGuildEmojisTime }, { result: memberGuildRoles, elapsed: queryGuildRolesTime }, { result: memberGuildStickers, elapsed: queryGuildStickersTime }, { result: memberGuildVoiceStates, elapsed: queryGuildVoiceStatesTime }, + { result: threadMembers, elapsed: threadMemberTime }, + { result: allThreadsRaw, elapsed: queryThreadsTime }, ] = await Promise.all([ timePromise(() => + Guild.find({ + where: { id: In(memberGuildIds) }, + select: Object.fromEntries( + getDatabase()! + .getMetadata(Guild) + .columns.map((x) => [x.propertyName, true]), + ), + }), + ), + timePromise(() => Channel.find({ where: { - guild_id: In(guildIds), + guild_id: In(memberGuildIds), type: Not(In([ChannelType.GUILD_PUBLIC_THREAD, ChannelType.GUILD_PRIVATE_THREAD, ChannelType.GUILD_NEWS_THREAD])), }, order: { guild_id: "ASC" }, @@ -328,34 +326,93 @@ export async function onIdentify(this: WebSocket, data: Payload) { ), timePromise(() => Emoji.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), timePromise(() => Role.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), timePromise(() => Sticker.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), timePromise(() => VoiceState.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), + timePromise(() => + ThreadMember.find({ + where: { member_idx: In(members.map(({ index }) => index)) }, + }), + ), + timePromise(() => + Channel.find({ + where: { + type: In([ChannelType.GUILD_NEWS_THREAD, ChannelType.GUILD_PUBLIC_THREAD]), + guild_id: In(memberGuildIds), + }, + }), + ), ]); + const guildIds = memberGuilds.map((g) => g.id); + + const allThreads = allThreadsRaw.filter(({ thread_metadata }) => thread_metadata?.archived === false); + const threadMemberMap = new Map(threadMembers.map((member) => [member.id, member] as const)); + + const channelsByGuild = new Map<string, Channel[]>(); + const emojisByGuild = new Map<string, Emoji[]>(); + const rolesByGuild = new Map<string, Role[]>(); + const stickersByGuild = new Map<string, Sticker[]>(); + const voiceStatesByGuild = new Map<string, VoiceState[]>(); + const threadsByGuild = new Map<string, Channel[]>(); + + for (const c of memberGuildChannels) { + const arr = channelsByGuild.get(c.guild_id!); + if (arr) arr.push(c); + else channelsByGuild.set(c.guild_id!, [c]); + } + for (const e of memberGuildEmojis) { + const arr = emojisByGuild.get(e.guild_id!); + if (arr) arr.push(e); + else emojisByGuild.set(e.guild_id!, [e]); + } + for (const r of memberGuildRoles) { + const arr = rolesByGuild.get(r.guild_id!); + if (arr) arr.push(r); + else rolesByGuild.set(r.guild_id!, [r]); + } + for (const s of memberGuildStickers) { + const arr = stickersByGuild.get(s.guild_id!); + if (arr) arr.push(s); + else stickersByGuild.set(s.guild_id!, [s]); + } + for (const v of memberGuildVoiceStates) { + const arr = voiceStatesByGuild.get(v.guild_id!); + if (arr) arr.push(v); + else voiceStatesByGuild.set(v.guild_id!, [v]); + } + for (const t of allThreads) { + const arr = threadsByGuild.get(t.guild_id!); + if (arr) arr.push(t); + else threadsByGuild.set(t.guild_id!, [t]); + } + + const guildMap = new Map(memberGuilds.map((g) => [g.id, g])); + const mergeMemberGuildsTrace: TraceNode = { micros: 0, calls: [], }; + members.forEach((m) => { const sw = Stopwatch.startNew(); const totalSw = Stopwatch.startNew(); @@ -364,32 +421,26 @@ export async function onIdentify(this: WebSocket, data: Payload) { calls: [], }; - const g = memberGuilds.find((mg) => mg.id === m.guild_id); + const g = guildMap.get(m.guild_id); if (g) { m.guild = g; trace.calls.push("findGuild", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //channels - g.channels = memberGuildChannels.filter((c) => c.guild_id === m.guild_id); + g.channels = channelsByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterChannels(${g.channels.length}/${memberGuildChannels.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //emojis - g.emojis = memberGuildEmojis.filter((e) => e.guild_id === m.guild_id); + g.emojis = emojisByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterEmojis(${g.emojis.length}/${memberGuildEmojis.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //roles - g.roles = memberGuildRoles.filter((r) => r.guild_id === m.guild_id); + g.roles = rolesByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterRoles(${g.roles.length}/${memberGuildRoles.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //stickers - g.stickers = memberGuildStickers.filter((s) => s.guild_id === m.guild_id); + g.stickers = stickersByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterStickers(${g.stickers.length}/${memberGuildStickers.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //voice states - g.voice_states = memberGuildVoiceStates.filter((v) => v.guild_id === m.guild_id); + g.voice_states = voiceStatesByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterVoiceStates(${g.voice_states.length}/${memberGuildVoiceStates.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //total trace.micros = totalSw.elapsed().totalMicroseconds; mergeMemberGuildsTrace.calls!.push(`guild_${m.guild_id}`, trace); } else { @@ -435,31 +486,15 @@ export async function onIdentify(this: WebSocket, data: Payload) { ]; }); const mergedMembersTime = taskSw.getElapsedAndReset(); - const member_idx = members.map(({ index }) => index); - - const threadMembers = await ThreadMember.find({ - where: { member_idx: In(member_idx) }, - }); - const threadMemberMap = new Map(threadMembers.map((member) => [member.id, member] as const)); - const threadMemberTime = taskSw.getElapsedAndReset(); // Populated with guilds 'unavailable' currently // Just for bots //TODO get this a better type const pending_guilds: { id: string }[] = []; - const allThreads = ( - await Channel.find({ - where: { - type: In([ChannelType.GUILD_NEWS_THREAD, ChannelType.GUILD_PUBLIC_THREAD]), - guild_id: In(members.map(({ guild }) => guild.id)), - }, - }) - ).filter(({ thread_metadata }) => thread_metadata?.archived === false); - // Generate guilds list ( make them unavailable if user is bot ) const guilds: GuildOrUnavailable[] = members.map((member) => { - member.guild.channels = member.guild.channels + member.guild.channels = (channelsByGuild.get(member.guild_id) ?? []) /* //TODO maybe implement this correctly, by causing create and delete events for users who can newly view and not view the channels, along with doing these checks correctly, as they don't currently take into account that the owner of the guild is always able to view channels, with potentially other issues .filter((channel) => { @@ -481,7 +516,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { }) .sort((a, b) => a.position - b.position); - const threads: Channel[] = allThreads.filter((_) => _.guild_id === member.guild_id); + const threads: Channel[] = threadsByGuild.get(member.guild_id) ?? []; const guildjson = { ...member.guild.toJSON(), @@ -512,10 +547,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { ...DefaultUserGuildSettings, ...x.settings, guild_id: x.guild_id, - channel_overrides: Object.entries(x.settings.channel_overrides ?? {}).map((y) => ({ - ...y[1], - channel_id: y[0], - })), + channel_overrides: x.settings.channel_overrides ? Object.entries(x.settings.channel_overrides).map(([k, v]) => ({ ...v, channel_id: k })) : [], })); const generateUserGuildSettingsTime = taskSw.getElapsedAndReset(); @@ -750,6 +782,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { queryGuildRolesTime, queryGuildStickersTime, queryGuildVoiceStatesTime, + threadMemberTime, + queryThreadsTime, })) { if (subvalue) { val.calls.push(subkey, { diff --git a/src/gateway/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts
index cba2c89c..fbb249d3 100644 --- a/src/gateway/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts
@@ -31,7 +31,8 @@ export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { if (d.user_ids && !Array.isArray(d.user_ids)) d.user_ids = [d.user_ids]; if (Config.get().offload.gateway.guildMembersUrl !== null) { - return await handleOffloadedGatewayRequest(this, Config.get().offload.gateway.guildMembersUrl!, d); + const guildIds: string[] = Array.isArray(d.guild_id) ? d.guild_id : [d.guild_id]; + return await handleOffloadedGatewayRequest(this, Config.get().offload.gateway.guildMembersUrl!, guildIds); } check.call(this, RequestGuildMembersSchema, d); diff --git a/src/gateway/util/Utils.ts b/src/gateway/util/Utils.ts
index 1ca45ee4..c54c4980 100644 --- a/src/gateway/util/Utils.ts +++ b/src/gateway/util/Utils.ts
@@ -71,6 +71,7 @@ export async function handleOffloadedGatewayRequest(socket: WebSocket, url: stri Authorization: `Bearer ${socket.accessToken}`, // because the session may not have an id in the token! "X-Session-Id": socket.session_id, + "Content-Type": "application/json", }, });