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

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

[*ORA-12529エラー (公式リファレンス)](https://docs.oracle.com/cd/F19136_01/errmg/ORA-12500.html#GUID-BD6AAC21-3F15-4F70-B3C5-064ADCF5EC51)

:::note info
補足情報
セキュリテイに関して特に問題なければ、*ADB間のデータベースリンクを作成した方が、複数ADBに接続する必要がなくなるので、ラクに解消できるかもしれない...
:::
*[ADB間のデータベースリンク (Qiita投稿@TakuyaAma「Autonomous Database間のDBLINKの自分用メモ」)](https://qiita.com/TakuyaAma/items/883344eb0916de0495b9)

# 前提条件 (参考リンクあり)
- 下記のOCI環境が構築済みであること。
  - [VCNを構築する。](https://oracle-japan.github.io/ocitutorials/beginners/creating-vcn/)
  - [コンピュートインスタンスを作成する。](https://oracle-japan.github.io/ocitutorials/beginners/creating-compute-instance/)
  - [ADBインスタンスを作成する。(今回は2つ必要)](https://oracle-japan.github.io/ocitutorials/database/adb101-provisioning/)
  - [コンピュートインスタンス内に各ADBのwalletをそれぞれ配置する。](https://oracle-japan.github.io/ocitutorials/database/adb104-connect-using-wallet/)
  - [複数ADBの各walletを参照可能なtnsnames.ora及びsqlnet.oraを用意する。](https://oraclesean.com/blog/managing-wallets-for-multiple-oracle-autonomous-databases)
  - Go言語がコンピュートインスタンス内にインストールされていること。
  - gccがコンピュートインスタンス内にインストールされていること。
  - [SQL*Plusクライアントがコンピュートインスタンス内にインストールされていること。](https://www.suzu6.net/posts/241-yum-install-sqlpuls/)
  - [Go言語の外部パッケージ「go-oci8」が "go get" されていること。](https://qiita.com/qt-luigi/items/fbbe6792a77b493a58f9)
  - [環境変数 "LD_LIBRARY_PATH" が正しく設定されていること。](https://blog.bulkus.net/post/wsl_go_oracle/)
  - (補足) Oracle Databseはインストールされていなくてもよい。

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

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

```go: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エラーでタイムアウト**されてしまう。

```bash
$ 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 }() をスタックさせておく。**

```go: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に同時接続して、正常な結果を得られた。

```bash
$ 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)
```