From b4b6c502729b2461d5cc53e1b244665a45877bf7 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 19 Nov 2020 23:45:01 +0000 Subject: [PATCH 1/6] Cache user features --- src/datastore/postgres/PgDataStore.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/datastore/postgres/PgDataStore.ts b/src/datastore/postgres/PgDataStore.ts index 177f68839..e56e9830c 100644 --- a/src/datastore/postgres/PgDataStore.ts +++ b/src/datastore/postgres/PgDataStore.ts @@ -27,9 +27,12 @@ import Bluebird from "bluebird"; import { StringCrypto } from "../StringCrypto"; import { toIrcLowerCase } from "../../irc/formatting"; import { NeDBDataStore } from "../NedbDataStore"; +import QuickLRU from "quick-lru"; const log = getLogger("PgDatastore"); +const FEATURE_CACHE_SIZE = 512; + export class PgDataStore implements DataStore { private serverMappings: {[domain: string]: IrcServer} = {}; @@ -37,6 +40,9 @@ export class PgDataStore implements DataStore { private pgPool: Pool; private hasEnded = false; private cryptoStore?: StringCrypto; + private userFeatureCache = new QuickLRU({ + maxSize: FEATURE_CACHE_SIZE, + }); constructor(private bridgeDomain: string, connectionString: string, pkeyPath?: string, min = 1, max = 4) { this.pgPool = new Pool({ @@ -465,14 +471,14 @@ export class PgDataStore implements DataStore { } public async getUserFeatures(userId: string): Promise { - const pgRes = ( - await this.pgPool.query("SELECT features FROM user_features WHERE user_id = $1", - [userId]) - ); - if (pgRes.rowCount === 0) { - return {}; + const existing = this.userFeatureCache.get(userId); + if (existing) { + return existing; } - return pgRes.rows[0].features || {}; + const pgRes = await this.pgPool.query("SELECT features FROM user_features WHERE user_id = $1", [userId]); + const features = (pgRes.rows[0] || {}); + this.userFeatureCache.set(userId, features); + return features; } public async storeUserFeatures(userId: string, features: UserFeatures): Promise { From 195e64b2cb616bfd821f4e8e2379c6a23499f5f5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 20 Nov 2020 00:05:38 +0000 Subject: [PATCH 2/6] Re-use chanList Set rather converting to array each time --- src/DebugApi.ts | 2 +- src/bridge/AdminRoomHandler.ts | 6 +++--- src/bridge/MatrixHandler.ts | 4 ++-- src/irc/BridgedClient.ts | 8 +++++--- src/irc/ClientPool.ts | 8 ++++---- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/DebugApi.ts b/src/DebugApi.ts index 71c32758c..dea6f0761 100644 --- a/src/DebugApi.ts +++ b/src/DebugApi.ts @@ -426,7 +426,7 @@ export class DebugApi { return undefined; } return { - channels: client.chanList, + channels: [...client.chanList], dead: client.isDead(), server: client.server.domain, nick: client.nick, diff --git a/src/bridge/AdminRoomHandler.ts b/src/bridge/AdminRoomHandler.ts index a9584fbfa..8e9c73c47 100644 --- a/src/bridge/AdminRoomHandler.ts +++ b/src/bridge/AdminRoomHandler.ts @@ -409,14 +409,14 @@ export class AdminRoomHandler { "notice", "You are not currently connected to this irc network" )); } - if (client.chanList.length === 0) { + if (client.chanList.size === 0) { return this.ircBridge.sendMatrixAction(room, this.botUser, new MatrixAction( "notice", "You are connected, but not joined to any channels." )); } - let chanList = `You are joined to ${client.chanList.length} rooms: \n\n`; - let chanListHTML = `

You are joined to ${client.chanList.length} rooms:

    `; + let chanList = `You are joined to ${client.chanList.size} rooms: \n\n`; + let chanListHTML = `

    You are joined to ${client.chanList.size} rooms:

      `; for (const channel of client.chanList) { const rooms = await this.ircBridge.getStore().getMatrixRoomsForChannel(server, channel); chanList += `- \`${channel}\` which is bridged to ${rooms.map((r) => r.getId()).join(", ")}`; diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index e57c6f5bd..1809f9f91 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -284,7 +284,7 @@ export class MatrixHandler { for (let i = 0; i < clients.length; i++) { const bridgedClient = clients[i]; - if (bridgedClient.chanList.length === 0) { + if (bridgedClient.chanList.size === 0) { req.log.info( `Bridged client for ${userId} is not in any channels ` + `on ${bridgedClient.server.domain}` @@ -294,7 +294,7 @@ export class MatrixHandler { // Get all rooms that the bridgedClient is in const uniqueRoomIds = new Set(); (await Promise.all( - bridgedClient.chanList.map( + [...bridgedClient.chanList].map( (channel) => { return this.ircBridge.getStore().getMatrixRoomsForChannel( bridgedClient.server, channel diff --git a/src/irc/BridgedClient.ts b/src/irc/BridgedClient.ts index 122732d6c..fb95290ea 100644 --- a/src/irc/BridgedClient.ts +++ b/src/irc/BridgedClient.ts @@ -169,7 +169,7 @@ export class BridgedClient extends EventEmitter { } public get chanList() { - return Array.from(this._chanList); + return this._chanList; } public get status() { @@ -320,14 +320,16 @@ export class BridgedClient extends EventEmitter { } public async reconnect(reconnectChanList: string[]) { + // XXX: Why do we add these here? reconnectChanList.forEach((c) => this._chanList.add(c)); await this.connect(); this.log.info( "Reconnected %s@%s", this.nick, this.server.domain ); - this.log.info("Rejoining %s channels", this.chanList.length); + this.log.info("Rejoining %s channels", this._chanList.size); // This needs to be synchronous - for (const channel of this.chanList) { + for (const channel of this._chanList) { + // XXX: Why do we not catch these? await this.joinChannel(channel); } this.log.info("Rejoined channels"); diff --git a/src/irc/ClientPool.ts b/src/irc/ClientPool.ts index af3b9bce7..7decf58f5 100644 --- a/src/irc/ClientPool.ts +++ b/src/irc/ClientPool.ts @@ -496,7 +496,7 @@ export class ClientPool { public getNickUserIdMappingForChannel(server: IrcServer, channel: string): {[nick: string]: string} { const nickUserIdMap: {[nick: string]: string} = {}; for (const [userId, client] of this.virtualClients[server.domain].userIds.entries()) { - if (client.chanList.includes(channel)) { + if (client.inChannel(channel)) { nickUserIdMap[client.nick] = userId; } } @@ -584,7 +584,7 @@ export class ClientPool { const isBot = bridgedClient.isBot; const chanList = bridgedClient.chanList; - if (chanList.length === 0 && !isBot && disconnectReason !== "iwanttoreconnect") { + if (chanList.size === 0 && !isBot && disconnectReason !== "iwanttoreconnect") { // Never drop the bot, or users that really want to reconnect. log.info( `Dropping ${bridgedClient.id} (${bridgedClient.nick}) because they are not joined to any channels` @@ -627,13 +627,13 @@ export class ClientPool { if (queue === null) { this.reconnectClient({ cli: cli, - chanList: chanList, + chanList: [...chanList], }); return; } queue.enqueue(cli.id, { cli: cli, - chanList: chanList, + chanList: [...chanList], }); } From 36d9838864ea8acf24c9b1aef50cb5143a1db642 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 20 Nov 2020 00:06:13 +0000 Subject: [PATCH 3/6] linting tweak --- src/datastore/postgres/PgDataStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datastore/postgres/PgDataStore.ts b/src/datastore/postgres/PgDataStore.ts index e56e9830c..7a0e22210 100644 --- a/src/datastore/postgres/PgDataStore.ts +++ b/src/datastore/postgres/PgDataStore.ts @@ -518,7 +518,7 @@ export class PgDataStore implements DataStore { [username, domain] ); if (res.rowCount === 0) { - return; + return undefined; } else if (res.rowCount > 1) { log.error("getMatrixUserByUsername returned %s results for %s on %s", res.rowCount, username, domain); @@ -546,7 +546,7 @@ export class PgDataStore implements DataStore { await this.pgPool.query(statement, [userId, Date.now()]); } - public async getLastSeenTimeForUsers(): Promise<{ user_id: string, ts: number }[]> { + public async getLastSeenTimeForUsers(): Promise<{ user_id: string; ts: number }[]> { const res = await this.pgPool.query(`SELECT * FROM last_seen`); return res.rows; } From fdc8858de0c0e947a8963ddf298b4955fee65ee3 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 20 Nov 2020 10:55:59 +0000 Subject: [PATCH 4/6] changelog --- changelog.d/1192 | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1192 diff --git a/changelog.d/1192 b/changelog.d/1192 new file mode 100644 index 000000000..05869df07 --- /dev/null +++ b/changelog.d/1192 @@ -0,0 +1 @@ +Improve the performance of sending messages by speeding up some function calls From 33e86973f03e49b808c37b5718ee6120eb64931c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 20 Nov 2020 10:56:15 +0000 Subject: [PATCH 5/6] Rename 1192 to 1192.bugfix --- changelog.d/{1192 => 1192.bugfix} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{1192 => 1192.bugfix} (100%) diff --git a/changelog.d/1192 b/changelog.d/1192.bugfix similarity index 100% rename from changelog.d/1192 rename to changelog.d/1192.bugfix From e77f3ebbb4062a7038672bb682c30a7546d2a976 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 24 Nov 2020 19:14:00 +0000 Subject: [PATCH 6/6] Update src/bridge/AdminRoomHandler.ts Co-authored-by: Christian Paul --- src/bridge/AdminRoomHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/AdminRoomHandler.ts b/src/bridge/AdminRoomHandler.ts index 8e9c73c47..e2280e1fe 100644 --- a/src/bridge/AdminRoomHandler.ts +++ b/src/bridge/AdminRoomHandler.ts @@ -416,7 +416,7 @@ export class AdminRoomHandler { } let chanList = `You are joined to ${client.chanList.size} rooms: \n\n`; - let chanListHTML = `

      You are joined to ${client.chanList.size} rooms:

        `; + let chanListHTML = `

        You are joined to ${client.chanList.size} rooms:

          `; for (const channel of client.chanList) { const rooms = await this.ircBridge.getStore().getMatrixRoomsForChannel(server, channel); chanList += `- \`${channel}\` which is bridged to ${rooms.map((r) => r.getId()).join(", ")}`;