From 7a30298cf61439a6d89bf83ad943932a03d2d5f7 Mon Sep 17 00:00:00 2001 From: hoseok Date: Mon, 11 Aug 2025 09:57:28 +0900 Subject: [PATCH] fix --- README.md | 35 +++++++++++++++++++ src/users/api/search.adjacent.users.query.ts | 13 +++++-- src/users/dto/search.users.filters.ts | 14 +------- .../interceptor/search.users.interceptor.ts | 19 +--------- src/users/users.controller.ts | 13 +++++-- 5 files changed, 58 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 942686f..e2982c5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,37 @@ # Comeet_BE [프로그래머스 풀스택 데브코스 7기] 게살버거팀 사이드 프로젝트_위치기반 개발자 커뮤니티 + +# 트러블 슈팅 경험 + +## 새로 빌드할 수록 서버 성능 저하 + +## 문제상황 +코드를 새로 배포할 수록, 빌드시간이 증가하여 종국에는 이미지 빌드에 23분이나 걸리게 됨 \ +또한 api 응답에 걸리는 시간도 10배 이상 증가함 + +### 관찰 1 +image를 새로 빌드할 때마다, CPU & Network 사용량이 급격하게 증가하는 것을 발견 + +### 해결 1 +github action을 이용하여 별도 서버에서 이미지를 빌드하여, docker hub에 올리고 \ +프로덕션 서버에서 빌드된 이미지를 pull 해서 쓰는 것으로 변경 +-> 빌드시간 3분 이내로 안정화됨, 그러나 api 성능 저하는 해결하지 못함 + +### 관찰2 + +``` shell +docker stats +``` +명령어를 통해 container의 자원 사용량을 발견 +-> 순간 Net/IO, Block/IO 항목이 할당량 보다 많은 자원을 소비하는 것을 알게 됨 + +### 해결2 +t2 micro 크래딧의 한계로 인한 성능저하로 판단하여, unlimited 모드로 설정함 \ +unlimited 모드: 자원 사용량이 limit을 초고할 경우 credit을 초과분 만큼 늘리고 후에 초과 사용량 만큼의 금액을 청구하는 방식 \ +응답시간 다시 이전과 같은 수준으로 감소함 + +### 결론 +docker를 통해 안정적인 배포를 하려면 t2 micro 보다 높은 사양 서버가 필요 \ +규모가 작은 프로젝트라먄 도커를 사용하지 않고 t2 micro에 배포하는 것도 합리적이어 보임 + + diff --git a/src/users/api/search.adjacent.users.query.ts b/src/users/api/search.adjacent.users.query.ts index 33d71df..2fd07f1 100644 --- a/src/users/api/search.adjacent.users.query.ts +++ b/src/users/api/search.adjacent.users.query.ts @@ -1,6 +1,13 @@ import { SearchAdjacentUsersDTO } from "../dto"; -import { OmitType } from "@nestjs/swagger"; +import { ApiProperty, OmitType } from "@nestjs/swagger"; +import { IsRange } from "../../utils"; +import { IsOptional } from "class-validator"; export class SearchAdjacentUsersQuery extends OmitType( - SearchAdjacentUsersDTO, ["id", "origin"] -) {} \ No newline at end of file + SearchAdjacentUsersDTO, ["id", "origin", "birthyear"] +) { + @IsRange() + @IsOptional() + @ApiProperty({ name: "age", type: "string", pattern: "^(\\d)-(\\d)$", required: false }) + age?: [number, number]; +} \ No newline at end of file diff --git a/src/users/dto/search.users.filters.ts b/src/users/dto/search.users.filters.ts index 675663f..acced3c 100644 --- a/src/users/dto/search.users.filters.ts +++ b/src/users/dto/search.users.filters.ts @@ -2,21 +2,9 @@ import { ArrayMinSize, IsInt, IsOptional } from "class-validator"; import { ApiProperty } from "@nestjs/swagger"; import { IsRange } from "../../utils"; import { RangeObject } from "../../utils/range"; -import { Expose, Transform } from "class-transformer"; +import { Transform } from "class-transformer"; export class SearchUsersFilters { - - @Transform(({ value }) => - value && RangeObject.fromRange( - value.map((age: number) => - new Date().getFullYear() - age + 1 - ).reverse() - ) - ) - @IsRange() - @IsOptional() - @Expose({ name: "age" }) - @ApiProperty({ name: "age", type: "string", pattern: "^(\\d)-(\\d)$", required: false }) birthyear?: RangeObject; @Transform(({ value }) => value && RangeObject.fromRange(value)) diff --git a/src/users/interceptor/search.users.interceptor.ts b/src/users/interceptor/search.users.interceptor.ts index 96fb67a..51f488a 100644 --- a/src/users/interceptor/search.users.interceptor.ts +++ b/src/users/interceptor/search.users.interceptor.ts @@ -2,7 +2,6 @@ import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nes import { from, map, mergeMap, Observable, toArray } from "rxjs"; import { SearchUserResult } from "../dto"; import { transformBirthyearToAge } from "./interceptor.internal"; -import type { Request } from "express"; type __ResponseT = { results: object[] }; @@ -14,25 +13,9 @@ export class SearchUsersInterceptor > { intercept( - ctx: ExecutionContext, + _: ExecutionContext, next: CallHandler ): Observable<__ResponseT> { - const req: Request = ctx.switchToHttp().getRequest(); - - if (req.query) { - const { age, ...rest } = req.query; - - if (age && typeof age === "string") { - - const birthyear = age - .split('-') - .reverse() - .join('-'); - - req.query = { birthyear, ...rest }; - } - } - return next.handle().pipe( mergeMap(data => from(data)), map(transformBirthyearToAge), diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 0eca4bd..7f7da68 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -32,6 +32,7 @@ import { UpdateUserBody } from "./api"; import { Coordinates } from "../common/geo"; +import { RangeObject } from "../utils/range"; @ApiTags("Users") @Controller('/api/users') @@ -87,8 +88,16 @@ export class UsersController { @Query() query: SearchAdjacentUsersQuery, ): Promise { + const { age, ...rest } = query; + + const birthyear = age && RangeObject.fromRange( + age.map(x => + new Date().getFullYear() - x + 1 + ) as [number, number] + ); + return this._searchUsersService - .searchAdjacentUsers({ id, origin, ...query }); + .searchAdjacentUsers({ id, origin, birthyear, ...rest }); } @Get("/search/hot") @@ -102,7 +111,7 @@ export class UsersController { .searchHotUsers(); } - @Get("/subscriptions") + @Get("/search/subscriptions") @ApiOperation({ summary: "구독중인 유저 검색" }) @ApiBearerAuth() @ApiOkResponse({ type: SearchUsersResponse })