Oracle DB로 기동 된 서버의 웹소켓을 호출하는 부분에서 알 수 없는 로그와 함께 에러가 발생하였다.
에러 내용
org.springframework.dao.DataAccessResourceFailureException: PreparedStatementCallback; SQL [INSERT INTO SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) VALUES (?, ?, ?)]; IO 오류: Socket read interrupted; nested exception is java.sql.SQLRecoverableException: IO 오류: Socket read interrupted
에러 파악
사용자가 기능을 사용하다가 업로드 취소할 경우 서버 측 WebSocket에서 onClose() 이벤트가 발생하고, 내부에서 업로드 처리 중인 스레드에 대해 Thread.interrupt()가 호출되도록 되어 있었다.
문제는 jdbc8부터 기본적으로 NIO 기반 소켓을 사용하도록 되어 있다는 점이다. 이 경우, interrupt() 호출되면 내부 SocketChannel.read() 작업이 강제로 종료된다.
즉, ojdbc8을 사용하는 환경에서 interrupt() 호출이 JDBC 내부 소켓을 끊어버리는 원인이 되었고, DB 커넥션이 비정상적으로 종료되며 Spring Session 저장 실패 에러가 나왔던 것이다.
해결 방법
1. JVM 옵션으로 Oracle JDBC의 NIO 소켓 기능 비활성화
-Doracle.jdbc.javaNetNio=false
- Tomcat 예시
# tomcat/bin/setenv.sh 또는 setenv.bat 에 추가
export CATALINA_OPTS="$CATALINA_OPTS -Doracle.jdbc.javaNetNio=false"
이 설정을 적용하면, JDBC가 NIO가 아닌 기존 블로킹 방식으로 동작하여 interrupt() 호출에도 소켓이 종료되지 않는다.
2. WebSocket 종료 시 interrupt 제거
t.interrupt(); // 제거 또는 사용 지양
업로드 스레드를 강제 종료하지 않고, 상태 플래그(isStop)를 사용하여 업로드 작업 내부에서 스스로 종료하도록 유도하는 게 안전하게 중단 할 수 있을 것이다.
단순한 업로드 취소 동작이 DB 커넥션 종료까지 이어질 수 있다는 점은 정말 예상하기 어려운 부분이었다. 드라이버 업데이트가 필요한 상황이라 해도, 가볍게 생각하고 적용해서는 안 된다는 걸 다시금 느꼈다. 이렇게 사소해 보이는 설정 하나가 시스템의 안정성에 큰 영향을 줄 수 있다는 사실을 기억해야겠다.