Primrose Docs
  • Home
    • ⛓️Blockchain
      • Avalanche
        • What is AVAX?
      • Ethereum
        • Ethereum Cancun Upgrade Explained(draft)
        • go-ethereum: gas estimate
        • Blockchain Transaction Lifecycle
        • Mempool
        • Gas optimization in Solidity, Ethereum
      • Solidity DeepDive
        • Meta transaction
        • solidity: patterns
        • UUPS vs Transparent
        • Solidity Interface
        • Smart contract storage
        • ERC-2981 Contract
        • Solidity modifier
        • Solidity delete keyword
        • How To Make NFTs with On-Chain Metadata - Hardhat and JavaScript
        • How to Build "Buy Me a Coffee" DeFi dapp
        • How to Develop an NFT Smart Contract (ERC 721) with Alchemy
        • Upgradeable Contract
        • Smart Contract Verification
      • Common
        • Eigenlayer
        • MultiSig(draft)
        • Chain-Based Proof-of- Stake, BFT-Style Proof-of-Stake
        • Byzantine Fault Tolerance
        • Zero-knowledge
        • Hierarchical Deterministic Wallet
        • Maker DAO
        • Defi
        • Uniswap
        • IBC
        • Cosmos
        • Gossip Protocol
        • Tendermint
        • UTXO vs Account
        • Blockchain Layer
        • Consensus Algorithm
        • How does mining work?
        • Immutable Ledger
        • SHA256 Hash
        • Filecoin
        • IPFS - InterPlanetary File System
        • IPFS와 파일코인
        • Livepeer
        • Layer 0
      • Bitcoin
        • BIP for HD Wallet
        • P2WPKH
        • Segwit vs Native Segwit
    • 📖Languages
      • Javascript/Typescript
        • Hoisting
        • This value in Javascript
        • Execution Context
        • About Javscript
        • tsconfig.json
        • Nest js Provider
        • 'return await promise' vs 'return promise'
      • Python
        • Pythonic
        • Python: Iterable, Iterator
        • Uvicorn & Gunicorn
        • WSGI, ASGI
        • Python docstring
        • Decorator in Python
        • Namespace in Python
        • Python Method
      • Go
        • GORM+MySQL Connection Pool
        • Context in golang
        • How to sign Ethereum EIP-1559 transactions using AWS KMS
        • Mongo DB in golang(draft)
        • Golang HTTP Package
        • Panic
        • Golang new/make
        • golang container package
        • errgroup in golang
        • Generic Programming in Golang
        • Goroutine(draft)
    • 📝Database
      • MongoDB in golang
      • Nested loop join, Hash join
      • DB Query plan
      • Index
      • Optimistic Lock Pessimistic Lock
    • 💻Computer Science
      • N+1 query in go
      • Web server 를 구성할 때 Thread, Process 개수를 어떻게 정할 것인가?
      • CAP
      • Socket programming
      • DNS, IP
      • URL, URI
      • TLS과 SSL
      • Caching(draft)
      • Building Microservices: Micro Service 5 Deploy Principle
      • Red Black Tree
      • AOP
      • Distributed Lock
      • VPC
      • Docker
      • All about Session and JWT
      • Closure
      • Singleton Pattern
      • TCP 3 way handshake & 4 way handshake
      • Race Condition
      • Process Address Space 
      • Call by value, Call by reference, Call by assignment
      • Zookeeper, ETCD
      • URL Shortening
      • Raft consensus
      • Sharding, Partitioning
    • 📒ETC
      • K8S SIGTERM
      • SQS
      • Git Branch Strategy: Ship / Show / Ask
      • Kafka
      • Redis Data Types
      • CI/CD
      • How does Google design APIs?
      • Minishell (42 cursus)
      • Coroutine & Subroutine
      • Redis
Powered by GitBook
On this page
  • GORM 의 Connection pool
  • 참고 링크
  1. Home
  2. Languages
  3. Go

GORM+MySQL Connection Pool

GORM 의 Connection pool

회사에서 마주친 에러

$ [mysql] 2024/03/11 18:37:09 packets.go:122: closing bad idle connection: unexpected read from socket
$ [mysql] 2024/03/11 18:37:09 packets.go:122: closing bad idle connection: unexpected read from socket

Batch 처리를 요하는 작업들이 모여있는 인스턴스에서 발생했다.

비즈니스 로직 자체에는 문제가 없었고, 커넥션 관련 설정도 디폴트로 되어있는 상황이었다.

사실 서비스 전체적으로 보면 MySQL 자체를 많이 쓰는 상황은 아니었고, 동시에 실행되는 job 이 MySQL 기반이었어서 커넥션 관련 문제로 좁혀놓고 디버깅을 시작했다.

이 과정에서 새롭게 알게된 사실에 대한 기록.

MaxLifeTime

gorm 은 sql 패키지의 DB 객체를 wrapping 하고 있다.

sql.DB 구조체에는 Stats 라는 메소드가 있다.

var db := &sql.DB{}
		
stats := db.Stats() // stats ?

Stats 구조체는 아래와 같이 생겼다.

// DBStats contains database statistics.
type DBStats struct {
	MaxOpenConnections int // Maximum number of open connections to the database.

	// Pool Status
	OpenConnections int // The number of established connections both in use and idle.
	InUse           int // The number of connections currently in use.
	Idle            int // The number of idle connections.

	// Counters
	WaitCount         int64         // The total number of connections waited for.
	WaitDuration      time.Duration // The total time blocked waiting for a new connection.
	MaxIdleClosed     int64         // The total number of connections closed due to SetMaxIdleConns.
	MaxIdleTimeClosed int64         // The total number of connections closed due to SetConnMaxIdleTime.
	MaxLifetimeClosed int64         // The total number of connections closed due to SetConnMaxLifetime.
}

위에서 부터 읽어보자.

  • 최대 커넥션 개수

  • 현재 열려있는 커넥션 개수

  • 사용중인 커넥션 개수

  • 유휴 커넥션 개수

  • 커넥션을 대기하는 개수

  • 총 대기 시간

  • SetMaxIdleConns 로 인해 닫힌 유휴 커넥션 개수

  • SetConnMaxIdleTime 로 인해 닫힌 유휴 커넥션 개수

  • SetConnMaxLifetime 로 인해 닫힌 커넥션 개수

go 의 *sql.DB 구조체에서는 아래와 같이 설정들을 커스터마이징 할 수 있다.

// 값은 예시 
db.SetConnMaxLifetime(time.Hour)
db.SetMaxIdleConns(100)
db.SetConnMaxIdleTime(time.Hour)
db.SetMaxOpenConns(100)

참고로 기본값은 0으로 들어가는데, 이 경우 제한이 없다는 말이다.

물론 제한이 없다고해서 커넥션을 무한대로 만들어낼수는 없다. 또한 어지간한 경우에는 비즈니스 로직을 먼저 검토해보는 것이 좋다.

GORM 에서는 아래와 같이 커스터마이징 할 것을 권고하고 있다.

// Get generic database object sql.DB to use its functions
sqlDB, err := db.DB()
// SetMaxIdleConns sets the maximum number of connections in the idle connection 
pool.sqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns sets the maximum number of open connections to the 
database.sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
sqlDB.SetConnMaxLifetime(time.Hour)

위에서 언급했듯이, 커넥션 관련 문제로 보고 모니터링을 시작했다.

주기적으로 Stats 를 찍어봤을때 MaxConn 이 많아도 20을 넘어가지 않았고, 특이사항으로 보기에는 어려웠다.

다만 특정 작업이 실행될때마다 OpenConn 이 늘어나고 줄어들지 않는 문제가 발견되었다.

이유는 다음과 같았다.

var db := &gorm.DB{}
	
	
db.Transaction(func(tx *gorm.DB) error {		
	db.Where("~~~").Find(&Something{})
})

트랜잭션 안에서 트랜잭션 대신 다른 커넥션을 계속해서 집어오는 레거시가 있었다.

이러한 Job 이 실행되고 나면 커넥션 개수가 하나 늘어나는데, 이런 작업이 몇 개가 있다보니 일종의 커넥션 누수가 발생한 것으로 추정했다.

특이한 것은 강제로 재실행을 했을때 SavePoint 로그가 찍히는 것도 아니고, 에러가 찍힌 뒤에 실행이 되지 않는 경우와 실행이 되는 경우 등 불규칙적인 행태를 보인다는 것이다.

우선 해당 레거시를 모두 수정하고, MaxConnLifetime 을 한 시간으로 설정해서 계속해서 좀비 커넥션이 계속해서 살아남는 것을 방지했다.

아직 작업은 못했지만 추후 커넥션 풀에서 하나씩 폴링해서 받아오는 구조로 변경을 고려중이다.

참고 링크

PreviousGoNextContext in golang

Last updated 1 year ago

📖
https://github.com/go-sql-driver/mysql/issues/1120
https://gorm.io/docs/generic_interface.html