2017-03-15 21 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이 throw됩니다. 그러나 일시적인 서버 측 문제 (예 : 너무 많은 연결)가있을 수 있으므로 SQL 오류 코드를 검사 할 수 있습니다 (the PG docs 참조).

이러한 예외에 IsTransient 속성을 추가하고 PostgreSQL 자체에서 이러한 검사를 인코딩하는 것이 좋습니다. Npgsql 저장소에서 문제를 열어도됩니다.

+0

귀하의 도움과 제안에 감사드립니다. 몇 가지 구체적인 오류 코드를 찾기 위해 정책을 업데이트 한 다음 여기에 수정 된 정책을 게시합니다. –

+0

좋아요. https://github.com/npgsql/npgsql에서 문제를 열어주세요. Google 정책을 Npgsql 자체에 통합 할 수 있습니다. –

+0

제 경험상 가장 중요한 일시적인 오류는 코드 40001 (트랜잭션 직렬화 실패)이있는 오류입니다. –