経緯
Oracle Cloud Infrastructure (OCI) 上にある複数の Autonomous Database (ADB) にGoで同時接続させる必要があったが、2つ目以降のADB接続時に*ORA-12529エラーで接続タイムアウトされてしまい、非常に苦しめられた。
(SQL PLUSでは普通に接続できるので、接続文字列やtnsnames.oraの記載は間違っていない…)
補足情報
セキュリテイに関して特に問題なければ、*ADB間のデータベースリンクを作成した方が、複数ADBに接続する必要がなくなるので、ラクに解消できるかもしれない...
*ADB間のデータベースリンク (Qiita投稿@TakuyaAma「Autonomous Database間のDBLINKの自分用メモ」)
前提条件 (参考リンクあり)
- 下記のOCI環境が構築済みであること。
- VCNを構築する。
- コンピュートインスタンスを作成する。
- ADBインスタンスを作成する。(今回は2つ必要)
- コンピュートインスタンス内に各ADBのwalletをそれぞれ配置する。
- 複数ADBの各walletを参照可能なtnsnames.ora及びsqlnet.oraを用意する。
- Go言語がコンピュートインスタンス内にインストールされていること。
- gccがコンピュートインスタンス内にインストールされていること。
- SQL*Plusクライアントがコンピュートインスタンス内にインストールされていること。
- Go言語の外部パッケージ「go-oci8」が "go get" されていること。
- 環境変数 "LD_LIBRARY_PATH" が正しく設定されていること。
- (補足) Oracle Databseはインストールされていなくてもよい。
改善前スクリプト
goroutineで処理を並列化して、2つのADB(atp1, atp2)に同時接続させ、同じクエリーを投げてそれぞれのPDB名を出力する。
なお、database/sqlパッケージの仕様として、sql.Open()の時点ではADBへの接続は確立されず、db.Query()の時に初めて接続される。
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 }() をスタックさせておく。
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)