Azure Table Storage を使用しており、InsertOrMerge 操作を実行するときに 408 タイムアウトが発生することがあります。この場合、再試行したいのですが、これらのエラーに対して再試行ポリシーが守られていないようです。
これは、テーブルの相互作用を処理するために使用するクラスです。メソッド GetFooEntityAsync は、Table Storage からエンティティを取得しようとします。できない場合は、新しい FooEntity を作成し、それをテーブルに追加します (FooTableEntity へのマッピング)。
public class FooTableStorageBase
{
private readonly string tableName;
protected readonly CloudStorageAccount storageAccount;
protected TableRequestOptions DefaultTableRequestOptions { get; }
protected OperationContext DefaultOperationContext { get; }
public CloudTable Table
{
get
{
return storageAccount.CreateCloudTableClient().GetTableReference(tableName);
}
}
public FooTableStorage(string tableName)
{
if (String.IsNullOrWhiteSpace(tableName))
{
throw new ArgumentNullException(nameof(tableName));
}
this.tableName = tableName;
storageAccount = CloudStorageAccount.Parse(ConnectionString);
ServicePoint tableServicePoint = ServicePointManager.FindServicePoint(storageAccount.TableEndpoint);
tableServicePoint.UseNagleAlgorithm = false;
tableServicePoint.ConnectionLimit = 100; // Increasing connection limit from default of 2.
DefaultTableRequestOptions = new TableRequestOptions()
{
PayloadFormat = TablePayloadFormat.JsonNoMetadata,
MaximumExecutionTime = TimeSpan.FromSeconds(1),
RetryPolicy = new OnTimeoutRetry(TimeSpan.FromMilliseconds(250), 3),
LocationMode = LocationMode.PrimaryOnly
};
DefaultOperationContext = new OperationContext();
DefaultOperationContext.Retrying += (sender, args) =>
{
// This is never executed.
Debug.WriteLine($"Retry policy activated in {this.GetType().Name} due to HTTP code {args.RequestInformation.HttpStatusCode} with exception {args.RequestInformation.Exception.ToString()}");
};
DefaultOperationContext.RequestCompleted += (sender, args) =>
{
if (args.Response == null)
{
// This is occasionally executed - we want to retry in this case.
Debug.WriteLine($"Request failed in {this.GetType().Name} due to HTTP code {args.RequestInformation.HttpStatusCode} with exception {args.RequestInformation.Exception.ToString()}");
}
else
{
Debug.WriteLine($"{this.GetType().Name} operation complete: Status code {args.Response.StatusCode} at {args.Response.ResponseUri}");
}
};
Table.CreateIfNotExists(DefaultTableRequestOptions, DefaultOperationContext);
}
public async Task<FooEntity> GetFooEntityAsync()
{
var retrieveOperation = TableOperation.Retrieve<FooTableEntity>(FooTableEntity.GenerateKey());
var tableEntity = (await Table.ExecuteAsync(retrieveOperation, DefaultTableRequestOptions, DefaultOperationContext)).Result as FooTableEntity;
if (tableEntity != null)
{
return tableEntity.ToFooEntity();
}
var fooEntity = CalculateFooEntity();
var insertOperation = TableOperation.InsertOrMerge(new FooTableEntity(fooEntity));
var executeResult = await Table.ExecuteAsync(insertOperation);
if (executeResult.HttpStatusCode == 408)
{
// This is never executed.
Debug.WriteLine("Got a 408");
}
return fooEntity;
}
public class OnTimeoutRetry : IRetryPolicy
{
int maxRetryAttempts = 3;
TimeSpan defaultRetryInterval = TimeSpan.FromMilliseconds(250);
public OnTimeoutRetry(TimeSpan deltaBackoff, int retryAttempts)
{
maxRetryAttempts = retryAttempts;
defaultRetryInterval = deltaBackoff;
}
public IRetryPolicy CreateInstance()
{
return new OnTimeoutRetry(TimeSpan.FromMilliseconds(250), 3);
}
public bool ShouldRetry(int currentRetryCount, int statusCode, Exception lastException, out TimeSpan retryInterval, OperationContext operationContext)
{
retryInterval = defaultRetryInterval;
if (currentRetryCount >= maxRetryAttempts)
{
return false;
}
// Non-retryable exceptions are all 400 ( >=400 and <500) class exceptions (Bad gateway, Not Found, etc.) as well as 501 and 505.
// This custom retry policy also retries on a 408 timeout.
if ((statusCode >= 400 && statusCode <= 500 && statusCode != 408) || statusCode == 501 || statusCode == 505)
{
return false;
}
return true;
}
}
}
GetFooEntityAsync() を呼び出すと、"Request failed" 行が実行されることがあります。値を検査する場合args.RequestInformation.HttpStatusCode
= 408。ただし、次のようになります。
Debug.WriteLine("Got a 408");
GetFooEntity メソッド内では実行されません。Debug.WriteLine($"Retry policy activated...
デリゲート内でDefaultOperationContext.Retrying
実行されることはありません(これは2回実行されると予想されます-これは再試行ではありませんか?)。DefaultOperationContext.RequestResults
結果の長いリストが含まれています (ほとんどの場合、ステータス コードは 404、一部は 204 です)。
この (かなり古い) ブログ投稿によると、コード 400 から 500、および 501 から 505 の例外は再試行できません。ただし、タイムアウト (408) は、まさに再試行が必要な状況です。この場合、カスタムの再試行ポリシーを作成する必要があるかもしれません。
RequestCompleted デリゲートが呼び出されたとき以外のコードでは見つからないため、408 がどこから来ているのか完全にはわかりません。再試行ポリシーのさまざまな設定を試してみましたが、うまくいきませんでした。ここで何が欠けていますか?テーブルストレージから 408 で操作を再試行するにはどうすればよいですか?
編集:コードを更新して、実装したカスタム再試行ポリシーを表示し、408 エラーで再試行します。ただし、再試行のブレークポイントがまだヒットしていないように見えるため、再試行がトリガーされていないようです。再試行ポリシーがアクティブ化されない理由は何ですか?