매 시간 실행되는 배치 작업 중 특정일 22:00에 java.net.SocketException: Connection reset by peer 오류와 Socket Timeout 오류가 발생했습니다. 이 문제는 일정 시간이 지나면 자연히 해결되거나, ojdbc 버전 변경으로 일시적으로 해결되었습니다
문제 상황
- 3개의 서비스 중 A 서비스에서만 문제 발생 -> 같은 DB 참조
- A 서버 오류 지속 시 나머지 서비스도 영향 받음
- 랜덤한 특정일 22:00에만 문제 발생
- DB 툴에서 쿼리 실행 시 정상 작동
- 서버 재시작 후에도 문제 지속
- SpringBoot의 ojdbc 버전 변경할 시 일시적 문제 해결
의문점
매 시간 배치도는 서비스가 총 3개이다. 각각 시스템이 다르지만 참조하는 DB는 같다. 문제가 되는 A서버는 매 시 정각, 잘 수행되는 B는 03분, C는 05분이다. 3 시스템 모두 마이그레이션을 동시에 진행했고 A 서버만 문제가 발생했다.
다만 A서버에서 오류가 지속될 시 나머지 3개의 서비스도 SocketTimeout이 발생했다.
1. 왜 나머지 서비스는 문제가 생기지 않았을까? -> A 서버의 오류가 지속되면 왜 나머지 서버가 영향을 받을까?
2. 랜덤한 특정일 22:00에만 문제가 발생할까? 오류가 간헐적으로 발생해 재현이 어렵다.
3. Orange에서 운영 DB에 붙어서 해당 쿼리를 수행하면 정상출력된다. -> 해당 쿼리 수행시간은 10초 내외다.
4. 서버를 재시작해도 쿼리가 내내 수행되며 3~4시간 후 SocketTimeOut이 발생한다.
5. 해당 API의 ojdbc 버전을 높이면 문제가 해결되고, 며칠 뒤 동일한 문제가 또 발생하고, 다시 버전을 낮추면 또 몇일 간 정상작동한다.
특히 3,4,5번의 의문점은 도대체 왜 이런 문제가 발생하는지 전혀 알 수가 없었다. ojdbc 버전을 바꾸는 것과 쿼리 수행에 연관성이 있을까?
시도해 본 방법
1. 네트워크 및 DB 상태 점검
가장 의심스러운 건 네트워크와 해당 시간 DB 상태다. 네트워크 팀과 DBA에게 해당 시간 이슈사항을 문의하였고 문제없다는 결론을 얻었다.
2. 커넥션 로그 출력
두 번째로 ConnectionPool log를 출력했다. hikari Pool에서 로그를 출력하니 total 10, idel 0, active 10이 나왔다.
여분의 커넥션이 없다는 건데 hikari의 커넥션 pool을 늘려서 해결이 될까?
매 시간 배치를 돌면서 커넥션을 1개씩 사용하고, 오류가 발생했을 때 커넥션을 도로 뱉지 않고 추가로 할당받기만 한다. 커넥션을 늘리면 유예시간만 늘어날 뿐 근본적인 해결책이 안된다.
나머지 서버 오류의 원인은 active = total이 될 동안 A서버 오류가 잡히지 않으면 나머지 3개 서버는 이용할 수 있는 커넥션이 없기 때문에 오류가 발생한다.
-> A번 서버가 API 서버에 요청을 보내고 일정 시간 응답이 없으면 실패 오류를 뱉는다. 하지만 API 서버는 A 서버가 보낸 쿼리를 계속 수행하고 있다. 그 상태에서 계속 추가적인 요청이 오기 때문에 커넥션을 잡아먹기만 한다.
1번 의문점이 일부 해결이 됐다.
3. 쿼리 직접 조회
오류가 발생한 날 22:00에 들어가는 파라미터를 Orange for Oracle에 넣고 그대로 수행하니 정상적으로 값이 나온다. 해당 시간 쿼리 오류는 없음을 확인.
4. 실행계획 확인
DBA에게 요청하여 해당 시간 쿼리 실행계획을 확인했다. 확인결과 랜덤한 요일 22:00에 실행계획이 2개로 나눠지고, 쿼리가 오래 걸리는 실행계획을 수행하면 5시간 이상 걸렸다. 쿼리 수행시간이 빠르게 동작하는 실행계획만 타도록 고정하니 문제가 해결됐다.
실행계획이 2개가 만들어진 이유
같은 쿼리, 같은 파라미터인데 특정 일에 실행계획이 두 개가 실행되는 이유가 무엇일까? DB의 옵티마이저는 같은 쿼리라도 알아서 최적의 쿼리가 수행되도록 실행계획을 짠다. 하지만 완벽하지 않기 때문에 실시간으로 들어오는 데이터에 따라 실행계획이 변하기도 하고, 안 좋은 방면으로 실행되기도 한다.
이번 케이스는 특정일, 22:00에 들어있는 데이터를 옵티마이저가 봤을 때 느린 실행계획을 타도록 판단해서 발생한 문제다.
이 외에 실행계획이 바뀌는 케이스는 DB마이그레이션을 진행할 때 AS-IS 데이터를 TO-BE에 넣은 뒤 실행계획이 틀어질 수 있으니 확인이 필요하다.
자연스럽게 2,3,4,5번의 궁금증도 해결됐다.
3번의 경우 API에서 DB에 커넥션 한 것과, DB 툴에서 커넥션 한 것을 다른 실행계획으로 수행되도록 해서 DB 툴에선 잘 수행된 것이다.
4,5번의 경우 ojdbc 버전을 바꾼 뒤 두 가지 실행계획 중 빠른 실행계획이 타져서 해결된 것이다.
추후 조치 사항
실행계획을 고정해서 문제가 발생하지 않았지만 쿼리가 하루종일 돌아가도 DB에서 오류를 뱉지 않는 것도 문제다. Oracle Connection time out 설정을 추가해서 추후 오류가 발생할 시 문제가 생기지 않도록 조치했다.
Connection time out도 종류가 되게 많아서 추후 포스팅하겠다.