현재 쿠폰을 등록하는 API의 TPS가 12로 매우 낮은 수치이다.
TPS
는 Transaction Per Second로, 초당 처리할 수 있는 트랜잭션을 의미한다.평균 테스트 시간도 요청부터 응답까지 약 58초가 걸리는 상황이라
정상적인 서비스가 가능하다 라고 보기 어려운 상태이다.
@Transactional
override fun getCommonCoupon(request : GetCouponRequest) : GetCouponResponse {
val userCheck = memberRepository.findByIdOrNull(/*userPrincipal.id*/ 2L)
?: throw IllegalArgumentException("Invalid Member")
val couponCheck = commonCouponRepository.findByCouponNumber(request.couponNumber)?.apply {
if (available && couponCount > useCount) {
memberId = userCheck
useCount += 1
} else {
throw IllegalArgumentException("사용할 수 없는 쿠폰입니다.")
}
} ?: throw IllegalArgumentException("쿠폰 번호가 틀렸습니다.")
commonCouponRepository.save(couponCheck)
return GetCouponResponse(couponNumber = couponCheck.couponNumber)
처음에는 DB에 접근하는 로직이 너무 많나? 하는 생각이 들었다.
(실제로 SELECT 쿼리가 4번이나 찍혔다)
쿼리를 줄여보자.
생각은 단순했다. UseCount를 가져와서 UseCount = UseCount + 1 을 한 다음
다시 저장하는 것 보다 Repository에서 직접 연산을 하면 적어도 한 단계는 줄어드니까
조금 더 빠를 것 같았다.
@Modifying
@Query("update CommonCouponEntity c set c.useCount = c.useCount + 1 where c.couponNumber = :couponNumber")
fun incUseCount(@Param("couponNumber") couponNumber : String) : Int
결과는 여전히 TPS 12
쿼리는 반드시 4개가 발생하는 것은 어쩔 수 없다는 것을 깨달아 버렸다.
User 정보의 일치 여부를 확인하고, Coupon의 유효성 검증을 위해서는 반드시 진행되어야 했다.
그렇다면, FetchJoin을 이용해서 1개의 쿼리로 합치는 것은 어떨까
override fun findByCouponNumberWithMember(couponNumber: String, memberId: Long): CommonCouponEntity {
return queryFactory
.selectFrom(coupon)
.join(coupon.memberId, member)
.fetchJoin()
.where(coupon.couponNumber.eq(couponNumber))
.fetchOne() ?: throw IllegalArgumentException("쿠폰 번호가 잘못되었습니다.")
}
QueryDSL을 사용해서 한 번의 쿼리로 필요한 모든 정보를 불러올 수 있도록 변경했다.
쿼리는 4번에서 1번으로 줄었지만, 여전히 TPS는 12로 조금도 줄지 않았다.
도대체 왜 TPS가 12에서 변동내역이 없을까.
내 코드에서 더 줄일 수 있는 방법을 찾지 못해 환경을 체크했다.
내가 하고있는 프로젝트는 PostgreSQL을 쓰는 Supabase로 선택했었다.
모든 팀원들이 가장 익숙하게 잘 다룰 수 있고,
무료로 제공하면서도 성능이 나쁘지 않다고 생각해 왔었다.
그러던 와중에 사실 가장 먼저 의심했어야 할 것이 supabase가 아닌가?
무료 요금제를 쓰면서 많은 처리를 기대한 것이 문제 아닐까? 싶었다.
show max_connections;
확인해 본 결과, max_connections는 60이다.
이 중 내가 보통 3~5개정도 connection을 잡아먹고 있고,
팀원이 3명에 노트북이 2대니 아무리 적게 잡아도 3~40connection은
기본적으로 할당이 되어있는 것 같다.
실제로 작업 중 reached max connections라는 에러를 종종 마주했었는데
신경을 안썼더니 더 크게 돌아와버린게 아닐까.
안그래도 생각했던 처리 방식을 구현하기 위해 Redis를 도입하려고 했었다.
local에서 redis를 통해 구현하고, 테스트하면 더 좋은 결과를 얻을 수 있지 않을까?
Redis를 설치하고, 도입해보면서 느낀 글은 다음 주제로 작성할 예정이다.
헉..
물론 무료DB에서 local Redis로 바뀐 만큼 차이가 엄청 클 것이라고 예상은 했지만
내 예상을 뛰어넘는 수치가 나왔다.
TPS 12 -> 2618
평균 테스트 시간도 비교가 안 될 만큼 줄었고, 그로 인해 실행 테스트도
훨씬 늘어났음에도 에러가 없이 테스트가 무사히 종료되었다.
사실 local DB에서 설치하고 테스트 한 것이 아닌 만큼,
이 테스트가 얼마나 부족한 테스트인지는 잘 알고있다.
주어진 방법에서 최선을 다해 구현해 보았고
힘들게 구현한 소득이 눈 앞에 테스트 결과로 나타나니
뿌듯함을 감출 수가 없었다.
재미있다.
너무 좋은 글이네요~ 잘 봤습니다^~^