import ( "코딩", "행복", "즐거움" )

커넥션 풀링 설정 테스트하기 본문

GO

커넥션 풀링 설정 테스트하기

더코드마니아 2023. 3. 23. 18:35

 

원문: https://zenn.dev/kouhei_fujii/articles/72ac1f8d4e8a84

 

【Golang】コネクションプーリングの設定をテストしてみる

最近はNuxt, TypeScriptで開発をしています。 バックエンドはGo, Rubyの経験があります。

zenn.dev

 

개요


이번 글에서는 Golang에서 연결 풀링 설정에 대해 설명한다. 커넥션 풀링은 데이터베이스와의 접속을 위한 리소스를 미리 확보해 두었다가 필요할 때 해당 리소스를 이용함으로써 접속 시간을 단축하거나 Mysql에 과도한 부하가 걸리지 않고 시스템의 응답성을 향상시킬 수 있게 됩니다.

환경


본 글의 코드는 아래 환경에서 동작을 확인하였습니다.

Golang v1.18.6
Mysql Ver 8.0.32
Docker version 20.10.17

 

커넥션 풀링이란?


커넥션 풀링은 데이터베이스와의 접속을 위한 리소스를 미리 확보해 두었다가 필요할 때 그 리소스를 이용함으로써 접속 시간을 단축할 수 있습니다.

Golang에서는 4종류의 설정을 지원한다.
Golang에는 SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime, SetConnMaxIdleTime의 4가지 설정이 있다. 각각에 대해 설명한다.


SetMaxOpenConns


SetMaxOpenConns는 동시에 사용할 수 있는 최대 연결 수를 설정하는 함수이다. 이를 통해 동시에 실행할 수 있는 쿼리 수를 제한하여 Mysql에 과부하가 걸리지 않고 시스템 응답성을 향상시킬 수 있습니다.

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database")
if err != nil {
    panic(err.Error())
}
db.SetMaxOpenConns(10)

이 예시에서는 10개의 연결을 동시에 열 수 있습니다. 이 숫자가 너무 크면 Mysql에 과도한 부하가 걸려 성능이 저하될 수 있으므로 이 숫자를 적절히 설정해야 한다.

 

SetMaxIdleConns


SetMaxIdleConns는 연결 풀에 보유할 최대 유휴 연결 수를 설정한다. 유휴 커넥션은 현재 아무런 쿼리를 수행하지 않는 커넥션을 말한다. 풀에 유지되어 새로운 연결을 설정하는 데 필요한 시간과 리소스를 절약할 수 있다.
유휴 연결이 너무 적으면 새로운 연결을 생성하는 데 시간이 낭비되어 성능에 악영향을 미칠 수 있다. 반면, 유휴 연결이 너무 많으면 메모리 소비를 증가시켜 서버 성능을 저하시킬 수 있다. 적절한 유휴 연결 수는 부하 테스트나 벤치마킹을 통해 결정해야 할 것으로 보인다.

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database")
if err != nil {
    panic(err.Error())
}
db.SetMaxIdleConns(5)

이 예시에서는 풀에 5 개의 유휴 연결을 유지할 수 있습니다.

SetConnMaxLifetime


SetConnMaxLifetime은 연결이 재사용되기 전에 허용되는 최대 시간을 설정한다. 이 시간이 지나면 연결이 풀에서 삭제되고 새로운 연결이 생성된다.

 

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database")
if err != nil {
    panic(err.Error())
}
db.SetConnMaxLifetime(5 * time.Minute)

이 예시에서는 5분을 초과하는 연결은 풀에서 삭제됩니다.

SetConnMaxIdleTime


SetConnMaxIdleTime은 연결이 유휴 상태(아무것도 처리하지 않는 상태)로 유지되는 시간을 지정한다. 지정한 시간이 지나면 연결이 닫히고 풀에서 삭제된다. 이를 통해 풀에 존재하는 유휴 커넥션 수를 최적화하여 리소스 낭비를 방지할 수 있습니다.

 

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database")
if err != nil {
    panic(err.Error())
}
db.SetConnMaxIdleTime(5 * time.Minute)

주의할 점은 SetConnMaxLifetime과 함께 설정하는 것이 중요하다. SetConnMaxLifetime은 연결이 풀에 존재할 수 있는 최대 시간을 설정하고, 이 기간을 초과하면 연결이 폐기된다. 반면, SetConnMaxIdleTime은 연결이 유휴 상태로 풀에 존재할 수 있는 최대 시간을 설정한다. 즉, 연결이 유휴 상태라도 SetConnMaxLifetime에서 설정한 최대 시간을 초과하면 해당 연결은 풀에서 삭제된다.

즉, 두 설정값 중 하나를 초과하면 연결이 풀에서 삭제되지만, SetConnMaxLifetime이 더 높은 우선순위를 가진다는 의미입니다. 따라서 애플리케이션에서 장시간 실행되는 연결을 설정하는 경우 SetConnMaxLifetime을 설정하는 것이 중요하다.


연결 풀링 설정 테스트하기


다음 명령어로 Docker를 실행한다.

docker run -p 3306:3306 --name test-mysql -e MYSQL_ROOT_PASSWORD=pass -d mysql:8.0

Mysql의 연결 한도를 초과하는 요청을 해보자.
실제로 Mysql의 연결 한도를 초과하는 요청을 하여 에러가 발생하는지 확인해 봅시다.

Mysql의 연결 상한을 확인하는 방법
최대 동시접속자 수를 확인하려면 다음 명령을 실행합니다.

mysql> show variables like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+
1 row in set (0.04 sec)

다음은 제 Mysql 환경에서 명령을 실행한 결과입니다.

테스트


이 테스트에서는 Mysql의 연결 상한을 초과하는 200개의 골루틴을 생성하여 각각의 골루틴이 쿼리를 실행하도록 되어 있습니다.
일정 수의 골루틴이 실행되는 시점에 Mysql에 대한 연결 수가 최대치를 초과하면 이후 연결 요청이 모두 실패하게 된다. 그리고 실패한 골루틴에서 전송된 에러를 받아 예상대로 에러가 발생하는지 확인합니다.

 

func TestMaxConnections(t *testing.T) {

	db, err := sql.Open("mysql", "root:pass@(localhost:3306)/testdb")
	assert.Nil(t, err)
	assert.NotNil(t, db)

	defer db.Close()

	var wg sync.WaitGroup
	var errCh = make(chan error)

	for i := 0; i < 200; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_, err := db.Exec("SELECT 1")
			if err != nil {
				errCh <- err
			}
		}()
	}

	go func() {
		wg.Wait()
		close(errCh)
	}()

	for err := range errCh {
		// エラーが発生した場合、期待通りのエラーであることを確認する
		assert.EqualError(t, err, "Error 1040: Too many connections")
	}
}

다음은 테스트 실행 결과입니다.

=== RUN   TestMaxConnections
[mysql] 2023/03/13 18:40:06 packets.go:37: read tcp [::1]:51852->[::1]:3306: read: connection reset by peer
...
--- PASS: TestMaxConnections (0.13s)

테스트가 통과되었습니다. read: connection reset by peer라는 것은 Mysql이 연결을 끊고 있을 때 표시됩니다. 이를 통해 연결 수가 너무 많아서 연결이 끊어졌다는 것을 알 수 있습니다.

연결 풀링을 설정한 테스트 수행하기


방금 전 테스트와 마찬가지로 200개의 골루틴을 생성하고, 각각의 골루틴이 쿼리를 실행하도록 되어 있습니다. 앞서 테스트와 다른 점은 연결 풀링을 설정했다는 점입니다. 커넥션 풀링을 설정함으로써 오류가 발생하지 않고 테스트가 종료되는 것을 확인합니다.

 

func TestConnectionPooling(t *testing.T) {
	db, err := sql.Open("mysql", "root:pass@(localhost:3306)/testdb")
	assert.Nil(t, err)
	assert.NotNil(t, db)
	defer db.Close()

	db.SetMaxOpenConns(10)                  // DBへの最大接続数を10に設定
	db.SetMaxIdleConns(5)                   // アイドル状態のコネクション数を5に設定
	db.SetConnMaxLifetime(10 * time.Minute) // コネクションの最大ライフタイムを10分に設定
	db.SetConnMaxIdleTime(5 * time.Minute)  // コネクションの最大アイドル時間を5分に設定

	var wg sync.WaitGroup
	var errCh = make(chan error)
	for i := 0; i < 200; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_, err := db.Exec("SELECT 1")
			if err != nil {
				errCh <- err
			}
		}()
	}

	go func() {
		wg.Wait()
		close(errCh)
	}()

	// エラーが発生しないことを確認する
	for err := range errCh {
		assert.Nil(t, err)
	}
}

다음은 테스트 실행 결과입니다.

=== RUN   TestConnectionPooling
--- PASS: TestConnectionPooling (0.03s)

 

'GO' 카테고리의 다른 글

API 서버에서의 오류 설계  (0) 2023.03.13
Golang sync.Pool 기본 원리  (0) 2023.03.03
고루틴??  (0) 2023.02.23
Go 리플렉션 성능이슈  (0) 2022.11.16
GO 백엔드 개발자 학습 로드맵  (0) 2022.11.16