오픈 15분 전, CMS가 안된다고 했다
TL;DR
- api를 호출하는 범위는 최대한 좁게 걸자.
- 해당 엔드 포인트의 목적이 무엇인지 분명하게 파악하고 그에 맞는 최적의 방법을 고민해보자.
사내 고객센터는 10시에 열리는데, 오픈 15분 전인 9시 45분에 CMS가 전체적으로 느리고 안되는 것 같다는 글이 올라왔다.
이전에도 비슷한 상황이 있었는데, 캐시를 삭제하고 재로그인하면 해결됐던 방법이 이번에는 먹히지 않았다. 이 때까지만 해도 뭐가 문제일까 싶었는데..
주변 동료가 찾아보더니 DB에 과부하가 걸린 것 같다고 했다. 좀 이따 Latency가 튄 것을 확인하면서 전 날 진행했던 릴리즈에 문제가 있는 것 같다고 했다.
어, 그건 내가 짠 코드인데?
어제 릴리즈한, 내가 구현한 기능은 두 가지였다.
- 카테고리 내에 존재하는 쿠폰의 개수를 반환하는 GET api
- 카테고리를 삭제하는 버튼을 클릭할 때, 1번의 반환값(쿠폰 개수)을 화면에 보여주기
Datadog에서 전 날 릴리즈한 시점부터 현재 시간까지의 로그를 확인해보았다.
역시나 어제 릴리즈한 코드에 문제가 있었다. Total time으로 정렬했을 때, 내가 짠 코드의 총 소요 시간은 4분 중반대에 P95 latency가 3.97s, 거의 4초가 걸렸다.
P95란?
좀 더 자세하게 보기 위해 해당 엔드포인트를 클릭했다.
latency가 튀는 것뿐만 아니라 request 횟수가 지나치게 늘어났다. 오른쪽 아래 ‘% of Time spent’ 그래프를 보면, 오전 9시 30분쯤 api-mysql 폭이 갑자기 상승한 걸 볼 수 있다.
Request 횟수가 증가함에 따라 latency 튀면서 CMS 전반에 영향을 미친 것이다.
어느 정도 파악을 했으니 코드를 보자
- request 횟수가 지나치게 많다.
- 카테고리 아이디를 클릭하면 카테고리의 상세 정보와 수정, 삭제하는 기능이 담겨있는 팝오버가 열린다.
- 팝오버 내 삭제 버튼을 클릭하면, 카테고리 내에 존재하는 쿠폰의 개수를 보여준다.
- 해당 api는 카테고리 모달을 누를 때마다 호출되어 비정상적인 request가 많았다. 즉, 중복 요청이 발생한 것이다.
2. 쿠폰의 개수를 세는 방법
- 삭제 버튼 클릭시 호출하는 api는 카테고리 내에 있는 쿠폰의 개수를 response한다.
- 기존의 코드는 findAll로 해당 조건(쿠폰 카테고리 아이디)에 맞는 모든 쿠폰을 찾고, 결과값의 length를 반환한다.
- 개수만을 위한 엔드포인트라면 count 함수를 사용하는 것이 훨씬 빠르다.
어디가 문제인지 고쳐야지?
사실 이 문제는 빠르게 해결해야했기 때문에, 해당 프로젝트 오너 분이 수정하시고 릴리즈하셨다.
그렇다고 이대로 지나칠 수는 없지.
- 엔드포인트를 호출하는 조건을 수정하자.
- 기존 코드에서 enabled에 걸린 조건은 ‘카테고리 아이디를 클릭했을 때, 즉 팝오버가 열릴 때’였다.
- 카테고리를 누를 때마다가 아닌 카테고리 삭제 버튼을 눌렀을 때 해당 엔드포인트를 호출한다.
useQuery(
couponCategoryKey.couponCount(currentCouponCategoryId),
() => fetchCouponCategoryCouponCount(currentCouponCategoryId),
{
enabled: isDeletePopoverOpen,
},
)
2. findAll – length 대신 count 함수를 사용하자.
const couponCount = await Coupon.count({
attributes: [‘id’],
where: {
couponCategoryId: req.params.couponCategoryId,
},
})
return couponCount
에러 원인을 찾는건 보물 찾기같다