2017-03-15 1 views
1

リモートpostgresサーバーに接続するサービスを作成しています。 どのような例外を一時的な(再試行の価値がある)ものとして扱うべきか、そしてリモートデータベースに接続するための適切なポリシーを定義する方法を判断する良い方法を探しています。呼び出しが再試行の価値があるかどうかをNpgsql例外から通知する方法(一時的なフォールト戦略)

サービスは、データアクセスにNpgsqlを使用しています。 ドキュメントによると、NpgsqlはSQLエラーの場合はPostgresExceptionをスローし、 "サーバー関連の問題"の場合はNpgsqlExceptionをスローします。

私が思いつくことができる最高のものは、PostgresExceptionsではないすべての例外が、一時的で再試行の価値があるとみなされると仮定することですが、PostgresExceptionはクエリに何か問題があり、再試行は役に立たないでしょう。私はこの前提で正しいですか?

私は、Pollyを使用して再試行およびサーキットブレーカーポリシーを作成しています。 はこのように、私のポリシーは次のようになります。

Policy.Handle<Exception>(AllButPotgresExceptions()) // if its a postgres exception we know its not going to work even with a retry, so don't 
         .WaitAndRetryAsync(new[] 
         { 
          TimeSpan.FromSeconds(1), 
          TimeSpan.FromSeconds(2), 
          TimeSpan.FromSeconds(4) 
         }, onRetry: (exception, span) => Log.Warning(exception, "Postgres Retry Failure: ")) 
        .WrapAsync(
          Policy.Handle<Exception>(AllButPotgresExceptions()) 
           .AdvancedCircuitBreakerAsync(
            failureThreshold:.7, 
            samplingDuration: TimeSpan.FromSeconds(30), 
            minimumThroughput: 20, 
            durationOfBreak: TimeSpan.FromSeconds(30), 
            onBreak: (ex, timeSpan, context) => Log.Warning(ex, "Postres Circuit Breaker Broken: "), 
            onReset: (context) => Log.Warning("Postres Circuit Breaker Reset: "), 
            onHalfOpen:() => Log.Warning("Postres Circuit Breaker Half Open: ") 
           ))); 
     } 
    } 

    private static Func<Exception, bool> AllButPotgresExceptions() 
    { 
     return ex => ex.GetType() != typeof(PostgresException); 
    } 

は一時的なものかもしれないエラーを決定するためのより良い方法はありますか?

UPDATE:私はNpgsqlの中に新しい問題を開いて、このように見て、私のポリシーを更新シェイの提案に続き

public static Policy PostresTransientFaultPolicy 
    { 
     get 
     { 
      return postgresTransientPolicy ?? (postgresTransientPolicy = Policy.Handle<Exception>(PostgresDatabaseTransientErrorDetectionStrategy()) 
         .WaitAndRetryAsync(
          retryCount: 10, 
          sleepDurationProvider: retryAttempt => ExponentialBackoff(retryAttempt, 1.4), 
          onRetry: (exception, span) => Log.Warning(exception, "Postgres Retry Failure: ")) 
        .WrapAsync(
          Policy.Handle<Exception>(PostgresDatabaseTransientErrorDetectionStrategy()) 
           .AdvancedCircuitBreakerAsync(
            failureThreshold:.4, 
            samplingDuration: TimeSpan.FromSeconds(30), 
            minimumThroughput: 20, 
            durationOfBreak: TimeSpan.FromSeconds(30), 
            onBreak: (ex, timeSpan, context) => Log.Warning(ex, "Postres Circuit Breaker Broken: "), 
            onReset: (context) => Log.Warning("Postres Circuit Breaker Reset: "), 
            onHalfOpen:() => Log.Warning("Postres Circuit Breaker Half Open: ") 
           ))); 
     } 
    } 

    private static TimeSpan ExponentialBackoff(int retryAttempt, double exponent) 
    { 
     //TODO add random %20 variance on the exponent 
     return TimeSpan.FromSeconds(Math.Pow(retryAttempt, exponent)); 
    } 

    private static Func<Exception, bool> PostgresDatabaseTransientErrorDetectionStrategy() 
    { 
     return (ex) => 
     {     
      //if it is not a postgres exception we must assume it will be transient 
      if (ex.GetType() != typeof(PostgresException)) 
       return true; 

      var pgex = ex as PostgresException; 
      switch (pgex.SqlState) 
      { 
       case "53000": //insufficient_resources 
       case "53100": //disk_full 
       case "53200": //out_of_memory 
       case "53300": //too_many_connections 
       case "53400": //configuration_limit_exceeded 
       case "57P03": //cannot_connect_now 
       case "58000": //system_error 
       case "58030": //io_error 

       //These next few I am not sure whether they should be treated as transient or not, but I am guessing so 

       case "55P03": //lock_not_available 
       case "55006": //object_in_use 
       case "55000": //object_not_in_prerequisite_state 
       case "08000": //connection_exception 
       case "08003": //connection_does_not_exist 
       case "08006": //connection_failure 
       case "08001": //sqlclient_unable_to_establish_sqlconnection 
       case "08004": //sqlserver_rejected_establishment_of_sqlconnection 
       case "08007": //transaction_resolution_unknown 
        return true; 
      } 

      return false; 
     }; 
    } 

答えて

1

あなたはアプローチだが良いです。 NpgsqlExceptionは、通常、内部例外を調べてIOExceptionがないかどうかを確認できますが、ネットワーク/ IOエラーを意味します。

PostgreSQLがエラーを報告すると、PostgresExceptionがスローされます。ほとんどの場合、クエリに問題があります。しかし、一時的なサーバー側の問題(接続が多すぎるなど)がある場合は、SQLエラーコードを調べることができます。the PG docsを参照してください。

これらの例外にIsTransientプロパティを追加し、これらのチェックをPostgreSQL自体でエンコードすることをお勧めします.Npgsqlリポジトリの問題を開いても構いません。

+0

ご協力いただきありがとうございます。私はいくつかの特定のエラーコードを探すように私のポリシーを更新し、ここに私の変更されたポリシーを掲示します。 –

+0

また、https://github.com/npgsql/npgsqlで問題を開いてください。あなたのポリシーをNpgsql自体に組み込むことができます。 –

+0

私の経験から、最も重要な一時的なエラーは、コード40001(トランザクションのシリアル化の失敗)のエラーです。 –

関連する問題