LoginSignup
1
0

More than 1 year has passed since last update.

【go-oci8】GolangでAutonomous Database (ADB) に複数同時接続させた時のORA-12529エラー解消方法

Last updated at Posted at 2021-10-23

経緯

Oracle Cloud Infrastructure (OCI) 上にある複数の Autonomous Database (ADB) にGoで同時接続させる必要があったが、2つ目以降のADB接続時に*ORA-12529エラーで接続タイムアウトされてしまい、非常に苦しめられた。
(SQL PLUSでは普通に接続できるので、接続文字列やtnsnames.oraの記載は間違っていない…)

*ORA-12529エラー (公式リファレンス)

補足情報 セキュリテイに関して特に問題なければ、*ADB間のデータベースリンクを作成した方が、複数ADBに接続する必要がなくなるので、ラクに解消できるかもしれない...

*ADB間のデータベースリンク (Qiita投稿@TakuyaAma「Autonomous Database間のDBLINKの自分用メモ」)

前提条件 (参考リンクあり)

改善前スクリプト

goroutineで処理を並列化して、2つのADB(atp1, atp2)に同時接続させ、同じクエリーを投げてそれぞれのPDB名を出力する。

なお、database/sqlパッケージの仕様として、sql.Open()の時点ではADBへの接続は確立されず、db.Query()の時に初めて接続される。

main.go
package main

import (
    "database/sql"
    "fmt"
    "sync"

    _ "github.com/mattn/go-oci8"
)

func main() {
    var wg sync.WaitGroup

    // connection strings
    atp1 := "admin/XXXXXXXX@atp1_low"
    atp2 := "admin/YYYYYYYY@atp2_low"

    for _, atp := range []string{atp1, atp2} {
        wg.Add(1)
        go func(atp string) {
            defer wg.Done()
            // create the sql instance (not connected yet!)
            db, err := sql.Open("oci8", atp)
            if err != nil {
                fmt.Printf("failed to open %v: %s\n", atp, err)
                return
            }
            fmt.Printf("ATP: Open (%v)\n", atp)

            // close the sql instance
            defer fmt.Printf("ATP: Closed (%v)\n", atp)
            defer db.Close() // after returned

            // throw a query (Error will be happened here!)
            rows, err := db.Query("select pdb_name from dba_pdbs")
            if err != nil {
                fmt.Printf("failed to throw the query: %s\n", err)
                return
            }
            defer rows.Close() // after returned

            // get the rows of thrown query
            for rows.Next() {
                var pdb_name string
                rows.Scan(&pdb_name) // put the value into the variable
                fmt.Printf("pdb_name: %v (%v)\n", pdb_name, atp)
            }
        }(atp)
    }
    wg.Wait()
}

改善前スクリプト実行結果

db.Query()で2つ目のADB(atp2)に接続できず、ORA-12529エラーでタイムアウトされてしまう。

$ go build -o go_connect_multiple_atp # main.goのビルド
$ chmod +x go_connect_multiple_atp    # 実行権限の付与
$ ./go_connect_multiple_atp           # ビルド済みバイナリファイルの実行
ATP: Open (admin/YYYYYYYY@atp2_low)
ATP: Open (admin/XXXXXXXX@atp1_low)

pdb_name: ABC123...XYZ_ATP1 (admin/XXXXXXXX@atp1_low)
ATP: Closed (admin/XXXXXXXX@atp1_low)

failed to throw the query: params=user=admin password="YYYYYYYY" connectString=atp2_low 
timezone= poolIncrement=1 poolMaxSessions=1000 poolMinSessions=1 poolSessionMaxLifetime=1h0m0s 
poolSessionTimeout=5m0s poolWaitTimeout=30s extAuth=0: 
ORA-12529: TNS: 現在のフィルタ・ルールに基づいて接続リクエストが拒否されました

原因

go-oci8では内部的にOracleクライアントを利用してADBに接続しているらしい。
そのため、2つ目以降のADBの各walletを参照させる際、直前に読み取ったwalletディレクトリパス情報が*sql.DBポインタ内にClose()後も残っており、適切なwalletディレクトリパスを参照できていなかったようだ。

(ポインタとdatabase/sqlパッケージに対する僕の理解不足がバレてしまう…)

特にクローン作成したADBなど、wallet間でtnsnames.ora及びsqlnet.ora内の情報が重複している場合は、以降の対処が必要となる。

対処方法

単純に、db.Close() でsqlインスタンスを閉じた後、*sql.DB型のポインタ変数dbにnilを代入して、ポインタの情報を完全にリリースさせる。

改善後スクリプト

defer db.Close() の前に、defer func() { db = nil }() をスタックさせておく。

main.go
package main

import (
    "database/sql"
    "fmt"
    "sync"

    _ "github.com/mattn/go-oci8"
)

func main() {
    var wg sync.WaitGroup

    // connection strings
    atp1 := "admin/XXXXXXXX@atp1_low"
    atp2 := "admin/YYYYYYYY@atp2_low"

    for _, atp := range []string{atp1, atp2} {
        wg.Add(1)
        go func(atp string) {
            defer wg.Done()
            // create the sql instance (not connected yet!)
            db, err := sql.Open("oci8", atp)
            if err != nil {
                fmt.Printf("failed to open %v: %s\n", atp, err)
                return
            }
            fmt.Printf("ATP: Open (%v)\n", atp)

            // * release the *sql.DB pointer as nil *
            defer fmt.Printf("ATP: Released (%v)\n", atp)
            defer func() { db = nil }()

            // close the sql instance
            defer fmt.Printf("ATP: Closed (%v)\n", atp)
            defer db.Close() // after returned

            // throw a query
            rows, err := db.Query("select pdb_name from dba_pdbs")
            if err != nil {
                fmt.Printf("failed to throw the query: %s\n", err)
                return
            }
            defer rows.Close() // after returned

            // get the rows of thrown query
            for rows.Next() {
                var pdb_name string
                rows.Scan(&pdb_name) // put the value into the variable
                fmt.Printf("pdb_name: %v (%v)\n", pdb_name, atp)
            }
        }(atp)
    }
    wg.Wait()
}

改善前スクリプト実行結果

無事、複数のADBに同時接続して、正常な結果を得られた。

$ go build -o go_connect_multiple_atp # main.goを修正したので再ビルド
$ ./go_connect_multiple_atp           # ビルド済みバイナリファイルの実行
ATP: Open (admin/YYYYYYYY@atp2_low)
ATP: Open (admin/XXXXXXXX@atp1_low)

pdb_name: ABC123...XYZ_ATP1 (admin/XXXXXXXX@atp1_low)
ATP: Closed (admin/XXXXXXXX@atp1_low)
ATP: Released (admin/XXXXXXXX@atp1_low)

pdb_name: ABC123...XYZ_ATP2 (admin/YYYYYYYY@atp2_low)
ATP: Closed (admin/YYYYYYYY@atp2_low)
ATP: Released (admin/YYYYYYYY@atp2_low)
1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0