goのspannerクライアントのReadOnlyTransactionでハマった

アプリケーション

概要

pkg.go.dev - cloud.google.com/go/spannerReadOnlyTransactionを使ったときにハマったところについてメモ。

何が起きたか?

数万件のデータを複数回のリクエストに分けて処理するようなバッチ処理のコードを書いていた。 ReadOnlyTransactionを使った処理を以下のように書いていた。 

for {
    // 〜略〜

    // cは*spanner.Client
    iter := c.ReadOnlyTransaction().Query(ctx, stmt)
    defer iter.Stop()

    // 〜略〜
}

一見問題なさそうに見えたのでバッチ処理を走らせていたのだが、特定件数を超えると処理が止まる問題が発生した。

原因

spannerのgoクライアントにはセッション管理の仕組みがあるのだが、トランザクションの終了処理が漏れていたことにより、セッションプールが枯渇、リクエストがタイムアウトしていたらしい。 内部的にはセッション管理の仕組みがReadOnlyTransactionの実行をブロックしているような形になってしまっていたらしい。

対応

トランザクションの終了処理を呼び出すように変更する。

for {
    // 〜略〜

    // cは*spanner.Client
    tx := c.ReadOnlyTransaction()
    defer tx.Close()
    iter := tx.Query(ctx, stmt)
    defer iter.Stop()

    // 〜略〜
}

トランザクションの終了処理がないとトランザクション実行の度に新規セッションが生成され、セッションプールが枯渇してしまう。 処理が毎回同じ件数で止まっていたのは、SPANNER_SESSION_POOL_MAX_OPENDの値制限に引っかかったからの模様。計算してみると帳尻が合う。

対策

ドキュメントをちゃんと読むということ以外には、ツールを使った解決方法もある。 github.com - gcpug/zagane

後はGCPのモニタリングでcloudspannerのsession countをウォッチするというのも有りかもしれない。

参考