Finatextのソフトウェアエンジニアが、金融サービスのバッチ処理がDBへのinsert処理の途中で停止した実際の事象をきっかけに、DBが無応答になったときGoのアプリケーションがどこで詰まるのかをtoxiproxy(通信障害を人工的に再現するツール)を使って検証しました。MySQL 8.0、go-sql-driver/mysql v1.10.0、Go 1.27.0という環境で、並行数5・顧客5件の並列バッチ処理を再現しています。
検証によると、DBが無応答になると「クエリ発行中のgoroutine(Goの軽量スレッド)が読み取りでブロック→コネクションプールの枯渇→新規goroutineの起動停止→wg.Wait()が永遠に返らない」という順で詰まりが連鎖するとのことです。タイムアウト設定なしの条件では処理が完了0件のまま返らなかったと報告されています。
一方、contextでdeadline10秒を設定した条件では約10秒でcontext.DeadlineExceededエラーとともに終了し、DSN(接続文字列)のreadTimeoutを10秒に設定した条件では約10秒でmysql.ErrInvalidConnエラーとともに終了したとのことです。読み取りタイムアウトを何も設定しない場合、TCPの再送タイマーの既定値(tcp_retries2=15)により15分以上、OS設定によってはLinuxで2時間11分を超える待機が発生しうると指摘されています。
記事は「readTimeoutはクライアント側の打ち切りのみで、サーバー側で実行中のクエリを停止しない」と注意を促し、contextのdeadlineとDSNのtimeout/readTimeout/writeTimeoutを階層的に設定する対策を提示しています。
出典が伝えている要点
- 著者はX(旧Twitter)アカウント@takuma5884rbb氏で、Finatextでソフトウェアエンジニアとして勤務している。
- 記事は金融サービスのバッチ処理がDB insert処理の途中で停止した事象の調査を発端としている。
- 検証にはtoxiproxyを用い、MySQLの前段に配置して無応答状態を再現した。
- 使用したDBはMySQL 8.0。
- 使用したライブラリはgo-sql-driver/mysql v1.10.0。
- 使用したGoのバージョンはGo 1.27.0。
- 検証は並行数5、顧客5件で並列バッチ処理を実施する構成で行われた。
- DBが無応答になった場合、詰まりは「クエリ発行中のgoroutineが読み取りでブロック→コネクションプール(MaxOpenConns=30)の枯渇→セマフォの枠が空かず新規goroutine起動停止→wg.Wait()が永遠に返らない」という順で連鎖するとされている。
- deadlineなし・DSNタイムアウトなしの条件では、処理は完了0件のまま返らなかった。
- contextでdeadline 10sを設定した条件では、約10秒でcontext.DeadlineExceededエラーとともに終了した。
- DSNのreadTimeoutを10sに設定した条件では、約10秒でmysql.ErrInvalidConnエラーとともに終了した。
- 読み取りタイムアウト未設定の場合、TCPの再送タイマーの既定値(tcp_retries2=15)により15分以上待機が発生し、OS既定によってはLinuxで2時間11分を超える可能性があると記事は述べている。
原文より
readTimeoutはクライアント側の打ち切りのみで、サーバー側で実行中のクエリを停止しない。
出典: zenn.dev