From e051290df40801433dd9b9cd9644903b6910844a Mon Sep 17 00:00:00 2001 From: Matchu Date: Fri, 7 Jan 2022 11:37:27 -0800 Subject: [PATCH] Better-scoped queries for currentUserOwnsThis etc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I hypothesize that loading people's full trade lists more often than necessary is part of the cause of the recent mega slowdown! My hypothesis is that we're clogging up the MySQL connection socket with a ton of data, which blocks all other queries until the big ones come through and parse out. (I haven't actually validated my assumption that MySQL connections send query results in serial like that, but it makes sense to me, and fits what I've been seeing.) There's more places we could potentially optimize, like the trade list page itself… (we currently aggressively load everything when we could limit it and load the rest on the followup pages, or even paginate the followup pages…) …but my hope is that this helps enough, by relieving the load on the homepage (latest items) and on item searches! --- src/server/loaders.js | 20 ++++++++++++++++++++ src/server/types/Item.js | 28 ++++++++++++++++++---------- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/server/loaders.js b/src/server/loaders.js index 6c61e68..282d32b 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -1245,6 +1245,25 @@ const buildUserClosetHangersLoader = (db) => ); }); +const buildUserItemClosetHangersLoader = (db) => + new DataLoader(async (userIdAndItemIdPairs) => { + const conditions = userIdAndItemIdPairs + .map((_) => `(user_id = ? AND item_id = ?)`) + .join(` OR `); + const params = userIdAndItemIdPairs + .map(({ userId, itemId }) => [userId, itemId]) + .flat(); + const [rows] = await db.execute( + `SELECT * FROM closet_hangers WHERE ${conditions};`, + params + ); + const entities = rows.map(normalizeRow); + + return userIdAndItemIdPairs.map(({ userId, itemId }) => + entities.filter((e) => e.userId === userId && e.itemId === itemId) + ); + }); + const buildUserClosetListsLoader = (db, loaders) => new DataLoader(async (userIds) => { const qs = userIds.map((_) => "?").join(","); @@ -1481,6 +1500,7 @@ function buildLoaders(db) { loaders.userByNameLoader = buildUserByNameLoader(db); loaders.userByEmailLoader = buildUserByEmailLoader(db); loaders.userClosetHangersLoader = buildUserClosetHangersLoader(db); + loaders.userItemClosetHangersLoader = buildUserItemClosetHangersLoader(db); loaders.userClosetListsLoader = buildUserClosetListsLoader(db, loaders); loaders.userNumTotalOutfitsLoader = buildUserNumTotalOutfitsLoader(db); loaders.userOutfitsLoader = buildUserOutfitsLoader(db, loaders); diff --git a/src/server/types/Item.js b/src/server/types/Item.js index 65cbe3a..05ef9ed 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -348,30 +348,38 @@ const resolvers = { currentUserOwnsThis: async ( { id }, _, - { currentUserId, userClosetHangersLoader } + { currentUserId, userItemClosetHangersLoader } ) => { if (currentUserId == null) return false; - const closetHangers = await userClosetHangersLoader.load(currentUserId); - return closetHangers.some((h) => h.itemId === id && h.owned); + const closetHangers = await userItemClosetHangersLoader.load({ + userId: currentUserId, + itemId: id, + }); + return closetHangers.some((h) => h.owned); }, currentUserWantsThis: async ( { id }, _, - { currentUserId, userClosetHangersLoader } + { currentUserId, userItemClosetHangersLoader } ) => { if (currentUserId == null) return false; - const closetHangers = await userClosetHangersLoader.load(currentUserId); - return closetHangers.some((h) => h.itemId === id && !h.owned); + const closetHangers = await userItemClosetHangersLoader.load({ + userId: currentUserId, + itemId: id, + }); + return closetHangers.some((h) => !h.owned); }, currentUserHasInLists: async ( { id }, _, - { currentUserId, userClosetHangersLoader } + { currentUserId, userItemClosetHangersLoader } ) => { if (currentUserId == null) return false; - const closetHangers = await userClosetHangersLoader.load(currentUserId); - const itemHangers = closetHangers.filter((h) => h.itemId === id); - const listRefs = itemHangers.map((hanger) => { + const closetHangers = await userItemClosetHangersLoader.load({ + userId: currentUserId, + itemId: id, + }); + const listRefs = closetHangers.map((hanger) => { if (hanger.listId) { return { id: hanger.listId }; } else {