Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 17441] ファイルの概要

このコミットは、Go言語の database/sql パッケージにおける以前の変更(CL 10726044 / c9bea548fb6f)を元に戻すものです。この元に戻された変更は、データベース接続プールの管理に関するもので、特に SetMaxOpenConns 機能の追加と、アイドル接続プールの実装変更(スライスベースからリストベースへ)を含んでいました。しかし、この変更はビルドを壊し、さらに競合状態(race condition)を引き起こす問題があったため、このコミットで元に戻されました。

コミット

commit 9456adb36b3731e1e4accadddd7cd2abf5a911b8
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Aug 29 17:26:00 2013 -0700

    undo CL 10726044 / c9bea548fb6f
    
    Breaks build, and has a race.
    
    ««« original CL description
    database/sql: add SetMaxOpenConns
    
    Update #4805
    
    Add the ability to set an open connection limit.
    Fixed case where the Conn finalCloser was being called with db.mu locked.
    Added seperate benchmarks for each path for Exec and Query.
    Replaced slice based idle pool with list based idle pool.
    
    R=bradfitz
    CC=golang-dev
    https://golang.org/cl/10726044
    
    »»»
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/13252046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/9456adb36b3731e1e4accadddd7cd2abf5a911b8

元コミット内容

このコミットによって元に戻された元のコミット (CL 10726044 / c9bea548fb6f) の内容は以下の通りです。

  • database/sql パッケージに SetMaxOpenConns 関数を追加し、オープンなデータベース接続の最大数を設定できるようにする機能。
  • ConnfinalCloserdb.mu (データベースミューテックス) がロックされた状態で呼び出されるケースを修正。
  • ExecQuery の各パスに対して個別のベンチマークを追加。
  • アイドル接続プールをスライスベースの実装からリストベースの実装に置き換え。

変更の背景

このコミットの背景には、Go言語の database/sql パッケージにおけるデータベース接続管理の改善試みがあります。元のコミット (CL 10726044) は、特にオープンな接続数の制限機能 (SetMaxOpenConns) を導入し、接続プールの効率を向上させることを目的としていました。これは、Issue #4805 で報告された、データベース接続の過剰なオープンによるリソース枯渇やパフォーマンス問題に対処するための一環でした。

しかし、この変更は以下の問題を引き起こしました。

  1. ビルドの破損 (Breaks build): 変更がGoのビルドシステムと互換性がなく、コンパイルエラーなどを引き起こした可能性があります。
  2. 競合状態 (Has a race): 導入されたコードに、複数のゴルーチンが同時に同じリソースにアクセスしようとした際に、予期せぬ動作やデータ破損を引き起こす可能性のある競合状態が含まれていました。これは、特に並行処理が重要なデータベース接続プールにおいて致命的な問題となります。

これらの問題が発見されたため、安定性と信頼性を確保するために、元の変更を完全に元に戻すことが決定されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびデータベース関連の概念に関する知識が必要です。

  • database/sql パッケージ: Go言語の標準ライブラリの一部で、SQLデータベースとの対話のための汎用インターフェースを提供します。このパッケージ自体は特定のデータベースドライバを含まず、ドライバは別途インポートして使用します。
  • データベース接続プール: データベースへの接続はリソースを消費し、確立に時間がかかるため、アプリケーションは通常、接続を再利用するために接続プールを使用します。接続プールは、使用可能な接続のキャッシュを維持し、必要に応じてアプリケーションに提供します。
  • アイドル接続 (Idle Connections): 使用中でないが、将来の再利用のためにプールに保持されているデータベース接続。
  • オープン接続 (Open Connections): 現在アクティブに使用されているか、アイドル状態であってもプールに保持されているすべてのデータベース接続。
  • SetMaxOpenConns: データベース接続プールが同時に保持できるオープン接続の最大数を設定する機能。これにより、データベースサーバーへの負荷を制御し、リソースの枯渇を防ぐことができます。
  • SetMaxIdleConns: データベース接続プールが保持できるアイドル接続の最大数を設定する機能。これにより、接続の再利用を促進し、接続確立のオーバーヘッドを削減できます。
  • ミューテックス (sync.Mutex): 複数のゴルーチンが共有リソースに同時にアクセスするのを防ぐための同期プリミティブ。これにより、競合状態を防ぎ、データの整合性を保ちます。
  • 競合状態 (Race Condition): 複数のゴルーチンが共有リソースに同時にアクセスし、そのアクセス順序によってプログラムの最終結果が変わってしまうバグ。デバッグが困難なことで知られています。
  • container/list パッケージ: Go言語の標準ライブラリで、双方向リンクリストの実装を提供します。
  • スライス (Slice): Go言語の動的配列。連続したメモリ領域に要素を格納します。
  • finalCloser: リソースが最終的にクローズされる際に呼び出される関数やメカニズム。
  • driver.Conn: database/sql/driver パッケージで定義されているインターフェースで、データベースへの単一の接続を表します。

技術的詳細

このコミットは、以前の変更によって導入された database/sql パッケージの主要なデータ構造とロジックを元に戻しています。

元に戻された主な変更点:

  1. DB 構造体の変更:
    • DB 構造体から list.List 型の freeConn (アイドル接続プール) と connRequests (接続要求キュー) が削除され、代わりにスライスベースの freeConn []*driverConn に戻されました。
    • numOpen, pendingOpens, openerCh, maxOpen といった、オープン接続数の制限と非同期的な接続オープンに関連するフィールドが削除されました。
    • connectionRequestQueueSize 定数も削除されました。
  2. 接続プールのロジックの変更:
    • DB.connectionOpener() ゴルーチンと、それに関連する openerCh を介した非同期的な接続オープンロジックが削除されました。
    • DB.maybeOpenNewConnections() 関数も削除されました。
    • DB.conn() メソッド内の、maxOpen 制限と connRequests を使用した接続取得ロジックが削除され、シンプルなアイドル接続の取得または新規接続のオープンに戻されました。
    • DB.putConnDBLocked() 関数が削除され、DB.putConn() が直接アイドル接続プールへの追加または接続のクローズを行うようになりました。
  3. SetMaxOpenConns の削除:
    • DB.SetMaxOpenConns() メソッドが完全に削除されました。これにより、database/sql パッケージはオープン接続数の上限を直接管理する機能を持たなくなりました(これはドライバの実装に依存する形に戻ります)。
  4. Close メソッドの変更:
    • DB.Close() メソッドのロジックが簡素化され、openerCh のクローズや connRequests の処理が削除されました。アイドル接続を閉じる処理は、スライスベースの freeConn を直接イテレートする形に戻りました。
  5. driverConn 構造体の変更:
    • driverConn 構造体から listElem *list.Element フィールドが削除されました。これは、アイドル接続プールがリンクリストではなくスライスで管理されるようになったためです。
  6. テストコードの変更:
    • TestMaxOpenConns テストが削除されました。
    • TestConcurrency および関連するベンチマークテストから、リンクリストベースの接続プールと SetMaxOpenConns に依存する並行テストの構造が削除されました。
    • fakedb_test.go における NOSERT コマンドと doInsert パラメータの削除。これは、元のコミットで導入された並行テスト用の特殊な挿入ロジックが不要になったためです。

このロールバックにより、database/sql パッケージは、以前の、よりシンプルな接続管理モデルに戻りました。これは、複雑な接続プール管理ロジックがビルドの破損や競合状態を引き起こしたため、安定性を優先した結果と言えます。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は、主に src/pkg/database/sql/sql.go ファイルに集中しています。

  • src/pkg/database/sql/sql.go:

    • import 文から "container/list" が削除。
    • DB 構造体から freeConn *list.List, connRequests *list.List, numOpen int, pendingOpens int, openerCh chan struct{}, maxOpen int フィールドが削除され、freeConn []*driverConn に戻された。
    • driverConn 構造体から listElem *list.Element フィールドが削除された。
    • connectionRequestQueueSize 変数が削除された。
    • Open 関数における DB の初期化で、openerChconnRequests の初期化が削除された。
    • Close 関数内のリンクリストと openerCh に関連するロジックが削除され、スライスベースの freeConn を直接処理する形に戻された。
    • SetMaxIdleConns 関数内のリンクリスト操作がスライス操作に置き換えられた。
    • SetMaxOpenConns 関数が完全に削除された。
    • maybeOpenNewConnections 関数が削除された。
    • connectionOpener 関数が削除された。
    • openNewConnection 関数が削除された。
    • connRequest 型の定義が削除された。
    • conn 関数内の、maxOpen 制限と connRequests を使用した複雑な接続取得ロジックが削除され、シンプルなスライスベースのアイドル接続取得または新規接続オープンに戻された。
    • connIfFree 関数内のリンクリスト操作がスライス操作に置き換えられた。
    • putConn 関数内の numOpen のデクリメントと maybeOpenNewConnections の呼び出しが削除された。
    • putConnDBLocked 関数が削除された。
  • src/pkg/database/sql/fakedb_test.go:

    • fakeConn.Prepare および fakeStmt.Exec から NOSERT コマンドの処理が削除された。
    • fakeStmt.execInsert 関数から doInsert パラメータが削除され、常に挿入を行うように変更された。
  • src/pkg/database/sql/sql_test.go:

    • import 文から "math/rand" が削除された。
    • putConnHook 内の listElem のチェックがスライス内の要素チェックに置き換えられた。
    • closeDB 関数内のリンクリスト操作がスライス操作に置き換えられた。
    • numPrepares 関数内のリンクリスト操作がスライス操作に置き換えられた。
    • numFreeConns 関数内のリンクリスト操作がスライス操作に置き換えられた。
    • TestMaxOpenConns テスト関数が完全に削除された。
    • TestConcurrency および関連する BenchmarkConcurrent* テスト関数が大幅に簡素化され、リンクリストベースの接続プールと SetMaxOpenConns に依存するテストロジックが削除された。

コアとなるコードの解説

このコミットの核心は、database/sql パッケージの接続プール管理メカニズムを、リンクリストベースの複雑な実装から、よりシンプルでスライスベースの実装へと巻き戻した点にあります。

DB 構造体と接続プール:

元の変更では、DB 構造体は container/list.List を使用してアイドル接続 (freeConn) と接続要求 (connRequests) を管理していました。また、maxOpen というフィールドでオープン接続の最大数を制限し、openerChconnectionOpener ゴルーチンを使って非同期的に新しい接続を開くメカニズムを持っていました。

このコミットでは、これらの複雑なメカニズムがすべて削除されました。freeConn は単純な []*driverConn (スライス) に戻され、接続の追加や削除はスライスの操作(append やスライスを再スライスする)によって行われます。これにより、接続プールの管理が大幅に簡素化されました。

SetMaxOpenConns の削除:

SetMaxOpenConns は、データベースへの同時接続数を制限するための重要な機能でしたが、このコミットで削除されました。これは、この機能の実装がビルドの破損や競合状態を引き起こしたため、一時的にこの機能を取り除くことでパッケージの安定性を回復させることを目的としています。この変更後、オープン接続数の制限は、Goの database/sql パッケージ自体ではなく、基盤となるデータベースドライバやデータベースサーバーの設定に依存することになります。

conn() メソッドの簡素化:

conn() メソッドは、データベース接続を取得する主要な関数です。元の変更では、このメソッドは maxOpen 制限を考慮し、必要に応じて connRequests を介して新しい接続のオープンを要求する複雑なロジックを持っていました。ロールバック後、conn() はまずアイドル接続プール (db.freeConn スライス) から利用可能な接続を探し、見つからなければ直接新しい接続を開くという、より直接的なアプローチに戻りました。

競合状態の解消:

元のコミットが「競合状態を持つ」と明記されていることから、特に db.mu (データベースミューテックス) のロックとアンロックのタイミング、および複数のゴルーチンが接続プールにアクセスする際の同期に問題があったと推測されます。このロールバックにより、複雑な非同期処理やリンクリスト操作が排除されたことで、これらの競合状態が解消され、パッケージの堅牢性が向上しました。

全体として、このコミットは、機能追加よりも安定性と正確性を優先した、典型的な「リバートコミット」の例です。問題のあるコードを迅速に削除し、既知の安定した状態に戻すことで、さらなる問題の発生を防ぎ、将来の修正のためのクリーンな基盤を提供します。

関連リンク

参考にした情報源リンク

  • Go database/sql パッケージのドキュメント: https://pkg.go.dev/database/sql
  • Go sync パッケージのドキュメント (Mutex): https://pkg.go.dev/sync
  • Go container/list パッケージのドキュメント: https://pkg.go.dev/container/list
  • Go言語における競合状態と同期プリミティブに関する一般的な情報源 (例: Go Concurrency Patterns, Go Memory Modelなど)
  • Go言語のコードレビューシステム (Gerrit) の使い方に関する情報