Reusing HTTP connections in Golang

Chung Hwan·2022년 11월 3일
0
post-thumbnail

Go에서 http 요청을 보내기 위해 net/http 패키지를 이용한다.

client := &http.Client{}
req, _ := http.NewRequest(method, url, body)

resp, err := client.Do(req)

기본적으로 Go의 http client는 커넥션을 재사용한다. 참고

By default, Transport caches connections for future re-use. This may leave many open connections when accessing many hosts.

그러나 조건이 있는데, response body가 close 되어야만 커넥션이 재사용된다. 참고

The default HTTP client's Transport may not reuse HTTP/1.x "keep-alive" TCP connections if the Body is not read to completion and closed

당연하지만 단위 시간 당 요청을 많이 보내야 하는 경우에 커넥션 재사용은 아주 중요하다. 서버/클라이언트 모두의 부하를 많이 줄여줄 수 있기 때문이다. 더 중요한 건, 짧은 시간 내에 요청을 아주 많이 보내는 경우 커넥션 재사용을 하지 않으면 높은 확률로 connection reset by peer 에러가 발생할 수 있다. 커넥션을 재사용하지 않는다는 건 매번 새로운 커넥션을 연다는 것인데, 서버가 유지할 수 있는 커넥션 개수는 제한적일 수 밖에 없다. 따라서 너무 많은 handshake 요청이 동시에 들어오면 서버 쪽에서 거부하기 시작하면서 해당 에러가 발생한다.

그러므로 Go에서 http 요청을 보내고 나서, response body를 close하는 것을 늘 잊지 말자.

client := &http.Client{}
req, _ := http.NewRequest(method, url, body)

resp, err := client.Do(req)
defer resp.Body.Close()

net/http 패키지를 사용할 때 뿐만 아니라 HTTP를 기반으로 하는 서드파티 API를 사용할 때도 조심하자.

필자의 한 동료 분은 aws/aws-sdk-go 패키지를 이용해 아래와 같이 코드를 짜셨다.

recordsBatchInput := &firehose.PutRecordBatchInput{}
// ...
req, _ := c.firehose.PutRecordBatchRequest(recordsBatchInput)
err := req.Send()

그리고 PR description에 아래와 같은 말씀을 남기셨다.

종종 read: connection reset by peer 에러로 인해 firehose에 전달되지 않는 일이 발생합니다. 비율은 대략 0.1% 미만입니다.

비율이 100%가 아닌 (무조건 발생하는 것은 아닌) 이유는 상술했듯이 서버가 감당할 수 없을 만큼 짧은 시간 동안 너무 많은 커넥션이 있을 때, 새로운 handshake를 거부할 때만 발생하기 때문일 것이다. 이것으로 커넥션 재사용이 안 되는 문제일 것이라고 예상하고, 아래처럼 답변해드렸다.

이거 뭔가 커넥션 재사용이 안 되어서 생기는 문제일 거 같은데요. 한번 req.Send 이후에 응답 body를 close할 수 있는 메소드가 있다면 시도해보시겠어요?

그리고 동료분은 아래 라인을 추가하셨고, 이슈는 해결되었다.

defer req.HTTPResponse.Body.Close()

0개의 댓글