사용자가 요청을 했는데 요청이 됐는지도 모르고 버튼을 두 번 눌러서 데이터가 두 번씩 들어가는 이슈가 있었다.
확인해보니 요청하고 응답이 올 때까지 생각보다 너무 오래 걸려서 (...) 그런 게 아닐까 싶었다.
프론트엔드에서도 요청중인 듯한 로딩 표시 버튼을 주기로 했지만
서버에서도 뭔가 조치를 취해야할 것 같았다!
해당 요청은 아래 프로세스를 거치고 있었다.
1. 요청을 받아 데이터를 DB 에 저장한 뒤
2. 해당 데이터를 구글 시트에 저장한다.
3. SSE 알림을 보내기.
처음엔 구글 시트에 저장하는 로직에서 이슈가 있는 게 아닐까 싶었다.
왜냐하면 구글 시트 저장 로직은 예전에 취소선이 추가되어 저장된다는 이슈가 있어서 확인해보니
추가되는 바로 상위 행에 스타일을 따라가기 때문에 상위 행에 취소선이 있으면 하위 행도 취소선이 자동으로 그어져서..
1. 행 추가
2. 추가한 행에 스타일 바꾸기
두 번의 요청을 하고 있었다.
그래서 사실은 이런 과정이 하나 추가된다.
1. 요청을 받아 데이터를 DB 에 저장한 뒤
2-1. 해당 데이터를 구글 시트에 저장한다.
2-2. 구글 시트에 저장한 행의 스타일을 기본형으로 변경한다.
3. SSE 알림을 보내기.
그래서 일단 구글 스프레드 시트 코드를 확인해보고 document 를 확인해보았다.
기존에는 INSERT_ROWS 방법으로 추가하고 있었는데
OVERWRITE 해서 덮어씌우면 왠지 기존 행의 스타일을 따라가지 않을 것 같아 데이터 삽입 방법을 바꿔보고
스타일 업데이트 구문을 주석처리 해보았다.
그랬더니 왠걸?
취소선이 그어지지 않고 데이터가 추가됨 ㅡ.ㅡ!!!!!!!!굿!!!!!!!ㅋㅋㅋㅎㅋㅎ
의기양양하게 postman 에서 api 테스트를 해보았는데.......큰 변화가 없었다.
다른 네트워크로 요청하는 게 2번에서 1번으로 줄었는데 왜 큰 차이가 없지???왜지??그럼 문제가 뭐지????
그래서 한번 SSE 코드를 주석처리해보았는데 속도가 확연히 빨라지는 게 아닌가??????????띠용?
그래서 SSE 코드를 뜯어보았더니... 쿼리가 20개씩 나가고 있었다.???
디비를 간단하게 설명해보겠다.
오늘은 삼겹살이 먹고 싶으니 삼겹살을 기준으로 설명을 하겠다.
멋진 고기테이블이 있다.
고기 종류가 있고 등급이 있다.
우리는 각 고기 등급에 따라 고기가 종류 별로 몇 인분이나 있는지 가져오면 된다.
예를 들어,
삼겹살 특상 10, 상 3, 중 2, 하 4
항정살 특상 5, 상 10, 중 1, 하 3 ...
그런데 기존 코드는
반복문으로 고기 종류에 따라 각 상태를 JpaRepository 로 야무지게 한번씩 조회하고 있었다.ㅎㅎㅎ
여기 예시로는 14번이겠지만 기존 구성으로는 총 20개의 쿼리가 나가고 있었다...! (오)
모두가 외롭지 않게 한번씩 조회하도록 개발하신 걸까...? 상냥해...
하지만 나 강휴일. 일할 때 만큼은 냉철하고 정없는 여자.
분명히 1번의 쿼리로도 충분히 조회할 수 있을 것 같았기에 코드를 수정해주기로 했다.
예전에 쿼리를 쓸 때 where 뿐만 아니라 select 부분에서도 조건을 줬던 적이 있던 것 같아 서치한 결과.
'CASE WHEN' 문을 사용하면 1번의 조회로도 각 인분(갯수)를 가져올 수 있을 것 같았다.
그래서 탄생한 쿼리는
SELECT
meat_name,
COUNT(CASE WHEN meat_rate = 'SPECIAL' THEN 1 END) AS special,
COUNT(CASE WHEN meat_rate = 'BEST' THEN 1 END) AS best,
COUNT(CASE WHEN meat_rate = 'GREAT' THEN 1 END) AS great,
COUNT(CASE WHEN meat_rate = 'GOOD' THEN 1 END) AS good,
COUNT(CASE WHEN meat_rate = 'NOT_BAD' THEN 1 END) AS not_bad
FROM meat
WHERE meat_name IN ('SAMGYUB', 'HANGJUNG', 'GABRISAL', 'MOKSAL')
GROUP BY meat_name
;
간단히 설명하면,
meat_rate 가 해당 값인 (해당 케이스가 true 인) 갯수를 가져와라 ! + 고기 이름도 가져와라 !
ㄴ 삼겹, 항정, 가브리살, 목살 내에서 가져와라!
meat_name 끼리 묶어서 가져와라!
하는 내용이다.
CASE WHEN 을 이용하면 해당 조건에 맞게 따로 select 할 수 있기 때문에 편리하다 ^ㅅ^
추가로 AS 로 별칭을 줬던 이유는 일단 쿼리 결과물 편하게 보려고.
이제 이 쿼리를 QueryDsl 구문으로 바꿔주었다.
public List<meatCountDto> getmeatCounts() {
return query.select(
Projections.constructor(meatCountDto.class,
meat.meatName,
meat.meatRate.when(meatRate.SPECIAL).then(1).otherwise(0).sum(),
meat.meatRate.when(meatRate.BEST).then(1).otherwise(0).sum(),
meat.meatRate.when(meatRate.GREAT).then(1).otherwise(0).sum(),
meat.meatRate.when(meatRate.GOOD).then(1).otherwise(0).sum(),
meat.meatRate.when(meatRate.NOT_BAD).then(1).otherwise(0).sum()
))
.from(meat)
.groupBy(meat.meatName)
.fetch();
}
QueryDsl 은 실제 쿼리를 코드로 사용할 수 있게 해놔서 아주 편리하당.
어쨌든 이렇게 쿼리를 한 번 날리는 것으로 수정하고 로컬에서 테스트 후, 서버에 배포 완료 !
결과는?
1.47s -> 886 ms 로 개선이 됐다 ㅎㅅㅎ/)
584 ms 줄은 셈!
저번 도커 이미지 캐싱 사용 이후로 두 번째 성능 개선,,,뿌듯,,
사실 저번에나 이번에나 별거 아닌 수정이었지만
별 거 아닌 걸 수정하더라도 큰 효과를 불러올 수 있다는 것을 알았다.
그리고 별 거 아닌 게 성능 저하의 원인일 수도 있으니 잘 살펴보자 라는 생각도 하게 됨 ㅋㅅㅋ
ORM 을 쓰면서 항상 느끼는 것은
쿼리를 모르면 ORM 을 "절대"!!! 제대로 사용할 수 없다는 것...
지금은 구글이랑 챗gpt 한테 굽신굽신 거리면서 물어보고 있지만
언젠가 물어보지 않고 직접 짤 날이 있겠지..ㅜ
지금 하고 있는 공부들 다 끝나면 sql 공부를 따로 해야겠다..
AWS 이전에 sql 공부를 하는 것이 더 맞을 거 같기도 하고 그렇다. 생각이 많아짐~
'개발 잡담' 카테고리의 다른 글
멀티 서버 Socket 통신에 Message Queue 사용하기 (0) | 2024.06.04 |
---|---|
CI/CD 도입..(with Jenkins) (0) | 2024.05.29 |
BDD 는 아는데 DDD는 무엇이냐 (0) | 2024.04.24 |
"IT 엔지니어를 위한 네트워크 입문"을 마치며 (0) | 2024.04.22 |
티스토리에 도메인을 연결했다 (0) | 2024.04.19 |