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

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

このコミットは、Go言語の標準ライブラリである database/sql パッケージにおける、データベース接続のエラーハンドリングに関する修正と、その修正を検証するためのテスト追加を目的としています。具体的には、database/sqldriver.ErrBadConnBegin メソッドが返した場合に、自動的にリトライを行うべきであることをテストで確認しています。

コミット

commit 19e2f26b21a8bb5f9cfed1808d25534d8fe88c47
Author: James David Chalfant <james.chalfant@gmail.com>
Date:   Fri Dec 14 09:00:33 2012 -0800

    database/sql: adds test for fix in issue 4433.
    
    Tests that here should be automatic retries if a database
    driver's connection returns ErrBadConn on Begin. See
    "TestTxErrBadConn" in sql_test.go
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/6942050

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

https://github.com/golang/go/commit/19e2f26b21a8bb5f9cfed1808d25534d8fe88c47

元コミット内容

このコミットは、database/sql パッケージにテストを追加するものです。このテストは、データベースドライバの Begin メソッドが driver.ErrBadConn を返した場合に、database/sql パッケージが自動的にトランザクションの開始をリトライするべきであるという振る舞いを検証します。これは、issue 4433 で報告された問題に対する修正をテストするためのものです。

変更の背景

Go言語の database/sql パッケージは、データベース操作のための汎用的なインターフェースを提供します。このパッケージは、様々なデータベースドライバ(MySQL, PostgreSQL, SQLiteなど)と連携して動作します。データベースとの接続は、ネットワークの不安定性、データベースサーバーの再起動、接続プールの問題など、様々な理由で一時的に無効になることがあります。

driver.ErrBadConn は、Goの database/sql/driver パッケージで定義されている特別なエラーです。このエラーは、ドライバがデータベースへの接続が不良であると判断した場合に返されます。例えば、ネットワーク接続が切断された、データベースサーバーがダウンした、または接続がタイムアウトしたなどの状況で発生します。

database/sql パッケージの設計思想として、一時的な接続不良は透過的に処理され、アプリケーション開発者がこれらの低レベルなエラーを直接扱う必要がないようにすることが望ましいとされています。特に、トランザクションを開始する Begin メソッドが ErrBadConn を返した場合、database/sql は新しい接続を確立してトランザクションの開始をリトライするべきです。これにより、アプリケーションは一時的な接続問題から回復し、より堅牢な動作が期待できます。

このコミットは、この自動リトライのメカニズムが正しく機能していることを確認するためのテストが不足していたため、そのテストを追加することを目的としています。コミットメッセージにある「issue 4433」は、この自動リトライが期待通りに動作しない、またはその振る舞いが明確にテストされていなかったという問題を示唆しています。

前提知識の解説

Go言語の database/sql パッケージ

database/sql パッケージは、Go言語でリレーショナルデータベースを操作するための標準インターフェースを提供します。このパッケージは、特定のデータベースシステムに依存しない抽象化レイヤーであり、実際のデータベースとの通信は、各データベースに対応する「ドライバ」によって行われます。

主要な概念:

  • sql.DB: データベースへの抽象的な接続プールを表します。複数のゴルーチンから安全に利用できます。
  • sql.Conn: データベースへの単一の物理的な接続を表します。通常、sql.DB から取得されます。
  • sql.Tx: データベーストランザクションを表します。sql.DB.Begin() または sql.Conn.Begin() から取得されます。
  • driver インターフェース: database/sql パッケージがデータベースドライバと通信するためのインターフェース群です。ドライバはこれらのインターフェースを実装することで、database/sql パッケージと連携します。

driver.ErrBadConn

database/sql/driver パッケージで定義されている特別なエラー変数です。ドライバがこのエラーを返した場合、database/sql パッケージは、その接続が再利用不可能であると判断し、接続プールからその接続を破棄します。そして、必要に応じて新しい接続を確立しようとします。

このエラーは、以下のような状況でドライバによって返されることが期待されます。

  • データベースサーバーとのネットワーク接続が切断された。
  • データベースサーバーが再起動した。
  • 接続がタイムアウトした。
  • 認証情報が無効になった。

database/sql パッケージは、driver.ErrBadConn が返された場合に、特定の操作(例: Query, Exec, Begin)を自動的にリトライするメカニズムを持っています。これにより、一時的な接続不良がアプリケーションに影響を与えるのを最小限に抑えます。

トランザクション (sql.Tx)

トランザクションは、一連のデータベース操作を単一の論理的な作業単位としてまとめるメカニズムです。トランザクション内のすべての操作は成功するか、すべて失敗するかのいずれかです(ACID特性)。 sql.DB.Begin() メソッドは、新しいトランザクションを開始します。このメソッドが呼び出された際に、database/sql は接続プールから利用可能な接続を取得し、その接続上でトランザクションを開始します。もしこの接続が不良であった場合、ドライバの Begin メソッドは driver.ErrBadConn を返す可能性があります。この場合、database/sql は別の接続を試みてリトライするべきです。

技術的詳細

このコミットは、database/sql パッケージの自動リトライメカニズムをテストするために、fakedb_test.go に偽のデータベースドライバ (fakeDriver, fakeDB, fakeConn) を拡張し、sql_test.go に新しいテストケース TestTxErrBadConn を追加しています。

fakedb_test.go の変更点

  1. fakeDB 構造体への badConn フィールドの追加:

    type fakeDB struct {
        name string
    
        mu      sync.Mutex
        free    []*fakeConn
        tables  map[string]*table
        badConn bool // 新しく追加されたフィールド
    }
    

    この badConn フィールドは、fakeDB が現在「不良な接続」の状態をシミュレートしているかどうかを追跡するために使用されます。

  2. fakeConn 構造体への bad フィールドの追加:

    type fakeConn struct {
        db          *fakeDB
        closed      bool
        stmtsMade   int
        stmtsClosed int
        numPrepare  int
        bad         bool // 新しく追加されたフィールド
    }
    

    この bad フィールドは、個々の fakeConn インスタンスが badConn オプション付きで開かれたかどうかを示します。

  3. fakeDriver.Open メソッドの拡張: DSN (Data Source Name) に ;badConn オプションが指定された場合、新しく作成される fakeConn インスタンスの bad フィールドが true に設定されるようになりました。

    // Supports dsn forms:
    //    <dbname>
    //    <dbname>;<opts>  (only currently supported option is `badConn`,
    //                      which causes driver.ErrBadConn to be returned on
    //                      every other conn.Begin())
    func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
        // ... (既存のDSNパースロジック) ...
        conn := &fakeConn{db: db}
        if len(parts) >= 2 && parts[1] == "badConn" {
            conn.bad = true
        }
        return conn, nil
    }
    

    これにより、テストケースで意図的に ErrBadConn を発生させるための接続を生成できるようになります。

  4. fakeConn.isBad() メソッドの追加: このヘルパーメソッドは、fakeConnbadConn オプション付きで開かれている場合に、fakeDBbadConn フィールドを交互に切り替えることで、ErrBadConn を返すタイミングを制御します。

    func (c *fakeConn) isBad() bool {
        // if not simulating bad conn, do nothing
        if !c.bad {
            return false
        }
        // alternate between bad conn and not bad conn
        c.db.badConn = !c.db.badConn
        return c.db.badConn
    }
    

    これにより、Begin() が呼び出されるたびに、ErrBadConn を返すか、正常に動作するかが交互に切り替わります。これは、database/sql がリトライを試みる際に、一度 ErrBadConn を受け取った後に、次の試行で正常な接続を得られる状況をシミュレートするために重要です。

  5. fakeConn.Begin() メソッドの変更: isBad() メソッドの結果に基づいて、driver.ErrBadConn を返すロジックが追加されました。

    func (c *fakeConn) Begin() (driver.Tx, error) {
        if c.isBad() {
            return nil, driver.ErrBadConn
        }
        // ... (既存のBeginロジック) ...
    }
    

    これにより、badConn オプション付きで開かれた接続の Begin メソッドが、交互に ErrBadConn を返すようになります。

sql_test.go の変更点

  1. TestTxErrBadConn テスト関数の追加: この新しいテスト関数は、database/sqlBegin メソッドで ErrBadConn を受け取った場合に、自動的にリトライが行われることを検証します。

    // Tests fix for issue 4433, that retries in Begin happen when
    // conn.Begin() returns ErrBadConn
    func TestTxErrBadConn(t *testing.T) {
        // 1. `badConn` オプション付きでデータベースを開く
        db, err := Open("test", fakeDBName+";badConn")
        if err != nil {
            t.Fatalf("Open: %v", err)
        }
        defer closeDB(t, db)
    
        // 2. テーブル作成などの初期設定
        exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
        stmt, err := db.Prepare("INSERT|t1|name=?,age=?")
        if err != nil {
            t.Fatalf("Stmt, err = %v, %v", stmt, err)
        }
        defer stmt.Close()
    
        // 3. トランザクションの開始
        // ここで `db.Begin()` が呼び出される。
        // 内部的には `fakeConn.Begin()` が呼び出され、
        // 最初の呼び出しでは `ErrBadConn` が返されるが、
        // `database/sql` が自動的にリトライし、
        // 2回目の呼び出しで正常な接続を得ることを期待する。
        tx, err := db.Begin()
        if err != nil {
            t.Fatalf("Begin = %v", err)
        }
        defer tx.Close() // トランザクションが正常に開始されたらクローズを予約
    
        // 4. トランザクション内での操作
        txs := tx.Stmt(stmt)
        defer txs.Close()
        _, err = txs.Exec("Bobby", 7)
        if err != nil {
            t.Fatalf("Exec = %v", err)
        }
    
        // 5. トランザクションのコミット
        err = tx.Commit()
        if err != nil {
            t.Fatalf("Commit = %v", err)
        }
    }
    

    このテストの重要な点は、db.Begin() の呼び出しです。fakeConn.Begin()badConn オプションが有効な場合、最初の呼び出しで driver.ErrBadConn を返します。しかし、database/sql パッケージがこのエラーを検知し、自動的にリトライを行うことで、テストは成功するはずです。もしリトライが行われなければ、db.Begin() はエラーを返し、テストは失敗します。

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

このコミットは、既存のコードの振る舞いを変更するものではなく、その振る舞いを検証するためのテストコードを追加・修正しています。

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

    • fakeDB 構造体に badConn bool フィールドを追加。
    • fakeConn 構造体に bad bool フィールドを追加。
    • fakeDriver.Open メソッドに、DSNの ;badConn オプションをパースし、fakeConn.bad を設定するロジックを追加。
    • fakeConn.isBad() ヘルパーメソッドを追加。
    • fakeConn.Begin() メソッドに、isBad() の結果に基づいて driver.ErrBadConn を返すロジックを追加。
  • src/pkg/database/sql/sql_test.go:

    • TestTxErrBadConn テスト関数を追加。このテストは、badConn オプション付きの偽のデータベース接続を使用し、db.Begin()driver.ErrBadConn を返しても、database/sql が自動的にリトライしてトランザクションを正常に開始できることを検証します。

コアとなるコードの解説

このコミットの核心は、database/sql パッケージが driver.ErrBadConn を適切に処理し、透過的なリトライメカニズムを提供していることを証明するテストの追加です。

fakedb_test.go での変更は、このテストを可能にするための「不良な接続」をシミュレートする機能を提供します。

  • fakeDB.badConnfakeConn.bad は、シミュレーションの状態を管理します。
  • fakeDriver.Open での ;badConn オプションのパースは、テストが特定のシナリオをトリガーできるようにします。
  • fakeConn.isBad() は、Begin() が呼び出されるたびに ErrBadConn を返すかどうかを交互に切り替えることで、database/sql がリトライを試みる状況を正確に模倣します。これにより、一度失敗した後に成功する可能性のある接続をシミュレートできます。
  • fakeConn.Begin() での driver.ErrBadConn の返却は、database/sql が処理すべき具体的なエラー条件を提供します。

sql_test.go での TestTxErrBadConn は、このシミュレーションされたエラー条件の下で database/sqlBegin メソッドが期待通りに動作することを確認します。テストが成功するということは、db.Begin() が内部的に fakeConn.Begin() から ErrBadConn を受け取ったにもかかわらず、database/sql がそれを検知し、新しい接続を試みてトランザクションを正常に開始できたことを意味します。これは、アプリケーションが一時的なデータベース接続の問題に対してより堅牢になることを保証します。

このテストの追加により、将来的に database/sql パッケージの内部実装が変更された場合でも、この重要なリトライ動作が維持されることが保証されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • database/sql および database/sql/driver パッケージのソースコード
  • Go言語のテストに関する一般的な知識
  • Gitのコミット履歴と差分表示
  • driver.ErrBadConn の挙動に関する一般的なデータベースドライバの知識