ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 네트워크 Timeout에 대처하는 방법
    CS/네트워크 2024. 1. 7. 00:01

    개요

    항상 조금 헷갈리던 개념이었던 네트워크 Timeout에 대해 정리해보고자 합니다.

     

    이 글을 읽으면 다음과 같은 궁금증을 해결할 수 있습니다.

    • 네트워크의 통신은 어떻게 일어날까?
    • 네트워크 Timeout은 왜 발생할까?
    • 네트워크 Timeout이 발생하면 어떤 것들을 주의해야 할까?
    • 네트워크 Timeout이 발생할 수 있으니 어떻게 대처해야 할까?

     

    네트워크 통신이란?

    https://thestudygenius.com/computer-network-components/

     

    컴퓨터, 스마트폰, 서버 등의 장치가 인터넷이나 네트워크를 통해 서로 데이터와 정보를 교환하는 프로세스입니다.

    이때 통신을 위해서 가장 일반적으로 TCP/IP 프로토콜이 활용됩니다.

     

    TCP/IP는 3 way handshake를 통하여 패킷(데이터)의 안정적인 전송을 보장합니다.

    네트워크에 있는 장치들이 데이터를 교환하기 위해서는 먼저 connection이 수립되어야 하고 이때 TCP Handshake가 사용됩니다.

     

    https://coderscat.com/tcp-3-way-handshake-in-detail/

    TCP Handshake는 Alice와 Bob이 통화를 할 때 연결이 안정적인지 확인하기 위해 서로 연결상태를 확인하는 모습을 떠올리면 됩니다.

     

    https://www.linkedin.com/posts/amigoscode_systemdesign-coding-interviewtips-activity-7130547516684554240-DWSt/?trk=public_profile_like_view

     

    실제 TCP Handshake에서는 음성으로 확인하는 대신 SYN, ACK를 활용합니다.

     

     

    네트워크 Timeout은 왜 발생할까?

    TCP Handshake로 Client와 Server간에 연결을 수립하였는데 네트워크 Timeout은 왜 발생할까요?

     

    우선 네트워크 Timeout은 Read Timeout과 Connection Timeout으로 2가지가 존재합니다.

    https://alden-kang.tistory.com/20

     

    Connection Timeout은 TCP Handshake 과정에서 발생하며 Read Timeout은 TCP Handshake 이후 데이터를 주고받을 때 발생합니다.

     

    질문을 살짝 바꾸어보겠습니다.

    "TCP Handshake를 못하는 경우와 이미 TCP Handshake 연결을 수행했을 때 데이터를 받지 못하는 경우는 언제일까요?"

     

    Server와 Client가 TCP Handshake를 수행하는 동안 혹은 연결을 수행하고 데이터를 전송하는 동안에는 Switch, Router 등 많은 장치들을 통과하면서 패킷을 교환합니다.

     

    이때 다양한 요인으로 시간이 오래 소요될 수 있습니다.

    • 서버에 많은 트래픽 요청으로 요청에 대한 응답시간이 느려짐
    • 패킷이 크게 지연될 정도로 네트워크가 정체됨
    • 패킷이 유실되어 재전송을 수행

     

    "그렇다면 Client는 언제까지 기다려야 할까요? 재전송에 성공할 때까지 무한정 기다려야 할까요?"

     

     

    내 리소스를 낭비하면서 상대방에서 요청이 오기를 무한정으로 기다릴 수 없기 때문에 Timeout이라는 개념이 생기게 되었습니다.

     

     

    네트워크 Timeout이 발생하면 어떤것들을 주의해야 할까?

    Timeout이 발생하면 에러가 발생한 것이니 재시도를 하면 될까요?

    Timeout이 발생하면 에러가 발생한 것이니 정말 실패한 걸까요?

     

    이런 경우 3가지 케이스로 나누어 볼 수 있습니다.

    1. 요청이 서버에 도달한 뒤, 에러가 발생했고, 그 요청은 실제로 실패했다.
    2. 요청이 서버에 도달한 뒤, 에러가 발생했지만, 사실 그 요청은 성공했다.
    3. 알고보니 요청이 서버에 도달조차 하지 못했다.

     

     

    만약 해당 요청이 성공했지만 에러가 발생했고 Timeout이 발생했으니 재시도를 하면 어떻게 될까요?

    그 요청이 결제요청이라고 가정해 보면 결제가 2번 일어나기 때문에 사용자 입장에서 큰 사고로 이어질 수 있고 매우 주의해야 합니다.

     

    반대로 해당 요청이 성공했지만 에러가 발생했고 Timeout이 발생했으니 실패처리를 하면 어떻게 될까요?

    실제로 결제는 완료되었으나  주문이나 배송이 되지 않는 상황으로 이어질 수 있습니다.

    이 또한 사용자 입장에서는 큰 사고이며 서비스의 신뢰성이 감소할 수 있습니다.

     

    실제로 실습을 통해 네트워크 Timeout 발생시켜 보기

    전체코드는 github을 참고해 주세요.

     

    회원저장을 요청하는 Client와 회원저장을 담당하는 Server로 구성하였고 HTTP 통신을 수행하여 의도적으로 Timeout을 발생시켜 봅니다.

     

    이후에 Client에서는 응답으로 Timeout 예외가 발생하였지만 Server에는 DB에 회원이 저장된 모습을 확인해 볼 예정입니다.

     

    Client Application

    @RestController
    class NetWorkTimeOutClientController(
            private val restTemplate: RestTemplate,
    ) {
    
        @PostMapping("/members")
        fun saveMember(){
            val url = "http://localhost:8081/members"
            restTemplate.postForEntity(url, null, String::class.java)
        }
    }
    
    @Configuration
    class RestTemplateConfig{
    
        @Bean
        fun restTemplate(restTemplateBuilder: RestTemplateBuilder): RestTemplate{
            return restTemplateBuilder
                    .setConnectTimeout(Duration.ofSeconds(5))
                    .setReadTimeout(Duration.ofSeconds(10))
                    .build()
        }
    }

    간단하게 ReadTimeout을 10초로 설정하였고 8081 포트에 회원가입 요청을 보냅니다.

    만약 10초가 넘어간다면 SocketTimeoutException이 발생하게 될 것입니다.

     

    Server Application

    @RestController
    @RequestMapping("/members")
    class SignUpController(
            private val memberRepository: MemberRepository,
    ) {
    
        @PostMapping
        fun signup(){
            memberRepository.save(Member())
            sleep(11* ONE_SECOND)
        }
    
        companion object{
            const val ONE_SECOND = 1000L
        }
    }

    Server Application은 요청을 받아 회원가입 적재를 수행하고 11초의 Thread Sleep을 수행합니다.

    이로써 Server Application이 요청을 처리할 때 Client Application에서는 항상 Timeout이 발생할 것입니다.

     

    실제로 Client Application에서 요청을 보내보게 되면 다음과 같은 예외가 발생합니다.

    java.net.SocketTimeoutException: Read timed out

     

     

    이제 Server Application의 DB를 h2-console으로 살펴보면 다음과 같이 회원이 저장되어 있습니다.

    ReadTimeout 예외가 발생했지만 회원이 저장되었다..!

     

    실습을 통해 네트워크 Timeout이 발생하여도 실제 내부 동작은 성공할 수 있음을 확인하였습니다.

    그러면 네트워크 Timeout이 발생하게 되었을 때 Client는 어떻게 대처해야 할까요?

     

     

    네트워크 Timeout이 발생할 수 있으니 어떻게 대처해야 할까?

    멱등성이 보장된다면 재시도해보자

     

    외부호출 API가 멱등성을 제공한다면 재시도로 해결해 볼 수 있을 것 같습니다.

    RFC 7231:Hypertext Transfer Protocol (HTTP/1.1): Semantics and Contet에 따르면 멱등성이란 여러 번 동일한 요청을 수행했을 때, 서버에 미치는 의도된 영향이 동일한 경우를 뜻합니다.

     

    이로써 외부호출 API가 중복된 결제를 막아주기 때문에 2번 결제되는 발생하지 않습니다.

    하지만 재시도를 몇 회까지 시도할지에 대해 잘 판단해야 하고 재시도 끝에도 Timeout을 만났다면 결국에는 결제 성공 여부를 모를 것입니다.

     

    그러면 멱등성이 보장 안되면 어떻게 하지?
    재시도 끝에도 Timeout이 발생했다면 결국 성공/실패 여부를 모를 것 같은데?
    이런 경우에는 성공/실패를 어떻게 판단할 수 있을까요?

     

    주문과 결제를 예시로 생각해 보겠습니다.

    주문서버에서 결제를 위해 결제서버에 요청을 보내고 네트워크 Timeout 예외를 받았습니다.

     

    첫 번째로는 주문을 대기상태로 저장하여 유저가 새로운 주문을 요청한 경우 '이전 주문 건이 진행 중'이라는 문구를 노출하고 재주문을 막아야 합니다.

     

    재주문을 막음으로써 중복으로 주문 혹은 결제되는 경우를 막을 수 있습니다.

    하지만 유저의 상태가 계속 주문대기로 남아있을 수는 없기 때문에 언젠가는 주문 상태를 성공/실패로 변경해야 합니다.

     

    두 번째로는 결재내역의 상태를 알아내야 합니다.

    폴링기법 혹은 Message Broker를 활용하여 결제 서버가 정의한 최종상태의 결재내역을 알아냅니다.

    이제 결제 성공/실패에 대한 상태를 주문서버가 알 수 있게 됩니다.

     

    이제 성공과 실패를 판단할 수 있을 것 같습니다.

     

    그런데 만약 결제서버에 요청이 도달조차 하지 않았다면 어떻게 될까요?

     

    결제서버는 어떤 요청도 받지 못하였고 그에 대한 결제상태를 주문서버에게 전달할 수 없게 됩니다.

    이제 주문은 영원히 대기 중인 상태로 남게 됩니다.

     

    대기중인 주문을 특정시간마다 Batch를 통해 결제서버에 결제 상태를 물어보고 없음으로 응답된다면 결제를 실패처리하면 될 것 같습니다.

     

    하지만 없음으로 되어 결제 실패를 처리하였지만 그 이후에 바로 결제서버에 요청이 들어오게 된다면 주문에서는 결제 실패로 기록되지만 실제 결제는 성공될 수 있습니다.

     

    주문 서버와 결제 서버가 타임아웃 시간을 1분으로 약속하면서 이 문제를 해결할 수 있습니다.

    주문 서버가 요청을 보낼 때 요청 시각을 포함시켜서 보냅니다.

    타임아웃 시간이 지났는데 결제요청이 도달하지 않으면 결제서버에 결제 상태를 물어보고 없음으로 응답된다면 결제를 실패처리 합니다.

    이후에 결제 요청이 뒤늦게 도달하더라도 요청 시간을 보고 타임아웃 시간이 지났다면 처리하지 않고 거절합니다.

     

    이제 결제서버에 요청이 도달하지 않은 경우에도 안전하게 성공과 실패를 판단할 수 있습니다.

     

     

    그러면 처음부터 모든 외부요청에 대한 Timout을 고려해야 할까요?

    처음부터 이런 모든 요소들을 고려하면서 프로그래밍을 하는 것은 시간이 너무 오래 걸릴 수 있습니다.

    심지어 여러 가지 케이스들을 고려하더라도 놓치는 케이스들이 존재할 수 있습니다.

     

    먼저 모니터링 시스템을 잘 구축하여 예외케이스에 대한 알림을 받는 것이 중요할 것 같습니다.

    예외에 대한 알림을 보면서 네트워크 Timeout이 발생하면 해당 원인을 수정하거나 대응해 줄 수 있습니다.

     

    추후에 네트워크 Timeout이 빈번하게 발생하는 경우라면 자동으로 처리될 수 있도록 위와 같이 구현을 고려해 볼 수 있습니다.

    다만 결제와 같이 중복처리에 매우 민감한 도메인의 경우에는 처음부터 Timeout에 대한 고려를 해볼 수도 있습니다.

     

     

     

     

     

    참고자료

    https://www.ibm.com/docs/ko/aix/7.2?topic=management-network-communication-concepts

    https://thestudygenius.com/computer-network-components/

    https://www.linkedin.com/posts/rasel9152_tcp-handshake-how-does-it-work-transmission-activity-7111464963034009600-Qe1l/

    https://thinklucid.com/tech-briefs/tcp-for-10gige-reliable-image-transfer/

    https://coderscat.com/tcp-3-way-handshake-in-detail/

    https://junuuu.tistory.com/849

    https://hudi.blog/safely-handling-network-errors/

    https://www.youtube.com/watch?v=v9rcKpUZw4o

    'CS > 네트워크' 카테고리의 다른 글

    HTTP/TCP 지연과 성능개선법  (0) 2023.08.13
    10만 유저 동시 접속 가능하게 하기  (0) 2023.06.26
    CLOSE_WAIT 과 TIME_WAIT  (0) 2023.06.25
    세션 하이재킹과 TCP Sequence Number  (0) 2022.07.28
    ICMP 프로토콜이란?  (0) 2022.07.22

    댓글

Designed by Tistory.