상황
현재, Spring으로 API 서버를 개발하는 프로젝트를 진행하고 있고, 카카오 API를 사용해 소셜 로그인을 구현하고 있습니다.
개발 환경은 IntelliJ IDE에서 Spring boot +JPA(Hibernate) + MySQL을 사용하고 있으며, 프론트와 연결하기 위해서 AWS의 EC2 인스턴스와 RDS를 띄워놓았습니다.
분명히, 어제 저녁까지만 해도 정상적으로 잘 동작하는 것을 확인하고 깃허브에 푸시까지 했습니다. 오늘 다시 서버를 동작시키니 접속이 안되는 상황이 발생했습니다. 아무것도 안했는데 에러가 발생한 것은 처음이라서 무척 당황했습니다.
AWS의 EC2 인스턴스, RDS 인스턴스가 모두 사라진 경우
연결이 안되니, 인스턴스가 중지되어 있는지 확인하기 위해 AWS에 접속하였습니다. 접속해서 확인해보니 인스턴스가 아예 없었습니다. 있다가 없으니까 너무 당황스러웠고, 프리티어가 끝난지 얼마 안되서, 자동 결제되는 줄 알았는데 삭제시키는거였나? 싶었습니다.
별에 별 생각 하면서 어떡하지? 싶었는데, 화면에 "이 리전에는 인스턴스가 존재하지 않습니다." 라고 되어 있는 것을 보고 지역을 확인했습니다. 지역이 서울이 아닌 버지니아 북부로 되어 있어서 인스턴스가 뜨지 않은 것이었습니다. 인스턴스들은 정상적으로 작동하고 있었습니다.
AWS홈페이지에 인스턴스가 나오지 않은 것은 선택된 지역의 문제였습니다.
CommunicationsException 예외 발생
다시 서버를 동작해서 확인해보니 CommunicationsException 예외가 발생했습니다. 딱 봐도 연결이 안되고 있는 문제같았습니다. 에러 로그를 통해 예상되는 원인은 Hikari(DBCP), DB Connection 정도가 있습니다. 구글링을 통해, 스프링과 MySQL을 사용할 때, 특정 시점이 지나면 자동으로 커넥션을 잃어버리는 문제가 있음을 알았습니다. 제대로 연결이 되어 있는지 확인하기 위해 설정 파일(.yml)을 확인했습니다.
//application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://엔드 포인트:3306/DB?validationQuery="select 1"?serverTimezone=Asia/Seoul
username: 이름
password: 비밀번호
url을 보시면, DB뒤에 파라미터가 붙는 것을 보실 수 있습니다. validationQuery="select 1"이라는 파라미터를 추가해서 위와 같은 문제를 해결할 수 있다고 합니다. validationQuery 대신 autoReconnect=true 도 방법이 될 수 있는데, 단순히 SqlException만 반환하고 재접속 처리를 한다고 합니다. 이렇게 되면, 기존에 처리하던 것이 남아있고 재접속 처리되는 문제가 발생합니다. 이 말은, 트랜잭션이 유지되지 못한다는 것이고, 데이터 무결성에 문제가 발생할 수 있습니다. (그래서 MySQL에서 권장하지 않는다고 합니다.)
*2022-04-10 수정 사항
HikariCP 개발자는 "select 1"과 같은 의미 없는 쿼리를 지양한다고 합니다. 그렇기 때문에, 쿼리를 보내서 연결을 유지하는 방법보다, 연결이 종료된 커넥션은 삭제하고 새로운 연결을 생성하는 것을 원칙으로 합니다. 연결된 커넥션을 한 번에 없애지 않고 종료된 커넥션만 삭제하는 매커니즘으로 작성되어 있습니다.
따라서, validationQuery="select 1", autoReconnect=true와 같은 옵션은 동작하지 않는다고 합니다.
그럼에도 불구하고 연결이 되지 않는다.
IDE의 문젠가 싶어서 Repair IDE도 해보고, 캐시도 지워봤지만 달라지는 것은 없었습니다. 처음 연동할 때 IntelliJ에 DataSource를 등록했던 것이 생각나서 확인해봤습니다.
우선, 설정과 URL은 문제가 없습니다. Test Connection을 진행하면 성공하는 것을 확인했습니다. 테스트도 정상적으로 작동하고, 동작을 하다가 안하니 도저히 무엇이 문제인지 전혀 감을 잡지 못하고 있었습니다.
max-lifetime 문제
CommunicationsException을 검색해보면 max-lifetime에 대한 내용이 많이 나옵니다. max-lifetime이란, DBCP에 생성된 커넥션이 IDLE상태로 얼마나 유지되는지에 대한 옵션입니다. MySQL과 Hikari의 max-lifetime의 시간이 맞지 않으면 해당 문제가 발생할 수 있습니다.
예를 들어, MySQL의 max-lifetime은 30분, HikariCP의 max-lifetime은 1시간이라고 가정한다면, 45분에 DB에 요청을 보내면 문제가 생길 수 있습니다. 왜냐하면, MySQL은 30분이 지나 커넥션을 종료했지만, HikariCP는 연결이 되어 있다고 판단하고, 종료된 커넥션에 연결을 요청하기 때문입니다. 따라서, MySQL의 max-lifetime은 HikariCP보다 길어야 합니다.
MySQL은 디폴트 값으로 28800(sec)이고, HikariCP의 경우 1800000(ms)입니다. 제 경우엔 따로 건든게 없으니 둘 다 디폴트 값으로 유지되고 있었습니다. 따라서, max-lifetime에 대한 문제는 아니었습니다. 혹시나 하는 마음에 값을 수정했지만, 완벽하게 해결되지 않았습니다.
결론
2022-04-11 수정
어떤 것이 문제인지 모르는 상황에서 RDS를 왜 사용하고 있지? 라는 생각이 들었습니다. RDS는 유지, 관리를 편리하게 해주는 장점이 있어 사용합니다. 다양한 편의 기능을 제공하고 있지만, 제 개발 수준에서 잘 활용하고 있지는 못합니다. 하지만, 에러가 발생하는 이 순간 RDS의 장점을 적극 활용해야 한다고 생각합니다.
에러 로그를 계속 확인했지만, 아무것도 뜨지 않았습니다. 어느 날, 별 생각없이 들어갔는데 "mysql warning ip address could not be resolved" 와 같은 로그가 찍혀있었습니다. 노트북으로 장소를 옮겨가면서 하니 IP가 계속 바뀌고 있었고, RDS의 보안 그룹에 할당되어 있는 IP가 맞지 않았습니다. 그래서 프로젝트를 빌드해서 EC2 인스턴스에 올려서 서버를 키면 정상적으로 동작하는 것이었습니다. 하지만, 보안 그룹에 0.0.0.0/32와 같이 모든 접근을 허락해놨기 때문에, IP 때문에 연결이 되는 것은 아니라고 생각합니다. 따라서, 아직 정확한 원인을 모르겠습니다.. 해결하기 위해, VPC를 이해하고 프록시를 고려해봐야할 것 같습니다.
2022-04-12 수정
사실, 위와 같은 문제는 제 고집에서부터 시작된 것입니다. 개발DB와 서버에 배포하는 DB를 분리하지 않았기 때문입니다. 아직 작은 프로그램이기 때문에 분리하지 않고 개발용과 배포용을 함께 사용하고 있었습니다. 노트북으로 장소를 옮기며 개발을 진행하니 IP와 관련한 문제가 발생한 것이었습니다. 우선, 로컬환경에 DB를 생성하여 연결해 개발하고, 배포시 설정을 바꿔서 배포했습니다. 문제는 해결이 되었습니다.
하지만, 보안 그룹을 모두 열어놨음에도 불구하고 특정 IP는 DB와 연결할 수 없는지 해결하진 못한 상황입니다. 제 수준이 더 올랐을 때 다시 한 번 생각해볼 것입니다.
물론, 다시는 개발용 DB와 서버에 배포하는 DB를 한 번에 사용하지 않을 것입니다..
정리
뭔가 손대지 않았는데 동작을 안하니까 정말 당황스러웠습니다. 다음에 똑같은 상황에 처하게 되면 참고하기 위해 기록으로 남깁니다. 비슷한 상황에 놓인 초보 개발자분이 계시다면 도움이 되면 좋겠습니다.
*참고한 블로그
좋은 글 남겨주셔서 감사합니다!
'개발 > Spring & Spring boot' 카테고리의 다른 글
[Spring] 내가 ~Service, ServiceImpl로 분리하는 이유 (0) | 2023.03.01 |
---|---|
[Spring] 전역 예외 처리를 위한 @ControllerAdvice와 @RestControllerAdvice (0) | 2022.07.06 |
[Spring] API 문서 자동화를 위한 Swagger 3.0.0 적용 (0) | 2022.06.29 |