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

← 2026.09.09 の号を通しで読む