공부/Go

golang http connection(socket) 재사용하기

토고미 2023. 6. 22. 23:09

회사에서 부하테스트를 하던 도중 겪은 일이다.

 

부하를 주는 클라이언트가 할당된 리소스도 다 사용하지 못하고, 기대하던 RPS도 나오지 않는 현상이 있었다.

netstat 명령어로 확인 결과 상당 수의 소켓의 status가 TIME_WAIT 인 것을 발견했다.

 

TIME_WAIT은 연결은 종료된 상태지만, 바로 소켓을 반납하지 않고 혹시나 송신측에서 데이터를 보낼 수 있는 것을 기다리는 상태이다.

 

내가 기대했던 바는 소켓을 종료하지도, 반납하지도 않고 다음 통신에 재사용하는 것이다.

 

사실 해결법은 정말 간단하다.

 

go http 패키지는 기본적으로 소켓 재사용 정책이 적용되어있기 때문에, 아래 사항만 모두 지켜주면 된다.

  • response body를 전부 읽는다.
  • body를 close한다.

중요해서 볼드체로 했다. body를 읽기만 해서도 안되고, close만 해서도 안된다. 반드시 둘 다 해야한다.

코드로 표현하면 아래와 같다.

    response, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("에러")
    }

    Close the connection to reuse it
    defer response.Body.Close() // 필수!!

    body, err := ioutil.ReadAll(response.Body) // 필수!!
    if err != nil {
        return fmt.Errorf("에러")
    }

body close는 대부분 습관적으로 defer를 사용해서 잘 닫는다.

 

하지만 간혹 body를 읽지 않고 그냥 close할 수 있는데(내가 그랬다), 그러면 소켓 재사용이 되지 않는다.

body를 읽을 필요가 없다면 아래처럼이라도 해줘야한다.

res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()

 

요청량이 적은 환경에서는 문제가 안되지만,

대규모 트래픽을 상정하는 환경에서는 정말 중요한 것이기 때문에 절대 잊지 말자

 

참고

https://stackoverflow.com/questions/17948827/reusing-http-connections-in-go

https://raisonde.tistory.com/entry/netstat%EC%9D%98-TCP-status-%EC%9D%98%EB%AF%B8