[インデックス 15822] ファイルの概要
このコミットは、Go言語の標準ライブラリである database/sql
パッケージに、データベース接続のアイドル接続プールにおける最大接続数を設定する DB.SetMaxIdleConns
メソッドを追加するものです。これにより、アプリケーション開発者はデータベース接続の管理をより細かく制御できるようになり、リソースの効率的な利用とパフォーマンスの向上が期待されます。
コミット
commit 3a2fe62f44a8a8513a087f75798425db7f9cc7bd
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Mar 18 15:33:04 2013 -0700
database/sql: add DB.SetMaxIdleConns
Update #4805
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7634045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3a2fe62f44a8a8513a087f75798425db7f9cc7bd
元コミット内容
このコミットは、database/sql
パッケージに DB.SetMaxIdleConns
メソッドを追加し、アイドル状態のデータベース接続の最大数を設定できるようにします。これにより、Goアプリケーションがデータベースとの接続を管理する方法に柔軟性がもたらされます。
変更の背景
Goの database/sql
パッケージは、データベースとのやり取りを抽象化し、接続プールを内部で管理しています。しかし、このコミット以前は、アイドル状態の接続をプールに保持する最大数(maxIdleConns
)がハードコードされており、開発者がこの値を変更する手段がありませんでした。
問題点としては、以下の点が挙げられます。
- リソースの非効率な利用: デフォルトのアイドル接続数がアプリケーションの負荷やデータベースの特性に合わない場合、不要な接続が長時間保持されたり、逆に接続の再確立が頻繁に発生したりして、リソースの無駄やパフォーマンスの低下を招く可能性がありました。
- データベース側の制約: データベースによっては、同時に保持できる接続数に上限がある場合があります。アプリケーションがその上限を超えてアイドル接続を保持しようとすると、エラーが発生したり、データベースのパフォーマンスに悪影響を与えたりする可能性がありました。
- パフォーマンスの最適化の欠如: アプリケーションの特性(例: 短期間に大量のクエリを実行するバッチ処理、長時間接続を維持するサービスなど)に応じて、アイドル接続数を調整することで、接続の確立・切断にかかるオーバーヘッドを削減し、全体的なパフォーマンスを向上させることができます。
このコミットは、これらの課題を解決するために、開発者がプログラムからアイドル接続数を動的に設定できるようにする SetMaxIdleConns
メソッドを導入しました。これは、GoのIssue #4805 に対応するものです。
前提知識の解説
データベース接続プール (Connection Pool)
データベース接続プールは、アプリケーションがデータベースに接続する際に、接続の確立と切断にかかるオーバーヘッドを削減するための技術です。アプリケーションがデータベースにアクセスするたびに新しい接続を確立するのではなく、事前に一定数の接続を作成し、それらをプール(貯蔵庫)に保持しておきます。
- 利点:
- パフォーマンス向上: 接続の確立はコストの高い操作であるため、これを再利用することで応答時間を短縮します。
- リソース管理: データベースへの同時接続数を制限し、データベースのリソース枯渇を防ぎます。
- スケーラビリティ: 多数のリクエストを効率的に処理できるようになります。
アイドル接続 (Idle Connections)
接続プール内の接続には、使用中の接続とアイドル状態の接続があります。アイドル接続とは、現在どのアプリケーションリクエストにも使用されていないが、プール内に保持されており、将来のリクエストのために再利用できる状態にある接続のことです。
maxIdleConns
: アイドル接続プールに保持できるアイドル接続の最大数です。この値を超えると、最も古いアイドル接続が閉じられます。maxOpenConns
: 接続プール全体で同時に開いておくことができる接続の最大数です。これには使用中の接続とアイドル接続の両方が含まれます。
これらの設定は、アプリケーションの負荷パターン、データベースの性能、ネットワークのレイテンシなどを考慮して適切に調整する必要があります。
Goの database/sql
パッケージ
database/sql
パッケージは、Go言語でSQLデータベースを操作するための汎用的なインターフェースを提供します。このパッケージ自体は特定のデータベースドライバを含まず、driver.Driver
インターフェースを実装する外部のデータベースドライバと組み合わせて使用されます。
DB
構造体: データベースへの接続プールを管理する主要な構造体です。Open
関数: データベースドライバ名とデータソース名(DSN)を指定してDB
オブジェクトを返します。Query
,Exec
などのメソッド: SQLクエリの実行に使用されます。
技術的詳細
このコミットでは、database/sql
パッケージの DB
構造体と関連するメソッドに以下の変更が加えられています。
-
DB
構造体へのmaxIdle
フィールドの追加:DB
構造体にmaxIdle int
フィールドが追加されました。zero means defaultMaxIdleConns
:maxIdle
が0
の場合、デフォルトのアイドル接続数 (defaultMaxIdleConns
、このコミットでは2
) が使用されます。negative means 0
:maxIdle
が負の値の場合、アイドル接続は保持されません(つまり、アイドル接続数は0
となります)。- 正の値は、設定された最大アイドル接続数を示します。
-
maxIdleConnsLocked()
メソッドの導入: 既存のmaxIdleConns()
関数がmaxIdleConnsLocked()
に変更され、DB
構造体のmaxIdle
フィールドの値を考慮して、実際に使用される最大アイドル接続数を返すようになりました。n == 0
の場合:defaultMaxIdleConns
(2) を返します。n < 0
の場合:0
を返します。- それ以外の場合 (
n > 0
):n
の値をそのまま返します。
-
SetMaxIdleConns(n int)
メソッドの追加:DB
構造体に新しいパブリックメソッドSetMaxIdleConns
が追加されました。このメソッドは、アイドル接続プールに保持する最大接続数を設定します。- メソッドは
db.mu.Lock()
とdefer db.mu.Unlock()
を使用して、DB
構造体のミューテックスをロックし、並行アクセスから保護します。 - 引数
n
が0
より大きい場合、db.maxIdle
にn
が設定されます。 - 引数
n
が0
以下の場合、db.maxIdle
に-1
が設定されます。これはmaxIdleConnsLocked()
メソッドによって0
として解釈され、アイドル接続が保持されないことを意味します。 - 設定後、既存のアイドル接続数が新しい
maxIdle
の値を超えている場合、超過した接続は閉じられます。これは、db.freeConn
スライスから接続を取り出し、それぞれのdriverConn
のClose()
メソッドをゴルーチン内で呼び出すことで行われます。これにより、接続のクローズ処理がブロックされず、非同期に行われます。
- メソッドは
-
putConn
メソッドの変更:putConn
メソッド内でアイドル接続数をチェックする箇所が、db.maxIdleConns()
からdb.maxIdleConnsLocked()
に変更されました。これにより、SetMaxIdleConns
で設定された新しい最大アイドル接続数が適切に反映されるようになります。
コアとなるコードの変更箇所
src/pkg/database/sql/sql.go
--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -197,6 +197,7 @@ type DB struct {
dep map[finalCloser]depSet
onConnPut map[*driverConn][]func() // code (with mu held) run when conn is next returned
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
+ maxIdle int // zero means defaultMaxIdleConns; negative means 0
}
// driverConn wraps a driver.Conn with a mutex, to
@@ -332,11 +333,45 @@ func (db *DB) Close() error {
return err
}
-func (db *DB) maxIdleConns() int {
- const defaultMaxIdleConns = 2
- // TODO(bradfitz): ask driver, if supported, for its default preference
- // TODO(bradfitz): let users override?
- return defaultMaxIdleConns
+const defaultMaxIdleConns = 2
+
+func (db *DB) maxIdleConnsLocked() int {
+ n := db.maxIdle
+ switch {
+ case n == 0:
+ // TODO(bradfitz): ask driver, if supported, for its default preference
+ return defaultMaxIdleConns
+ case n < 0:
+ return 0
+ default:
+ return n
+ }
+}
+
+// SetMaxIdleConns sets the maximum number of connections in the idle
+// connection pool.
+//
+// If n <= 0, no idle connections are retained.
+func (db *DB) SetMaxIdleConns(n int) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+ if n > 0 {
+ db.maxIdle = n
+ } else {
+ // No idle connections.
+ db.maxIdle = -1
+ }
+ for len(db.freeConn) > 0 && len(db.freeConn) > n {
+ nfree := len(db.freeConn)
+ dc := db.freeConn[nfree-1]
+ db.freeConn[nfree-1] = nil
+ db.freeConn = db.freeConn[:nfree-1]
+ go func() {
+ dc.Lock()
+ dc.ci.Close()
+ dc.Unlock()
+ }()
+ }
}
// conn returns a newly-opened or cached *driverConn
@@ -441,7 +476,7 @@ func (db *DB) putConn(dc *driverConn, err error) {
if putConnHook != nil {
putConnHook(db, dc)
}\n-\tif n := len(db.freeConn); !db.closed && n < db.maxIdleConns() {
+\tif n := len(db.freeConn); !db.closed && n < db.maxIdleConnsLocked() {
db.freeConn = append(db.freeConn, dc)
db.mu.Unlock()
return
src/pkg/database/sql/sql_test.go
--- a/src/pkg/database/sql/sql_test.go
+++ b/src/pkg/database/sql/sql_test.go
@@ -761,3 +761,32 @@ func TestSimultaneousQueries(t *testing.T) {
}\n \tdefer r2.Close()\n }\n+\n+func TestMaxIdleConns(t *testing.T) {\n+\tdb := newTestDB(t, "people")\n+\tdefer closeDB(t, db)\n+\n+\ttx, err := db.Begin()\n+\tif err != nil {\n+\t\tt.Fatal(err)\n+\t}\n+\ttx.Commit()\n+\tif got := len(db.freeConn); got != 1 {\n+\t\tt.Errorf("freeConns = %d; want 1", got)\n+\t}\n+\n+\tdb.SetMaxIdleConns(0)\n+\n+\tif got := len(db.freeConn); got != 0 {\n+\t\tt.Errorf("freeConns after set to zero = %d; want 0", got)\n+\t}\n+\n+\ttx, err = db.Begin()\n+\tif err != nil {\n+\t\tt.Fatal(err)\n+\t}\n+\ttx.Commit()\n+\tif got := len(db.freeConn); got != 0 {\n+\t\tt.Errorf("freeConns = %d; want 0", got)\n+\t}\n+}\n```
## コアとなるコードの解説
### `DB` 構造体への `maxIdle` フィールドの追加
`DB` 構造体はデータベース接続プール全体の状態を管理します。`maxIdle` フィールドの追加により、この構造体がアイドル接続数の設定値を保持できるようになりました。
```go
type DB struct {
// ... 既存のフィールド ...
maxIdle int // zero means defaultMaxIdleConns; negative means 0
}
maxIdleConnsLocked()
メソッド
このメソッドは、DB
構造体の maxIdle
フィールドの値に基づいて、実際に使用されるアイドル接続の最大数を決定します。これにより、デフォルト値、ゼロ、またはユーザーが設定した値のいずれかを柔軟に適用できます。
const defaultMaxIdleConns = 2
func (db *DB) maxIdleConnsLocked() int {
n := db.maxIdle
switch {
case n == 0:
// TODO(bradfitz): ask driver, if supported, for its default preference
return defaultMaxIdleConns
case n < 0:
return 0
default:
return n
}
}
SetMaxIdleConns(n int)
メソッド
このメソッドは、ユーザーがアイドル接続数を設定するための主要なインターフェースです。ミューテックス (db.mu
) を使用してスレッドセーフティを確保し、設定値に応じて db.maxIdle
を更新します。特に重要なのは、新しい設定値が現在のアイドル接続数よりも小さい場合に、余分なアイドル接続を閉じるロジックです。これにより、設定変更が即座に反映され、リソースが適切に解放されます。
func (db *DB) SetMaxIdleConns(n int) {
db.mu.Lock()
defer db.mu.Unlock()
if n > 0 {
db.maxIdle = n
} else {
// No idle connections.
db.maxIdle = -1
}
// 現在のアイドル接続数が新しい最大値を超えている場合、余分な接続を閉じる
for len(db.freeConn) > 0 && len(db.freeConn) > n {
nfree := len(db.freeConn)
dc := db.freeConn[nfree-1] // 最も新しいアイドル接続を取得
db.freeConn[nfree-1] = nil
db.freeConn = db.freeConn[:nfree-1] // スライスから削除
go func() { // ゴルーチンで非同期に接続を閉じる
dc.Lock()
dc.ci.Close() // 実際のデータベース接続を閉じる
dc.Unlock()
}()
}
}
putConn
メソッドの変更
putConn
メソッドは、使用済みの接続をプールに戻す際に呼び出されます。この変更により、接続をプールに戻す際に、SetMaxIdleConns
で設定された新しい最大アイドル接続数が考慮されるようになりました。
// ...
if n := len(db.freeConn); !db.closed && n < db.maxIdleConnsLocked() {
db.freeConn = append(db.freeConn, dc)
db.mu.Unlock()
return
// ...
TestMaxIdleConns
テストケース
このテストケースは、SetMaxIdleConns
メソッドが正しく機能することを確認します。
newTestDB
でデータベース接続を作成し、トランザクションを開始・コミットすることで、1つのアイドル接続がプールに存在することを確認します。db.SetMaxIdleConns(0)
を呼び出し、アイドル接続数をゼロに設定します。これにより、既存のアイドル接続が閉じられ、プールが空になることを確認します。- 再度トランザクションを開始・コミットし、アイドル接続数が依然としてゼロであることを確認します。これは、
SetMaxIdleConns(0)
が新しい接続がプールされるのを防ぐことを意味します。
func TestMaxIdleConns(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
// 1. トランザクションで接続を使用し、プールに1つのアイドル接続があることを確認
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
tx.Commit()
if got := len(db.freeConn); got != 1 {
t.Errorf("freeConns = %d; want 1", got)
}
// 2. SetMaxIdleConns(0) を呼び出し、アイドル接続が閉じられることを確認
db.SetMaxIdleConns(0)
if got := len(db.freeConn); got != 0 {
t.Errorf("freeConns after set to zero = %d; want 0", got)
}
// 3. 再度トランザクションで接続を使用し、アイドル接続がプールされないことを確認
tx, err = db.Begin()
if err != nil {
t.Fatal(err)
}
tx.Commit()
if got := len(db.freeConn); got != 0 {
t.Errorf("freeConns = %d; want 0", got)
}
}
関連リンク
- Go Issue #4805: https://github.com/golang/go/issues/4805
- Go CL 7634045: https://golang.org/cl/7634045
参考にした情報源リンク
- Go
database/sql
パッケージのドキュメント: https://pkg.go.dev/database/sql - Goのデータベース接続プールに関する一般的な情報 (例:
SetMaxOpenConns
,SetConnMaxLifetime
など): - データベース接続プールの概念に関する一般的な情報: https://en.wikipedia.org/wiki/Connection_pool
- Goのコミット履歴と関連する議論 (GitHub Issues, Go CLs)