본문 바로가기

computer science

Timeout이란? - Connection Timeout / Read Timeout

타임아웃이란?

timeout은 특정 이벤트를 완료하는 데 할당된 제한 시간입니다. 이 제한 시간 안에 특정 이벤트가 완료되지 않으면 보통 'timed out' 되었다고 말합니다. 즉, timeout은 '제한시간' 그 자체를 나타내고, timed out은 제한 시간 내에 완료되지 못한 상태에 가깝습니다. 따라서 개발을 할 때 보통 timeout을 세팅하고, 그 시간 내에 처리되지 못하면(timed out) 어떤 조치를 취할 것인가를 구현하는 것이 일반적입니다.

이 때, 위에서 말한 특정 이벤트란 다음과 같이 network layer, application layer 두가지 계층으로 나누어 볼 수 있습니다.

Network layer

서버 간 TCP/IP 통신은 먼저 connection을 수립하고 이루어집니다. 예를 들어 인터넷을 사용해 브라우저 주소창에 'www.google.com'을 입력하여 구글 서버에 요청을 보내면, client인 우리는 구글 서버와 'connection'을 맺은 뒤 요청을 보내고 응답을 받는 통신을 하게 됩니다. 이외에도 특정 서버에서 데이터베이스 서버와 통신을 하거나 네트워크를 통해 파일 I/O를 하는 상황도 생각해볼 수 있습니다. 이처럼 Network layer에서의 이벤트는 네트워크 통신을 하는 상황에서 connection을 수립하는 행위입니다.

Application layer

연결이 수립되면 application server는 요청 받은 작업을 처리하고 그 결과를 응답합니다. 이 때 해당 서버가 수행하는 작업을 application layer에서의 이벤트라고 볼 수 있습니다. 특정 WAS 서버에서 로그인 요청을 받았다면 인가된 사용자인지 확인하거나, 데이터베이스에서 특정 쿼리를 수행하는 요청을 받았다면 해당 쿼리를 실행시키는 작업 등이 그 대상이 될 것입니다.

만약 네트워크 연결이 수립되지 않거나 서버의 작업 처리가 지연되는 상황이 발생한다면, 작업을 요청한 client는 무한정 대기하거나 서버에 부하가 생기는 상황이 발생할 수 있습니다. 이를 적절히 처리해주기 위해 timeout을 설정하는 것입니다.

 

Connection Timeout

network layer의 문제를 해결하기 위해 설정하는 제한시간입니다. 네트워크 간 connection을 맺기 위해 소비하는 시간을 조절할 수 있습니다.

존재하지 않는 서버(ip, port)에 소켓 요청을 보내는 상황을 가정해보겠습니다.

@SpringBootTest
public class ConnectionTimeoutTest {

    @Test
    void connectionTimeout() throws IOException {
        Socket socket = new Socket();
        SocketAddress unavailableHost = new InetSocketAddress("192.168.0.2", 12345);

        socket.connect(unavailableHost);
    }
}

임의의 host와 port를 할당하여 connect 요청을 보내면 테스트가 종료되지 않고 무한정 실행됩니다. connection을 맺지 못한 채 대기하고 있기 때문입니다. 이제 timeout을 설정해보겠습니다.

@SpringBootTest
public class ConnectionTimeoutTest {

    @Test
    void connectionTimeout() throws IOException {
        Socket socket = new Socket();
        SocketAddress unavailableHost = new InetSocketAddress("192.168.0.2", 12345);
        int timeout = (int) Duration.ofMillis(1000).toSeconds();

        socket.connect(unavailableHost, timeout);
    }
}

1초가 지나면 timed out이 발생하도록 파라미터로 값을 전달했습니다. 다음과 같은 메시지가 출력됩니다.

socket 연결이 timeout 시간을 초과했기 때문에, SocketTimeoutException을 던지고 'Connect timed out' 되었다는 메시지가 전달됩니다.

 

Read Timeout

network/application layer 모두에서 적용될 수 있지만 application layer 측면에서 보도록 하겠습니다. 10초 간 실행되는 간단한 RestController를 만들고, Read Timeout을 1초로 설정하였습니다.

@RestController
public class SlowController {

    @GetMapping
    public String slow() throws InterruptedException {
        Thread.sleep(10000);
        return "ok";
    }
}

@SpringBootTest
public class ConnectionTimeoutTest {

    @Test
    void connectionTimeout() {
        RestTemplate restTemplate = new RestTemplateBuilder()
                .setReadTimeout(Duration.ofSeconds(1))
                .build();

        String url = "http://localhost:8080";
        restTemplate.getForEntity(url, String.class);
    }
}

Thread를 sleep 하지 않으면 정상적으로 body에 "ok"를 출력하지만, Read Timeout을 1초로 설정하였기 때문에 아래와 같은 에러가 발생합니다.

local에서 실행시킨 slow() 메서드 처리가 지연되었기 때문에, SocketTimeoutException을 던지고 'Read timed out' 되었다는 메시지가 전달됩니다.