Connection Timeout
- 클라이언트가 서버측으로 connection을 맺길 원하지만 서버의 장애 상황으로 connection조차 맺어지지 못할 때 발생하는 timeout이다.
- 우리가 흔히 알고있는 TCP 3 way handshake를 통해 TCP 연결이 생성되지 못한것을 의미함.
Read Timeout
- 클라이언트와 서버가 connection을 맺으면 하나의 데이터 덩어리가 아닌 여러개의 패킷으로 나눠서 전송하게 되는데, I/O작업이 길어지거나 락이 걸려 요청이 처리되지 못하고 있을 때 클라이언트는 더 이상 기다리지 못하고 커넥션을 끊는다. 즉, 응답을 기다리지 못하는 것이다. 이런 상황을 Read Timeout 이라고 하는데 java에서는 SocketTimeout Exception이 떨어진다.
java에서의 Connection, Socket/Read timeout과 관련된 예외
java.net.SocketException
: Thrown to indicate that there is an error creating or accessing a Socket. → connection timeout
java.net.SocketTimeoutException : Signals that a timeout has occurred on a socket read or accept. → socket timeout, read timeout
Write Timeout
- 클라이언트가 서버로 패킷을 보낼 수 있는 timeout을 설정할 수 있다. 클라이언트와 서버가 connection은 맺어졌지만 설정된 timeout의 값 보다 데이터를 보내는(요청) 시간이 길어지면 Write Timeout이 발생하여 Exception이 떨어진다.
Statement Timeout
- 네트워크 연결 장애에 대한 timeout을 담당하는 것이 아니다.
- Statement 하나가 얼마나 오래 수행되어도 괜찮은지에 대한 한계 값이다. JDBC API인 Statement에 타임아웃 값을 설정하며, 이 값을 바탕으로 JDBC 드라이버가 StatementTimeout을 처리한다. JDBC API인 java.sql.Statement.setQueryTimeout(int timeout) 메서드로 설정한다.
- 네트워크 장애에 대비하는 타임아웃은 JDBC Driver SoecketTimeout이 처리해야 한다.
Transaction Timeout
- 프레임워크나 애플리케이션 레벨에서 유효한 타임아웃이다.
- 간단히 설명하면 "StatementTimeout x N(Statement 수행 수) + α(가비지 컬렉션 및 기타)"라고 할 수 있다. 전체 Statement 수행 시간을 허용할 수 있는 최대 시간 이내로 제한하려 할 때 TransactionTimeout을 사용한다.
- Statement 한 개를 수행할 때 0.1초가 필요하다면, 몇 개 안 되는 Statement를 수행할 때에는 문제가 없다. 그러나 Statement 10만 개를 수행할 때에는 일만 초(약 7시간)가 필요하다. TransactionTimeout은 이런 경우에 사용할 수 있다.
JDBC Driver’s SocketTimeout
- JDBC Driver Type4는 소켓을 사용하여 DBMS에 연결하는 방식이고, 애플리케이션과 DBMS 사이의 ConnectTimeout 처리는 DBMS에서 하지 않는다.
- JDBC 드라이버의 SocketTimeout 값은 DBMS가 비정상으로 종료되었거나 네트워크 장애(기기 장애 등)가 발생했을 때 필요한 값이다. TCP/IP의 구조상 소켓에는 네트워크의 장애를 감지할 수 있는 방법이 없다. 그렇기 때문에 애플리케이션은 DBMS와의 연결 끊김을 알 수 없다. 이럴 때 SocketTimeout이 설정되어 있지 않다면 애플리케이션은 DBMS로부터의 결과를 무한정 기다릴 수도 있다(이러한 Connection을 Dead Connection이라고 부르기도 한다). 이러한 상태를 방지하기 위해 소켓에 타임아웃을 설정해야 한다. SocketTimeout은 JDBC 드라이버에서 설정할 수 있다. SocketTimeout을 설정하면 네트워크 장애 발생 시 무한 대기 상황을 방지하여 장애 시간을 단축할 수 있다.
- SocketTimeout 값을 Statement의 수행 시간 제한을 위해 사용하는 것은 바람직하지 않다. 그러므로 SocketTimeout 값은 StatementTimeout 값보다는 크게 설정해야 한다. SocketTimeout값이 StatementTimeout보다 작으면, SocketTimeout이 먼저 동작하므로 StatementTimeout 값은 의미가 없게 되어 동작하지 않는다.
- SocketTimeout에는 아래 두 가지 옵션이 있고, 드라이버별로 설정 방법이 다르다.
- Socket Connect 시 타임아웃(connectTimeout): Socket.connect(SocketAddress endpoint, int timeout) 메서드를 위한 제한 시간
- Socket Read/Write의 타임아웃(socketTimeout): Socket.setSoTimeout(int timeout) 메서드를 위한 제한 시간
OS level socket timeout 설정
- SocketTimeout이나 ConnectTimeout을 설정하지 않으면 네트워크 장애가 발생해도 애플리케이션이 대부분 이를 감지할 수 없다. 따라서 연결이 되거나 데이터를 읽을 수 있을 때까지 애플리케이션이 무한정 기다리게 된다. 그러나 서비스에서 발생한 실재 장애 상황에서는 30분 후에 애플리케이션(WAS)이 재연결을 시도하여 문제가 해결되는 경우가 많다. OS에서도 SocketTimeout 시간을 설정할 수 있기 때문이다. 해당 설정 값을 통해 OS 레벨에서 네트워크 연결 끊김을 확인하는 것이다. 만약 리눅스 서버의 KeepAlive 체크 수행 주기가 30분이면 SocketTimeout 설정을 0으로 해도 네트워크 장애로 인한 DBMS 연결 장애 지속 시간이 30분을 넘지 않는 것이다. Linux 서버에서 KeepAlive 체크 수행 주기는 tcp_keepalive_time로 조정할 수 있다.
TCP Retransmission
사실상 TCP 이야기가 나오면 항상 메인 토픽으로 다뤄지는 이야기가 있다. 바로 재전송 (Retransmission)에 관한 내용이다. TCP는 송신에 대한 응답이 오지않는 경우 일정시간을 대기 후에 다시 재전송을 한다. TCP Protocol 자체가 전송에 관한 Protocol이고 그 특성으로 Reliability를 갖고 있는 만큼 재전송에 대한 이야깃거리가 상당히 많다. 그중 대표적인 두가지가 RTO와 RTT 이다. RTO는 Retransmission Timeout 을 뜻하는 말로 타이머가 작동하는 시간을 의미, RTT는 Round Trip Time을 뜻하는 말로 네트워크 통신을 하는 두 노드 간에 패킷이 전달되는데 소요된 시간을 의미한다.
TCP Handshake를 하기 위한 첫 번째 SYN 패킷은 상대방과의 RTT에 대한 정보가 전혀 없기 때문에 InitRTO라고 부르는 값을 사용하고 OS 마다 값이 조금씩 다르지만, Linux에서는 1초로 되어 있다. 1초 --> 2초 --> 4초 이런 식으로 InitRTO 값은 2의 제곱수로 늘어남. 따라서 애플리케이션에서는 timeout 설정 시 최소 1초 이상을 설정 해야 함.(커넥션 풀 방식으로 네트워크 세션을 미리 만들어 두고 통신하는 경우에는 InitRTO가 발생할 일이 없기 때문에 더 작은 값으로 설정해도 무방함.)
Timeout 설정 팁
위에 설명한 것처럼 InitRTO의 경우는 1초로 설정되어 있기 때문에 애플리케이션의 타임아웃은 1초보다 큰 값으로 설정해야 한다. 이미 맺어진 세션에서의 재전송은 RTT를 기반으로 생성되기 때문에 대부분 1초를 넘기지 않겠지만, TCP Handshake를 맺는 과정에서의 재전송은 최소 1초는 소요되기 때문에 애플리케이션에서 타임아웃을 1초로 설정한다면 재전송으로 커버 가능한 통신을 타임아웃 에러로 끊어 버릴 수 있음.
이상적인 Connection Timeout
보통 커넥션 타임아웃은 3초 로 한번정도 애플리케이션 단에서 재전송 하는게 이상적이고 그 이상은 서버 문제가 있지 않은지 확인해야함.
이상적인 Read Timeout
Read Timeout에 영향을 주는 패킷 재전송을 위한 타임아웃 값인 RTO가 RTT를 기준으로 만들어져서 보통 1초보다 짧기 때문에 RTO의 최소값은 200ms + 상대 서버의 프로세스 처리 시간이 가장 이상적인 값
단순 조회의 경우 짧게 가져가고, 복잡한 로직의경우 프로세스 시간을 여유있게주는게 좋음.
하지만 이도 로드밸런서나 서버단의 idle timeout을 넘지 않도록 설정해야한다.
결론
재전송은 TCP 메커니즘에 의해서 반드시 발생할 수밖에 없다.
TCP 통신 장애가 일어나지 않게 기도하는 것이 중요한 게 아니고, 재전송이 빨리 일어날 수 있도록 해주는 것이 중요하다.