티스토리 뷰

개요

요청이 빈번하게 발생한다면 연결을 끊지않고 유지해두는 것이 낫다. 하지만 요청이 없는데도 연결을 유지하는 것은 또 다른 비용이다. 이번에는 연결을 언제까지 유지하여야 하는지에 대한 설정을 알아보자.

IdleConnTimeout 이다.

MaxIdleConns: 유지 가능한 최대 유휴 커넥션 수, default: 100
MaxIdleConnsPerHost: 호스트마다 유지 가능한 최대 유휴 커넥션 수, default: 2
IdleConnTimeout: 유휴 커넥션 타임아웃, default: 90초
MaxConnsPerHost: 호스트마다 사용 가능한 최대 활성/유휴 커넥션 수, default: 0 (무제한)

커넥션 풀에 무한정 연결을 유지할 수 없다.

클라이언트 코드

서버는 이전 포스팅에서와 같이 바로 회신을 하는 코드이기에 생략한다.

깃헙 코드: https://github.com/bigwhite/experiments/blob/master/http-client/client-with-idleconntimeout/client.go

클라이언트 전송 계층 설정

기본값은 90초인 IdleConnTimeout 값을 10초로 변경하였다.

tr := &http.Transport{
	MaxConnsPerHost:     5,
	MaxIdleConnsPerHost: 3,
	**IdleConnTimeout:     10 * time.Second,**
}
client := http.Client{
	Transport: tr,
}

1차 전화 통화(비유) - 커넥션 풀에 3개의 연결 넣어두기

동시다발로 5번의 요청이 발생하고, 5초간 기다린다.

커넥션 풀에 3개의 연결이 유지될 것이다.

wg.Add(5)	
for i := 0; i < 5; i++ {
	go func(i int) {
		defer wg.Done()
		resp, err := client.Get("<http://localhost:8080>")
		if err != nil {
			panic(err)
		}
		defer resp.Body.Close()
		body, err := io.ReadAll(resp.Body)
		fmt.Printf("g-%d: %s\\n", i, string(body))
	}(i)
}
wg.Wait()

time.Sleep(5 * time.Second)

2차 전화 통화(비유) - 커넥션 풀의 연결을 사용한 다음 닫히게 하기

5개의 고루틴이 각각 1초 간격으로 두 번의 요청을 한다. 결과적으로 5개의 요청이 동시다발로 이루어지고 1초뒤에 한 번더 5개의 요청이 동시다발로 이루어진다. 그리고 나서 15초간 대기한다.

5번 + 5번의 요청은 커넥션 풀의 연결로 이루어짐을 확인하고, 15초가 지나면 타임아웃으로 커넥션 풀의 연결이 닫히게 하려는 것이다.

wg.Add(5)
for i := 0; i < 5; i++ {
	go func(i int) {
		defer wg.Done()
		for i := 0; i < 2; i++ {
			resp, err := client.Get("<http://localhost:8080>")
			if err != nil {
				panic(err)
			}
			defer resp.Body.Close()
			body, err := io.ReadAll(resp.Body)
			fmt.Printf("g-%d: %s\\n", i+10, string(body))
			time.Sleep(time.Second)
		}
	}(i)
}

time.Sleep(15 * time.Second)

3차 전화 통화(비유)

이제 커넥션 풀에 유지되는 연결이 없는 상태이다.

여기서 5개의 고루틴이 각각 1초 간격으로 100번의 요청이 이루어지게 한다. 결과적으로 5개의 동시다발 요청이 1초 간격으로 100번이나 이루어지게 된다.

첫 5번의 요청은 5개의 연결을 생성하고, 이후는 커넥션 풀에 3개의 연결이 유지되면서 이를 계속 사용할 것이 예상된다.

wg.Add(5)
for i := 0; i < 5; i++ {
	go func(i int) {
		defer wg.Done()
		for i := 0; i < 100; i++ {
			resp, err := client.Get("<http://localhost:8080>")
			if err != nil {
				panic(err)
			}
			defer resp.Body.Close()
			body, err := io.ReadAll(resp.Body)
			fmt.Printf("g-%d: %s\\n", i+20, string(body))
			time.Sleep(time.Second)
		}
	}(i)
}
wg.Wait()

서버 로그 분석

1단계에서는 5개의 연결이 생성되고

2단계에서는 커넥션 풀의 3개의 연결을 계속 사용하는 것을 보여주며

3단계에서는 2단계의 연결이 닫힌 다음 다시 5개의 연결이 발생, 커넥션 풀에 3개의 연결이 유지되며 이후 계속 사용된다.

$go run server.go
// 1단계 - 5개의 연결(= 5개의 다른 포트 사용. 52484~52488)
receive a request from: [::1]:52484 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52488 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52486 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52485 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52487 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]

// 2단계 - 3개의 연결이 커넥션 풀에 들어갔다가 재사용된다. 52484, 52487, 52488
receive a request from: [::1]:52487 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52488 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52484 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52484 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52487 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]

receive a request from: [::1]:52487 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52488 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52484 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52487 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52484 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]

// 3단계 - 커넥션 풀의 연결이 모두 닫히고 새로운 5개의 연결이 생긴다. 52542~52546
receive a request from: [::1]:52542 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52544 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52545 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52543 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52546 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]

// 다시금 3개가 커넥션 풀로 들어와 재사용된다. 52542, 52544, 52545
receive a request from: [::1]:52542 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52544 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52545 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52542 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
receive a request from: [::1]:52544 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]

... ...

정리

전송 계층의 연결은 비용이 든다. 한 번 연결한 다음에 이를 계속 사용하면 연결의 비용, 시간을 아낄 수 있다. 한 편 연결을 사용하지도 않으면서 계속 유지하는 것 역시 낭비이다. 사용하는 도메인의 특성과 상황을 이해하고 그에 맞게 설정을 하여야 하는 이유이다.

반응형
반응형
잡학툰 뱃지
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함