📚 프로젝트 소개
- 지금까지 겪었던 다양한 에러나 문제 상황, 효율성 증대 방법들을 정리하기 위해 만들었습니다.
- 반려동물 커뮤니티 서비스 입니다.
📂 서비스 주요 기능
- 게시글 CRUD
- 게시글 목록 조회
- 제목 검색 동적 쿼리
- 본인이 작성한 게시글 목록 조회
- 본인이 좋아요한 게시글 목록 조회
- 댓글 CRUD
- 대댓글 CRUD
- 게시글 '좋아요' 하기
- 게시글 '좋아요' 취소
- OAuth2 + Jwt + Spring Security
- 소셜 로그인
- 채팅 기능 (구현 예정)
🔎 목차
- nGrinder를 사용한 부하 테스트
- 인덱스를 사용한 쿼리 성능 개선
- N+1 문제 해결
- QueryDSL을 사용한 동적 쿼리 기능
- OAuth2 + JWT 기반 로그인 기능
- 좋아요 기능 동시성 제어
- 단위 테스트 작성
1. nGrinder
- nGrinder는 네이버에서 제공하는 서버 부하 테스트 오픈 소스 프로젝트입니다.
Controller: 성능 측정을 위한 웹 인터페이스를 제공하며, Web Application으로 Tomcat과 같은 웹서버 엔진을 이용하여 구동할 수 있습니다.Agent: Controller의 명령을 받아 실행하며, target에 프로세스와 스레드를 실행시켜 부하를 발생시킵니다.Target: 테스트하려는 target 애플리케이션을 의미합니다.
2. 같은 환경에서 테스트하기
Agent: 성능 측정에 사용할 Agent 개수.- 일반적인 로컬에서 테스트를 실행할 경우 1이 고정값
- Agent를 여러개로 구성하고 싶은 경우 Docker 나 cloud service 를 사용해 동시적으로 실행시켜야 합니다.
Vuser per agent: 동시에 요청을 날리는 사용자 수Process / Thread: 한 Agent 에서 생성할 프로세스와 스레드 수Run Count / Duration: Run Count 와 Duration 중 선택하여 얼마나 오래, 많이 테스트를 실행할 것인지 정합니다.Ramp-up: 점진적으로 부하를 테스트 할 때 사용합니다.
3. 부하 테스트 지표
TPS: 일정 시간동안 얼마나 많은 요청을 처리할수 있는지를 나타내는 지표로 성능 테스팅의 주요 지표로 활용됩니다.- TPS 가 높기만 해서 성능 좋은 서버를 의미하는 것은 아닙니다.
- 사용자가 늘어나면서 TPS 가 높아지다 한계를 만나면 거의 증가하지 않는 그래프 특성을 띄는 것이 가장 이상적입니다.
Mean Test Time: 평균 테스트 시간- 사용자가 늘어나면서 조금씩 높아지다 특정 지점에서 급증하는 그래프의 형태를 갖습니다.
- 부하 테스트 지표를 분석할 때에는 사용자, TPS, Time 의 관계를 함께 고려하는 것이 좋습니다.
3. 주의할 점
- nGrinder 를 사용하는 방법에는 깃허브 릴리즈 페이지에서 다운로드 받는 방법과 도커로 설치하는 방법이 있습니다.
- 저는 도커를 사용했는데 이때 nGrinder 의 localhost(127.0.0.1)는 내 노트북 ip 가 아닌 도커의 ip 를 의미합니다. nGrinder 가 도커 위에서 실행되고 있기 때문입니다.
- 스크립트 작성 시 api 를 호출하는 코드를 작성하게 되는데 이때 ip 를 노트북의 ip 주소를 입력해야
Connection Refused가 일어나지 않습니다.
1. 쿼리 성능 개선
- 데이터베이스에서 인덱스(Index)를 사용하는 주된 목적은 쿼리의 성능을 개선하는 것입니다.
- 인덱스는 데이터베이스 테이블의 특정 열(들)에 대해 생성되는 데이터 구조로, 빠른 데이터 검색과 검색 속도를 향상시키기 위해 사용됩니다.
- 조회 시 많이 사용되는 userId 에 인덱스를 생성하여 board 조회 api 를 기준으로 부하테스트를 진행했습니다.
1. N+1 문제
- 한 번에 여러개의 select 문이 나가는 N+1 문제 발생
- 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(N) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 현상입니다.
- JPA가 JPQL을 분석해서 SQL을 생성할 때는 글로벌 Fetch 전략을 참고하지 않고 오직 JPQL 자체만을 사용하기 때문에 문제가 발생합니다.
2. N+1 문제 해결
-
Fetch 모드를 Lazy(지연로딩) 으로 하는 방법- N+1 문제가 일어나지 않은 것 처럼 보이지만 실제로 객체를 탐색했을 때 N+1 문제가 발생합니다.
-
Fetch join 사용- JPQL을 사용하여 DB에서 데이터를 가져올 때 처음부터 연관된 데이터까지 같이 가져오는 방법
- @Query 어노테이션을 사용해서
join fetch 엔티티.연관관계_엔티티구문을 작성 - Inner Join 구문으로 변경되어 실행되고 N+1 문제가 일어나지 않습니다
-
Batch Size- application.properties 에서 batch size 를 설정합니다.
- N+1 문제를 아예 안일어나게 하는 방법이 아닌 조회 횟수를 줄여 성능을 최적화하는 방법
- IN 절로 변경되어 실행됩니다.
- 대부분의 DB에서 IN 절의 최대 개수 값이 1000이라 기본적으로 1000 이하로 설정
1. QueryDSL
- QueryDSL은 정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해 주는 오픈소스 프레임워크입니다.
- 복잡한 로직의 쿼리문을 JPQL로 작성했을 때 오타나 문법적 오류가 생기면 컴파일 에러가 아닌 런타임 에러가 발생하게 됩니다.
- 이러한 문제를 어느 정도 해결해주는 것이 QueryDSL 입니다.
- 특히 동적 쿼리 생성에 장점을 가지고 있는데 저는 게시판 내 제목 검색을 구현과 댓글, 대댓글 관련 기능 구현할 때 QueryDSL을 사용했습니다.
2. QueryDSL 장점
타입 안정성: 문자가 아닌 코드로 쿼리를 작성함으로써, 컴파일 시점에 문법 오류를 쉽게 확인할 수 있습니다.코드 자동 완성: 자동 완성 등 IDE의 도움을 받을 수 있습니다.동적 쿼리: 동적인 쿼리 작성이 편리합니다.유지 보수: 쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용할 수 있습니다.
1. 단위 테스트
- 단위 테스트(Unit Test)는 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트입니다.
- 버그 조기 발견 : 단위 테스트는 개발 초기 단계에서 버그를 발견하고 수정할 수 있게 해줍니다. 이는 문제를 조기에 발견하고 수정함으로써 개발 비용과 시간을 절약할 수 있게 해줍니다.
- 코드 품질 향상 : 단위 테스트는 개발자가 더 깨끗하고 재사용 가능한 코드를 작성하도록 돕습니다. 테스트 가능한 코드는 일반적으로 더 좋은 설계 원칙을 따르며, 이는 전체적인 코드 품질의 향상으로 이어집니다.
- 리팩토링 용이 : 안정적인 단위 테스트 스위트가 있으면 개발자는 기존 코드를 리팩토링(코드 구조를 개선)하거나 새로운 기능을 추가할 때 더 자신감을 가지고 작업할 수 있습니다. 리팩토링 후에도 단위 테스트를 통해 기존 기능이 여전히 올바르게 작동하는지 확인할 수 있습니다.
- 이러한 테스트 코드의 중요성을 인식하고 Junit5와 Mockito를 사용하여 단위 테스트를 작성했습니다.
