Keep: 좋았던 부분. 유지할 것
Problem: 문제였던 부분. 버려야 할 것
Try: 시도해볼 부분
KPT라는 회고 방법이 있다고 한다. 이번 고카톤 개발을 진행하면서 느꼈던 점을 정리하여 문제점과 개선방향을 찾아보려고 한다......
고카톤 운영진 참여하게 된 계기
과눈이 덕분에 갑자기... 참여하게 되었다. 과눈이덕에 새로운 경험을 많이 하게 되는 것 같다
고카톤 개발
해커톤이라 하믄 보통 한 공간에서 모두가 무박으로 밤을 새며 개발하며 성과물을 만드는 대회이다. 하지만 올해는 코로나로 인해 언텍트로 진행되었고 각자 집에서 줌으로 개발을 할 수 밖에 없었다.
집에서 각자 개발을 하면 다른 팀의 진행상황과 아이디어를 보기가 어려우므로 슬랙과 유사하게 생긴 대시보드를 만들기로 했다.
대시보드의 주요 기능은 팀단위로 로그인하여 자신들의 진행상황을 글과 사진으로 공유하고 서로 댓글을 남겨 아이디어를 공유하는 것이었다.
나는 백엔드를 맡았고 코틀린 + Spring Boot를 사용하여 2주동안 자가격리하면서 개발을 했다...ㅠ
스키마가 단순해서 양이 많진 않다고 느꼈지만 코틀린을 처음 해보고 스프링 부트에 대한 이해가 부족해서 에러가 많았다. 이 때 작동만 되면 되지^^ 하고 넘어갔던 코드들이 문제가 되어 고카톤 당일 서버가 터졌다 ㅎㅎㅎㅎ
프론트엔드와 메세지큐를 현채와 과눈이가 구현했는데 정말 둘다 대단하다,,
디자이너도 고생했고 나의 코드로 고통받았을 현채와 과눈..미안....
너무 예쁘다
고카톤 당일
1월 9일 ~ 10일 고카톤이 진행되었다. 운영진은 생각보다 할일이 없었다.
그래서 에러 발생시 slack 개발팀 채널에 알림이 오게끔 설정을 했다. 차후 정리 예정
이것도 삽질을 많이 했다.
문제는 밤에 발생했는데 첨에는 정상 작동 -> 점차 느려진 서버가 아예 터졌다는 것이다....!
여기부터 진짜
slack에 온 에러 메세지는 아래와 같았다.
급하게 수홍이에게 sos를 보냈다.
Unable to acqurie JDBC Connection 메세지를 본 수홍이는 connection pool 부족이 원인인 것 같다고 했다.
그래서
1. db에 접속
2. max connection pool 확인 151개였음
3. 그 절반인 70개를 max connection pool로 설정함
application.properties에
를 추가했다.
이렇게 하니까 커넥션 풀 오류는 사라졌다...갓수홍....
분석 과정은 이렇다.
1. 스프링은 사용자 요청을 처리하는 thread가 미리 만들어져 있다. 이것을 Thread Pool이라고 한다.
(쓰레드가 우리가 만든 코드를 작동시키고 결과값을 리턴한다.)
2. 이 쓰레드가 DB에서 데이터를 가져와야하는데 DB에 데이터를 날릴 때 connection이라는게 필요하다.
즉, connection을 맺어야 DB와 데이터를 주고받을 수 있다.
3. 하지만 쓰레드에는 커넥션이 처음부터 존재하는게 아니다. 쿼리를 날릴 때마다 커넥션을 맺고 날린 후에는 커넥션을 종료한다.
4. 그런데 이 Connection 객체를 만드는 과정이 상당히 많은 시간이 소요된다. 한 쓰레드가 한 커넥션씩 만든다면 대기 시간과 커넥션 객체 생성 과정이 상당히 오래 소요될 것이다.
5. 따라서 대기 시간을 줄이고 비용과 네트워크 부담을 줄이기 위한 방법으로 DBCP가 있다. 바로 DB Connection Pool이다.
Connection Pool이란 커넥션 객체를 여러 개 만들어서 pool에 저장해두는 것이다. 그 다음에 http 요청에 따라 pool에서 커넥션을 꺼내 쓰고 사용 후 반납한다.
물론 이 커넥션 풀도 무제한은 아니고 우리 DB의 경우 151개였다. 스프링에서 커넥션 풀을 만들 때 default값이 10개이다.
6. 쓰레드는 쿼리 결과가 나올 때 까지 커넥션을 잡아두는데 쿼리가 느리니까 요청이 밀려서 사용가능한 커넥션 풀이 모두 소진된 상황이었다. 그래서 뒤에 커넥션을 받으려는 쓰레드들이 대기타다가 터져버린 것...그래서 500에러가 떴다.
그래서 일단 커넥션 풀을 70개로 늘렸다.
하지만 여전히 api 속도가 상당히 느렸는데,,, 프론트엔드에서의 문제를 해결했는데도 api 처리 속도가 매우 느렸다.
데이터가 아마 총 200개가 안됐을텐데 api 처리 속도가 몹시 느린 것에서 이상함을 느꼈고
원인을 쿼리라고 추측했다.
그래서 slow query log를 사용해서 디비에 slow query를 남겼다.
그랬더니 엄청난 수의 쿼리가 날라간 것을 확인할 수 있었다.
쿼리가 그 정도 날아갔으니 상당히 느릴 수 밖에 없었고 문제는 쿼리의 수가 데이터 수에 비해 과하게 많다는 것이었다.
원인은 처음에 스프링에서 엔티티를 만들때 걸었던 조인 칼럼들이었다.
엔티티를 만들 때 team에 one to many에 fetch타입을 전부 EAGER로 한 것이 문제였다. 거기다 칼럼 정렬까지 걸어놨다. ㅎㅎㅎㅎ 쿼리가 미친듯이 날아갈만 하다.
이 모든 걸 알아낸 갓수홍 찬양해
eager는 즉시 로딩으로 연관된 엔티티를 모두 불러온다.
정말 필요한 상황을 제외하고는 지연 로딩 lazy를 사용하라고 한다..
예를 들어 eager를 사용하면
멤버를 조회하려는데 멤버 조회 -> 팀이 비어서 각 팀을 또 조회 -> .. 이렇게 된다.
team 각 칼럼에 전부 eager를 걸어놨더니 유저, 투두, 커멘트 전부 다 조회된 것 같다.
팀에 걸린 조인 칼럼을 전부 lazy로 바꾸고 정렬을 지웠더니 대충 해결됐다.
추가로 알게 된 사실은
- 죽을 때 까지 1000개 안넘어가는 데이터에만 findAll사용하기
- manytoone만 걸지 onetomany는 지양
- 데이터 조회 시 paging을 사용한다
였다.
매번 로컬에서 작동하는 코드만 짜다보니 대처 능력이 부족했다. 정말 많은 경험을 쌓아야겠다.
100명 정도의 적은 유저가 사용하는 서비스였지만 막상 서버가 터지니까 심장떨렸다. 이게 대고객 서비스였으면...
그치만 해결하는 과정이 색다른 경험이고 재밌었다ㅎㅎㅎㅎㅎ늦은 밤 수고해준 수홍이에게 매우 고맙고 현채와 관훈이도 고생을 너무 많이 했다 ㅠㅠ
Try:
JPA 공부를 제대로 해야 겠다.
인프런 강의 들어야겠다.
공부할 개념: LAZY VS EAGER / slow query / thread pool / connection pool / 슬랙 로깅 정리
/ JPA N+1문제
댓글