발단
회사에서 커넥션 풀 시스템을 개발하고 있었는데, 개발을 마친 시스템이 동작하지 않았다. 어제 퇴근할 때쯤 개발이 끝나서 스프링부트 프로젝트에 올려 모든 과정이 정상적으로 동작하는 걸 확인했는데 오늘 아침에 출근을 해보니 스프링 프로젝트가 구동되지를 않는다. 에러로그를 쫓아보니 어제 내가 개발한 풀 시스템 쪽에서 에러가 나는건 확실한데... 왜 에러가 나는지 도저히 감이 잡히지 않는다. 아니 어제 정상적으로 동작하는 거 확인 했었단 말이야!
상황
개략적인 상황은 다음과 같았다. 스프링부트로 구동 중인 프로젝트가 하나 있고, 풀링 시스템이 동작하는 라이브러리가 하나 있다. 풀링 시스템 라이브러리는 내가 만든 라이브러리였고, GenericObjectPool이라고 하는 아파치의 JDBC 관련 풀 객체를 사용해 만들었다. 처음에는 풀링 시스템을 처음부터 직접 구현해볼까 생각을 하기도 했었는데, 코드의 양이 너무 많아질 것 같기도 했고 스레드 등을 코드에서 직접 다뤄야 했기 때문에 내 실력 상 회사 프로젝트로 쓰기에는 무리가 있다 싶었다. 그래서 GenericObjectPool을 상속 받아 새로 클래스를 만들어 작업을 진행한 것인데 바로 여기에서 문제가 생긴 것 같다.
원인
문제의 원인을 (오전 근무를 거의 통째로 날린 구글링 결과) 찾아냈는데 원인은 아무래도 스프링부트와 관련 되어 있는 것으로 보였다. 에러 로그를 보면 두 부분을 주목해볼 수 있는데, 첫 번째
org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [내가 만든 풀링 시스템 자바 객체]
내가 만든 클래스를 MBean으로 등록할 수 없다고 하는 부분.
그리고 두 번째,
Caused by: javax.management.InstanceAlreadyExistsException: MXBean already registered with name org.apache.commons.pool2:type=GenericObjectPool,name=pool
MBean으로 등록할 수 없는 이유가 MXBean으로 org.apache.commons.pool2:type=GenericObjectPool 형태의 객체가 이미 등록되어 있다는 부분이었다.
정리해보자면, 스프링부트는 애플리케이션을 기동할 때 빈들을 (MBean이 뭔지 MXBean이 뭔지는 잘 몰라도) MBean, MXBean 등으로 등록을 하는데, 이 빈이 이미 등록되어 있다는 뜻인 것 같았다. 스프링 컨테이너에 올라가는 모든 객체는 싱글톤 디자인 패턴을 보장받기 때문에 빈이 중복 등록되는 상황이 런타임 에러를 발생 시킨 이유인 것 같았다. 참고로 MBean, MXBean은 JMX(Java Management eXtension)에 의해 만들어지는 애들인데, JMX는 자바 애플리케이션을 모니터링 하고 관리하는 녀석이라고 한다.
해결
에러의 원인이 빈의 중복 등록이니 가장 먼저 코드 내부에 내가 만든 클래스를 중복으로 등록하는 로직이 있는지 체크했지만, 그런 부분은 발견되지 않았고 문제는 또 한번 미궁으로 흘러갔다. 한참 또다시 이슈 핸들링을 하다 보니, 문득 GenericObjectPool이 JDBC에 관여하는 객체라는 사실이 생각났다. JDBC라면 자바 애플리케이션에서 상당히 빈도있게 사용되는 기능이고, 나는 스프링부트를 쓰고 있으니 스프링부트가 내부적으로 GenericObjectPool과 관련된 JDBC 객체들을 미리 빈으로 등록해 놓은 것은 아닐까? 하는 가설이 머릿속을 스치고 지나갔다.
MBean을 확인할 수 있을까 싶어 구글링을 해봤고, 자바 1.5부터 자바와 함께 자동으로 설치되는 jconsole이라는 모니터링 프로그램을 이용해 순수한 스프링부트 프로젝트를 돌려 GenericObjectPool로 등록된 MBean이 존재하는지 확인해 봤다. 못찾는 건지 몰라도, 안보이는데..?
다시 좌절을 하던 찰나 우선 에러 로그가 "중복 등록 중!!"으로 너무 명확했고 중복 등록된 빈이 GenericObjectPool이라는 사실도 너무 자명하다는 사실에 집중하기로 했다. 결국 눈으로 직접 확인할 수는 없었지만 스프링부트가 내부적으로 GenericObjectPool을 핸들링 한다는 내 가정을 믿고 내가 만든 빈을 MBean으로 등록하지 않거나 중복 등록할 수 있는 기전이 있는지 체크해 봤고, 아래와 같이 EnableMBenaExport라는 애노테이션을 빈 등록 로직에 추가하면서 에러를 해결할 수 있었다.
에러 해결을 위해 사용한 코드는 @EnableMBeanExport라는 애노테이션이었고, 등록하는 빈을 MBean으로 등록하지 않는다?는 의미를 갖는 것으로 보인다. 아니면 등록은 하되 무시한다.. 이런 뜻인가? 모르겠다.
@Configuration
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) // 이슈 해결에 사용된 애노테이션
public class MqttConfiguration {
...
}
느낀 점
개발이 참 어렵다. JMX라는 건 또 처음 알았다.
신기한 건, 스프링부트가 내부적으로 사용하는 톰캣이 아니라, 별도의 외장 톰캣을 통해 프로젝트를 돌리면 에러 없이 정상적으로 프로젝트가 올라간다는 것이다. 스프링부트가 사용하는 내장톰캣과 외장톰캣이 어떤 방식으로든 차이가 있다는 것을 의미하는 것 같은데, 현재 내 기술적인 실력으로는 그 부분까지 파악하기에는 조금 부친다. 나중에.. 나중에 알아가겠지..
아니 근데, 어제 퇴근할 때는 됐었는데 오늘은 왜 안된거지. 이건 진짜 모르겠다.. (아침에 pc에 mariaDB를 설치하기는 했는데 그거 때문일까? 그건 아닌 것 같은데..)
'메모 & 삽질기록보관소' 카테고리의 다른 글
[자바] 자바 데이터 마이그레이션 툴 개발기 (0) | 2024.03.31 |
---|---|
MQTT 공부 중 참고할만한 문헌 모아두기 (5) | 2023.06.11 |
[데이터베이스] MySQL과 MariaDB의 차이점? (0) | 2022.11.17 |
[strapi] NginX 리버스 프록싱 관련 이슈 (0) | 2022.07.07 |
[JPA] 스프링이 엔티티를 인식하지 않는 것 같습니다! (0) | 2022.06.18 |