From 3b667001cd060a2c554603a6f632d71ee00d9ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Thu, 2 Apr 2026 13:37:12 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20N+1=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cardset/application/cardset.use-case.ts | 68 +++++++++---------- .../infrastructure/grpc/group-grpc.client.ts | 10 +++ src/proto/group.proto | 24 ++++++- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/cardset/application/cardset.use-case.ts b/src/cardset/application/cardset.use-case.ts index ce3bef3..1e51ed4 100644 --- a/src/cardset/application/cardset.use-case.ts +++ b/src/cardset/application/cardset.use-case.ts @@ -161,24 +161,22 @@ export class CardsetUseCase { category: req.category, }); - const visibleCardsets: Cardset[] = []; - for (const cardset of items) { - if (cardset.visibility === Visibility.PUBLIC) { - visibleCardsets.push(cardset); - continue; - } - if (isNaN(userId)) continue; + const hasPrivate = items.some((c) => c.visibility !== Visibility.PUBLIC); + let myGroupIds = new Set(); + if (hasPrivate && !isNaN(userId)) { try { - const inGroup = await this.groupGrpcClient.isUserInGroup( - cardset.groupId, - userId, + myGroupIds = await this.groupGrpcClient.getMyGroupIds(userId); + } catch (err) { + this.logger.error( + `[findAllPaged] 그룹 목록 조회 실패 (userId=${userId}): ${err instanceof Error ? err.message : String(err)}`, ); - if (inGroup) visibleCardsets.push(cardset); - } catch { - // 그룹 조회 실패 시 해당 카드셋 제외 } } + const visibleCardsets = items.filter( + (c) => c.visibility === Visibility.PUBLIC || myGroupIds.has(c.groupId), + ); + const ids = visibleCardsets.map((c) => c.id); if (ids.length === 0) { @@ -237,23 +235,20 @@ export class CardsetUseCase { }[] > { const cardsets = await this.cardsetRepository.findAll(); - const visibleCardsets: Cardset[] = []; - for (const cardset of cardsets) { - if (cardset.visibility === Visibility.PUBLIC) { - visibleCardsets.push(cardset); - continue; - } - if (isNaN(userId)) continue; + const hasPrivate = cardsets.some((c) => c.visibility !== Visibility.PUBLIC); + let myGroupIds = new Set(); + if (hasPrivate && !isNaN(userId)) { try { - const inGroup = await this.groupGrpcClient.isUserInGroup( - cardset.groupId, - userId, + myGroupIds = await this.groupGrpcClient.getMyGroupIds(userId); + } catch (err) { + this.logger.error( + `[findAll] 그룹 목록 조회 실패 (userId=${userId}): ${err instanceof Error ? err.message : String(err)}`, ); - if (inGroup) visibleCardsets.push(cardset); - } catch { - // 그룹 조회 실패 시 해당 카드셋 제외 } } + const visibleCardsets = cardsets.filter( + (c) => c.visibility === Visibility.PUBLIC || myGroupIds.has(c.groupId), + ); const ids = visibleCardsets.map((c) => c.id); const [metadataMap, likedMap, bookmarkedMap, managersMap] = @@ -536,19 +531,20 @@ export class CardsetUseCase { userId: number, ): Promise { const cardsets = await this.cardsetRepository.findByIds(cardSetIds); - const viewable: Cardset[] = []; - for (const cardset of cardsets) { - if (cardset.visibility === Visibility.PUBLIC) { - viewable.push(cardset); - } else { - const inGroup = await this.groupGrpcClient.isUserInGroup( - cardset.groupId, - userId, + const hasPrivate = cardsets.some((c) => c.visibility !== Visibility.PUBLIC); + let myGroupIds = new Set(); + if (hasPrivate && !isNaN(userId)) { + try { + myGroupIds = await this.groupGrpcClient.getMyGroupIds(userId); + } catch (err) { + this.logger.error( + `[getCardSetsByIds] 그룹 목록 조회 실패 (userId=${userId}): ${err instanceof Error ? err.message : String(err)}`, ); - if (inGroup) viewable.push(cardset); } } - return viewable; + return cardsets.filter( + (c) => c.visibility === Visibility.PUBLIC || myGroupIds.has(c.groupId), + ); } async updateCardCount( diff --git a/src/cardset/infrastructure/grpc/group-grpc.client.ts b/src/cardset/infrastructure/grpc/group-grpc.client.ts index 8b9ccdb..97148ed 100644 --- a/src/cardset/infrastructure/grpc/group-grpc.client.ts +++ b/src/cardset/infrastructure/grpc/group-grpc.client.ts @@ -9,6 +9,7 @@ interface GroupCommandService { groupId: number; userId: number; }): Observable<{ exists: boolean }>; + getMyGroup(data: { userId: number }): Observable<{ groupId: number[] }>; } @Injectable() @@ -40,4 +41,13 @@ export class GroupGrpcClient implements OnModuleInit { ); return result.exists; } + + async getMyGroupIds(userId: number): Promise> { + console.log('[getMyGroupIds] request userId:', userId); + const result = await firstValueFrom( + this.groupService.getMyGroup({ userId }), + ); + console.log('[getMyGroupIds] raw result:', JSON.stringify(result)); + return new Set((result.groupId ?? []).map(Number)); + } } diff --git a/src/proto/group.proto b/src/proto/group.proto index 329997a..309ccd0 100644 --- a/src/proto/group.proto +++ b/src/proto/group.proto @@ -2,9 +2,21 @@ syntax = "proto3"; package group.v1; + +option java_multiple_files = true; +option java_package = "flipnote.group.grpc.v1"; +option java_outer_classname = "GroupServiceProto"; + service GroupCommandService { - rpc GetGroupName (GetGroupNameRequest) returns (GetGroupNameResponse); - rpc CheckUserInGroup (CheckUserInGroupRequest) returns (CheckUserInGroupResponse); + + // 그룹 이름 조회 + rpc GetGroupName(GetGroupNameRequest) returns (GetGroupNameResponse); + + // 그룹 내 유저 존재 여부 확인 + rpc CheckUserInGroup(CheckUserInGroupRequest) returns (CheckUserInGroupResponse); + + // 내 그룹 전체 조회 + rpc GetMyGroup(GetMyGroupRequest) returns (GetMyGroupResponse); } message GetGroupNameRequest { @@ -23,3 +35,11 @@ message CheckUserInGroupRequest { message CheckUserInGroupResponse { bool exists = 1; } + +message GetMyGroupRequest { + int64 user_id = 1; +} + +message GetMyGroupResponse { + repeated int64 group_id = 1; +}