本日、次の 2 つのアナウンスがありました。
- Released: Microsoft.Data.SqlClient 3.0 Preview 1
- Introducing Configurable Retry Logic in Microsoft.Data.SqlClient v3.0.0-Preview1
Microsoft.Data.SqlClient 3.0 では、ユーザーが設定可能な再試行ロジックを利用することができるようになるようです。
従来までの ConnectRetryCount/ConnectRetryInterval との違い
従来までの SqlClient では、接続再試行用の .NET SqlConnection パラメーター で説明されているような、次の 2 種類のオプションがありました。
- SqlConnectionStringBuilder.ConnectRetryCount プロパティ
- SqlConnectionStringBuilder.ConnectRetryInterval プロパティ
詳細については Idle Connection Resiliency で解説されていたりもしますが、これらのプロパティでは、「アイドル状態の接続のエラー」に対しての再試行の設定となっており、そもそも接続ができない場合などについてのリトライにはなっていませんでした。
- 接続が完了
- 接続が何らかの要因で切断される
- コマンドを実行
というような場合に、「3.」 のタイミングで接続を再試行するというようなものでした。
「1.」のタイミングで接続ができ無かった場合については、対応しておらず、初期接続に失敗した場合には、個別に接続の再試行のロジックを組み込んでおく必要がありました。
3.0 で導入される、設定可能な再試行ロジックでは、初期接続の失敗から SqlClient の機能で再試行ロジックを設定することができます。
再試行ロジックの設定方法
使用方法については Introducing Configurable Retry Logic in Microsoft.Data.SqlClient v3.0.0-Preview1 に記載されている内容となります。
using System;
using System.Text.RegularExpressions;
using Microsoft.Data.SqlClient;
namespace SqlClientTest
{
class Program
{
static void Main()
{
var conString = "<Connection String>";
AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.EnableRetryLogic", true);
SqlRetryLogicOption rto = new SqlRetryLogicOption()
{
NumberOfTries = 5,
DeltaTime = TimeSpan.Parse("00:00:05"),
MinTimeInterval = TimeSpan.Parse("00:00:10"),
MaxTimeInterval = TimeSpan.Parse("00:00:30"),
TransientErrors = new[] { 53, 208 },
AuthorizedSqlCondition = delegate (string query)
{
if (Regex.IsMatch(query, "(SELECT)")) { return true; }
else { return false; }
}
};
SqlRetryLogicBaseProvider prov = SqlConfigurableRetryFactory.CreateFixedRetryProvider(rto);
using (SqlConnection cnn = new SqlConnection(conString))
{
cnn.RetryLogicProvider = prov;
cnn.RetryLogicProvider.Retrying += new EventHandler<SqlRetryingEventArgs>(OnRetrying);
try
{
cnn.Open();
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} : Connection Open Done.");
var cmd = cnn.CreateCommand();
cmd.RetryLogicProvider = prov;
cmd.CommandText = "SELECT * FROM Tmp";
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
static void OnRetrying(object sender, SqlRetryingEventArgs args)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} : Retrying for {args.RetryCount} times for {args.Delay.TotalMilliseconds / 1000.0:0.00} sec - Error code: {(args.Exceptions[0] as SqlException).Number} - Error msg: {args.Exceptions[0].Message}");
}
}
}
リトライについては「SqlRetryLogicOption」で設定を行い、リトライの間隔については「CreateFixedRetryProvider」「CreateIncrementalRetryProvider」「CreateExponentialRetryProvider」のいずれかを指定することで設定を行います。
ハンドリングするエラー番号については「TransientErrors」で指定することができます。
今回は 53 / 208 を指定していますが、53 は接続時のエラーとなり、208 は、存在しないオブジェクトに対しての SELECT を実行した際に発生するエラーとなります。
今回、SQL Server のサービスを停止した状態にしているのですが、初期の接続の失敗についても指定した回数リトライが行われていますね。
クエリの実行についても、同様に指定した回数リトライが行われています。
今回、「CreateFixedRetryProvider」を使用しているため、「DeltaTime」に指定した間隔でリトライが行われていますが、これについて「CreateIncrementalRetryProvider」「CreateExponentialRetryProvider」による調整を行うこともできます。
Command については、どのようなステートメントが実行された場合に、リトライの対象にするかを制御することもできるため、一部のステートメントのみリトライ対象とするというようなこともできるようです。
これらのリトライの中では、ConnectRetryCount / ConnectRetryInterval も考慮されているようで、接続のオープン後に切断された場合の再接続も ConnectRetryCount / ConnectRetryInterval の範囲で実施することができるようです。
ADO.NET を使用した再試行については Azure サービスの再試行ガイダンス では、 Polly を使用した例が紹介されていますが、今後、SqlClient で実施する方法も追加されるかもしれませんね。