-
Notifications
You must be signed in to change notification settings - Fork 1
부하테스트
이지호 edited this page Jan 25, 2025
·
6 revisions
-
ask-it.site
는 실시간 Q&A 서비스를 제공하고 있으며, 네이버 부스트캠프 마스터클래스 인원 수용을 목표로 동시 200명 이상의 사용자를 안정적으로 지원하는 것을 목표로 삼았습니다. - 본 문서는 API, 채팅 등 200명 수용을 위한 다양한 부하 테스트 과정과 결과, 그리고 테스트를 통해 확인된 개선점을 다룹니다.
- 구체적인 부하테스트 시나리오 및 결과 보고서는 본 레포지토리에서 확인하실 수 있습니다.
- 부하테스트의 목표
- 한 번에 200명의 동시 사용자가 접속했을 때, 서비스가 시간 초과나 오류 없이 정상적으로 동작하는지 확인하는 것이었습니다:
- 질문 조회, 질문 새성, 답변 생성, 좋아요 토글 등의 API 서버의 기능이 200명을 수용할 수 있는지 확인합니다.
- 채팅 생성이라는 WebSocket 서버의 기능이 200명을 수용할 수 있는지 확인합니다.
- 한 번에 200명의 동시 사용자가 접속했을 때, 서비스가 시간 초과나 오류 없이 정상적으로 동작하는지 확인하는 것이었습니다:
- 서버 스펙
- 네이버 클라우드 플랫폼(NCP) VPC 서버: s2-g2-s50
- vCPU: 2EA
- Memory: 8GB
- [SSD] Disk: 50GB
- 네이버 클라우드 플랫폼(NCP) VPC 서버: s2-g2-s50
- 아키텍처 구조도
- Public Subnet: Nginx, NestJS 서버(REST API, Socket.IO)
-
Private Subnet: PostgreSQL DB
부하 테스트 도구를 선정할 때 다음과 같은 요구사항이 있었습니다:
-
성능 테스트와 개선에 집중
- 도구 학습보다는 실제 성능 개선에 시간을 투자하고 싶었습니다. 때문에 러닝 커브가 낮은 도구를 사용하고 싶었습니다.
-
명확한 목표: 200명 동시 접속 검증
- REST API 서버와 Socket.IO 서버 각각의 성능을 분리하여 측정하고 싶었습니다.
- 특히 Socket.IO 기반 실시간 통신의 안정성 검증이 중요했습니다.
-
테스트의 재현성과 버전 관리
- 시나리오를 코드로 관리하여 버전 관리가 가능해야 했습니다.
- 추후 이 시나리오는 캐싱 등의 성능 개선 전후를 수치화 하는 데에도 사용되어야 했습니다.
이러한 요구사항을 바탕으로 JMeter, K6, Artillery를 검토했습니다.
- JMeter는 GUI 기반으로 테스트를 구성할 수 있었지만, Socket.IO 지원이 미흡했고 시나리오의 버전 관리가 불편했습니다.
- K6는 코드 기반으로 테스트를 작성할 수 있어 매력적이었지만, Socket.IO를 지원하지 않아 추가 개발이 필요했습니다.
Artillery를 최종 선택한 이유는 다음과 같습니다:
-
Socket.IO 지원
- Artillery는 Socket.IO를 공식적으로 지원합니다.
- Socket.IO 공식문서의 Load Testing 페이지에 Artillery가 소개되어 있어 신뢰성이 높았습니다.
- processor.js를 통해 Socket.IO 클라이언트를 직접 활용할 수 있어, 다양한 시나리오 구성이 가능했습니다.
-
실용적인 테스트 작성
- YAML과 JavaScript를 통해 테스트 시나리오를 코드로 관리할 수 있었습니다.
- Git으로 버전 관리가 가능해 성능 개선 전후 비교가 용이했습니다.
-
낮은 러닝 커브
- YAML 파일로 간단한 시나리오를 빠르게 작성할 수 있었습니다.
- JavaScript 기반이라 node.js를 사용하는 우리 팀에겐 익숙한 작업 환경이었습니다.
200명의 목표 수용을 검증하기 위해 실제 사용자의 행동 패턴을 반영한 시나리오를 구성했습니다:
- 시나리오 가중치
- 질문 목록 조회: 35%
- 질문 작성: 30%
- 답변 작성: 20%
- 좋아요: 15%
- 실제 사용 패턴 반영
- 각 작업 사이 현실적인 대기 시간 추가 (1-10초)
- 5분간 지속적인 부하 발생
- 초당 평균 40개 요청 처리
채팅의 특성상 일반 API보다 더 많은 부하가 예상되어 한계치까지 검증을 진행했습니다.
테스트 시나리오는 다음과 같이 구성되었습니다:
- 테스트 기간: 총 180초 (3분)
- 사용자 증가 패턴:
- 초기 접속률: 초당 2명
- 최대 접속률: 초당 6명까지 점진적 증가
- 최종 목표: 총 720명의 사용자 생성
- 사용자 행동 패턴:
- API 요청을 통한 인증 토큰 획득 (getToken)
- WebSocket 연결 수립 (createWSConnection)
- 2초간 대기
- 채팅 메시지 전송 15회 반복
- 각 메시지 전송 후 0.1초 대기
- 랜덤한 문장으로 구성된 메시지 전송
-
API 서버: 200명 동시 접속자의 질문 조회/작성, 답변 작성, 좋아요 요청 처리 결과
- 99.3%의 높은 요청 성공률 (560/564 요청)
- 질문 목록 조회 API의 경우 요청 수신부터 응답까지 평균 44.3밀리초 소요
- 답변 작성 API의 경우 요청 수신부터 응답까지 평균 34.2밀리초 소요
- 좋아요 API의 경우 요청 수신부터 응답까지 평균 28.6밀리초 소요
- 모든 종류의 API 요청 중 95%가 106.7밀리초 이내 처리 완료
-
Socket.IO 서버: 실시간 채팅 성능 검증 결과
- 사용자별 테스트 시나리오:
- API 요청으로 인증 토큰 발급
- WebSocket 연결 수립
- 연결 성공 후 2초 대기 (시나리오 설계값)
- 15회의 채팅 메시지 전송
- 메시지 전송 간격: 0.3~0.7초 (시나리오 설계값)
- 메시지 전송 후 0.1초 대기 (시나리오 설계값)
- 500명까지 안정적 운영
- 총 테스트 시간: 평균 13초 (의도된 대기 시간 포함)
- 토큰 발급 및 연결 수립: 즉시 처리
- 시나리오 설계상 대기 시간: 약 11초
- 연결 후 대기: 2초
- 메시지 전송 간 대기: 약 9초 (15회 × 평균 0.6초)
- 실제 메시지 전송-수신 지연: 1초 미만
- WebSocket 연결 실패율 0% 달성
- 사용자별 테스트 시나리오:
-
API 서버 한계치: 질문 조회/작성, 답변 작성, 좋아요 요청에 대해
- 처리량: 초당 40개 요청까지 안정적 처리 가능
- 모든 종류의 API 요청에 대해 최대 응답 시간 186밀리초
- 병목 구간: 좋아요 기능의 동시성 처리
-
Socket.IO 서버 한계치: 실시간 채팅 시나리오 검증 결과
- ~500명: 이상적 성능
- 실제 메시지 전송-수신 지연: 1초 미만
- 총 테스트 시간: 평균 13초 (시나리오 설계상 대기시간 11초 포함)
- ~600명: 성능 저하 시작
- 실제 메시지 전송-수신 지연: 2-3초로 증가
- 총 테스트 시간: 평균 14초 (시나리오 설계상 대기시간 11초 포함)
- 600명 이상: 급격한 성능 저하
- 실제 메시지 전송-수신 지연: 최대 17초까지 증가
- 총 테스트 시간: 최대 28초 (시나리오 설계상 대기시간 11초 포함)
- ~500명: 이상적 성능
-
좋아요(Like) 기능 동시성 처리
-
현상
- 200명 부하 테스트 중 좋아요 기능에서 4건의 500 에러 발생
- "Record to delete does not exist" 에러 로그 확인
-
원인 분석
- 동일 유저가 같은 게시글에 대해 빠르게 여러 번 좋아요를 토글할 때 Race Condition 발생
async toggleLike(questionId: number, userToken: string) { const exist = await this.questionRepository.findLike(questionId, userToken); if (exist) await this.questionRepository.deleteLike(exist.id); else await this.questionRepository.createLike(questionId, userToken); return { liked: !exist }; }
- findLike로 조회한 시점과 실제 deleteLike/createLike가 실행되는 시점 사이의 시간 차로 인해 데이터 정합성이 깨짐
- 더 강한 부하테스트 진행 시, 다음과 같이 같은 유저가 같은 질문에 여러 번 좋아요를 누른 현상이 발견됨
-
해결 방안
- DB에 QuestionID + UserID 복합키로 UNIQUE 제약 추가
- 이를 통해 동일 사용자가 같은 게시글에 중복으로 좋아요를 누르는 것을 DB 레벨에서 방지 가능
-
-
인증/세션 검증 로직 최적화
-
현재 상황
-
매 요청마다 DB 조회를 통한 토큰 유효성 검증
@UseGuards(SessionTokenValidationGuard, QuestionExistenceGuard) // ...
-
-
문제점
- 요청이 몰릴 때 DB 자원 소비가 커져 응답 지연 발생 가능
-
해결 방안
- 토큰 캐싱 (TTL 적용)
- LRU 캐시로 자주 쓰이는 토큰 정보를 메모리에 유지
- 캐시 미스 시에만 DB 조회
-