SQL Server でコネクションタイムアウトを発生させる方法としては、
- 存在しないサーバーに対しての接続
- シングルユーザーモードのデータベースに対しての接続
を実行することでコネクションタイムアウトを発生させるという方法があります。
これらの方法では初期の接続ができないためエラーとなりますが、運用環境でコネクションタイムアウトが発生するケースとしては接続プロセスの途中で、タイムアウトの設定秒数に達したため、コネクションタイムアウトが発生するということがあります。
このコネクションタイムアウトの状態を意図的に発生させるための方法を残しておきたいと思います。
Contents
コネクションタイムアウトの発生方法
コネクションタイムアウトにより発生させたいエラー
今回の目的は、接続元のアプリケーションで次のようなエラーを発生させることです。
接続がタイムアウトしました。ログイン前のハンドシェイクの確認を実行しようとしている間にタイムアウト期間が過ぎました。ログイン前のハンドシェイクに失敗したか、サーバーが時間内に応答できなかった可能性があります。 このサーバーへの接続の試行に費やされた時間 – [ログイン前] 初期化 =2137; ハンドシェイク =2279
このようなエラーを発生させるためには、ログインプロセスの最中にコネクションタイムアウトの秒数に達する必要があり、ログインの一連のプロセスに関与する必要があります。
コネクションタイムアウトを意図的に発生させる方法
ログインプロセスに関与する方法としては次のようなものが考えられます。
- TDSBridge のような TDS のゲートウェイを間に挟み、ログイン処理の遅延を実現する
- ネットワークの遅延を使用することでログイン処理の遅延を実現する
SQL Server のメッセージのルールについては 3.1.5 Message Processing Events and Sequencing Rules に記載されており、ログインをする際には何回かイベントが発生し、各イベント発生のタイミングでクライアント / サーバー間で通信が発生します。
特定のイベントの発生時にタイムアウトをさせる場合には「1.」の方法で制御をするのが良いかと思いますが、今回は特定のイベントではなく、ログインプロセスの途中でタイムアウトを発生させることが目的ですので、容易に実現することができる「2.」の方法を使用してみます。
ネットワークの遅延を発生させるためには、以前投稿した clumsy を使用して Windows でネットワーク遅延を発生させる を使用しています。
TDS に対して遅延を発生させるためには、SQL Server 側で clumsy を実行し、「ip.DstAddr = xxx.xxx.xxx.xxx and tcp.SrcPort=1433」というようなフィルターを設定します。(SQL Database のような PaaS で発生させたい場合にはクライアント側で実行すればよいかと)
これで、In/Out に対して TDS のパケットのネットワークの遅延を発生させることができます。
In/Out に対して 700ms の遅延を発生させています。SQL Server のログインプロセスではアプリケーションと何回か通信が発生しますので、700ms の遅延を発生させた場合には、接続側で「Connection Timeout=3」辺りを指定しておく良い感じにログインプロセスの途中でタイムアウトを発生させることができるかと思います。
SQL Server 側での接続エラーの取得
このような接続エラーについては、拡張イベントの「error_reported」「connectivity_ring_buffer_recorded」で確認することができ、このイベントはデフォルトで設定されている「system_health」の拡張イベントセッションで取得されていますので、初期状態で状況を確認することができます。
「system_health」の拡張イベントのデータを確認すると次のように、SQL Server 側でも接続エラーが認識される状態でのコネクションタイムアウトを発生させることができます。
接続を確立中にネットワーク エラー コード 0x2746 が発生したので、接続が閉じられました。原因として、クライントまたはサーバーのログインがタイムアウトした可能性があります。ログインに要した時間: 合計 499 ミリ秒、エンキュー 0 ミリ秒、ネットワーク書き込み 0 ミリ秒、ネットワーク読み取り 0 ミリ秒、SSL の確立 499 ミリ秒、SSL の間のネットワーク読み取り 499 ミリ秒、SSL の間のネットワーク書き込み 0 ミリ秒、SSL の間の安全な呼び出し 0 ミリ秒、SSL の間のエンキュー 0 ミリ秒、SSPI のネゴシエーション 0 ミリ秒、SSPI の間のネットワーク読み取り 0 ミリ秒、SSPI の間のネットワーク書き込み 0 ミリ秒、SSPI の間の安全な呼び出し 0 ミリ秒、SSPI の間のエンキュー 0 ミリ秒、ログインの検証 0 ミリ秒 (ユーザー定義ログインの処理の 0 ミリ秒を含む)。 [クライアント: 192.168.0.9]
このようなエラーが system_health の error_reported に出力されていることが確認できます。
上記のメッセージですとエラーコード 0x2746 となっていますので、Microsoft Error Lookup Tool で確認をすると次のメッセージであることが確認できます。
An existing connection was forcibly closed by the remote host.
このメッセージからコネクションタイムアウトに達したことで、リモートホスト (接続元アプリケーション) から、ログインプロセスの最中で接続が強制的に切断された可能性を判断することができるのではないでしょうか。
system_health で取得されている情報はコールスタックが含まれていますので、コールスタックから参考となる情報を確認できる可能性もあります。
ログインプロセスの途中でコネクションタイムアウトを発生させ、エラー発生時の挙動をシミュレートしたい場合にはこのような方法を使用することができるのではないでしょうか。