C++ 클라이언트 라이브러리의 재시도 정책

이 페이지에서는 C++ 클라이언트 라이브러리에서 사용되는 재시도 모델을 설명합니다.

클라이언트 라이브러리가 사용자를 대신하여 RPC(리모트 프로시져 콜)를 실행합니다. 이러한 RPC는 일시적인 오류로 인해 실패할 수 있습니다. 서버 다시 시작, 부하 분산기에서 과부하되거나 유휴 상태인 연결 종료 및 비율 제한이 적용될 수 있으며, 이는 일시적인 오류의 몇 가지 예일 뿐입니다.

라이브러리가 애플리케이션에 이러한 오류를 반환할 수 있습니다. 하지만 이러한 오류의 대부분은 라이브러리에서 쉽게 처리할 수 있으므로 애플리케이션 코드가 더 간단해집니다.

재시도 가능한 오류 및 재시도 가능한 작업

일시적인 오류만 재시도할 수 있습니다. 예를 들어 kUnavailable은 요청을 처리하는 동안 클라이언트가 연결할 수 없거나 서비스에 대한 연결이 끊어졌음을 나타냅니다. 이 문제는 거의 대부분이 일시적이지만 복구하는 데 시간이 오래 걸릴 수 있습니다. 이러한 오류는 항상 재시도할 수 있습니다(작업 자체가 재시도해도 안전하다고 가정할 때). 계약 시 kPermissionDenied 오류를 해결하려면 일반적으로 사람이 개입해야 합니다. 이러한 오류는 '일시적'으로 간주되지 않으며 클라이언트 라이브러리의 재시도 루프에서 고려하는 기간에서 적어도 일시적이지 않습니다.

마찬가지로 일부 작업은 오류의 특성과 관계없이 재시도해도 안전하지 않습니다. 여기에는 점진적인 변경을 수행하는 모든 작업이 포함됩니다. 예를 들어 'X'라는 리소스의 여러 버전이 있을 수 있는 '최신 버전의 X'를 삭제하는 작업을 재시도하는 것은 안전하지 않습니다. 호출자가 단일 버전을 삭제하려 했을 가능성이 높기 때문에 이러한 요청을 재시도하면 모든 버전이 삭제될 수 있습니다.

재시도 루프 구성

클라이언트 라이브러리는 재시도 루프를 제어하기 위해 세 가지 구성 매개변수를 허용합니다.

  • *IdempotencyPolicy는 특정 요청이 멱등성을 갖는지 확인합니다. 이러한 요청만 재시도됩니다.
  • *RetryPolicy는 (a) 일시적인 오류를 오류로 간주해야 하는지 여부와 (b) 클라이언트 라이브러리가 요청을 재시도하는 기간(횟수)을 결정합니다.
  • *BackoffPolicy는 클라이언트 라이브러리가 요청을 다시 실행하기 전에 대기하는 시간을 결정합니다.

기본 멱등성 정책

일반적으로 함수를 여러 번 성공적으로 호출하면 시스템이 함수를 한 번 성공적으로 호출한 것과 동일한 상태로 유지되는 경우 작업은 멱등성을 가집니다. 멱등성 작업만 재시도하는 것은 안전합니다. 멱등성 작업의 예로는 모든 읽기 전용 작업과 한 번만 성공할 수 있는 작업이 포함되지만 이에 국한되지는 않습니다.

기본적으로 클라이언트 라이브러리는 GET 또는 PUT 동사를 통해 구현된 RPC만 멱등적으로 처리합니다. 이는 너무 보수적일 수 있으며, 일부 서비스에서도 일부 POST 요청이 멱등성을 갖습니다. 필요에 따라 언제든지 기본 멱등성 정책을 재정의할 수 있습니다.

일부 연산은 전제조건을 포함하는 경우에만 멱등성을 가집니다. 예를 들어 '최신 버전이 Y인 경우 최신 버전을 삭제합니다'는 멱등성을 가지므로 한 번만 성공할 수 있습니다.

경우에 따라 클라이언트 라이브러리는 더 많은 작업을 멱등성으로 취급하기 위한 개선 사항을 수신합니다. 이러한 개선사항은 버그 수정으로 간주되므로 클라이언트 라이브러리 동작을 변경하더라도 중단되지 않습니다.

작업을 재시도하는 것이 안전할 수 있지만 작업이 두 번째 시도와 첫 번째 성공적인 시도에서 동일한 결과를 산출하는 것을 의미하지는 않습니다. 예를 들어 고유하게 식별된 리소스를 만드는 것은 두 번째로 연속된 시도가 실패하고 시스템이 동일한 상태로 유지되므로 재시도해도 안전할 수 있습니다. 하지만 재시도하려고 하면 클라이언트가 '이미 존재함'으로 표시할 수 있습니다.

기본 재시도 정책

aip/194에 설명된 가이드라인에 따라 대부분의 C++ 클라이언트 라이브러리는 UNAVAILABLE gRPC 오류만 재시도합니다. 이 오류는 StatusCode::kUnavailable에 매핑됩니다. 기본 정책은 30분 동안 요청을 재시도하는 것입니다.

kUnavailable 오류는 서버가 요청을 수신하지 못했음을 나타내지 않습니다. 이 오류 코드는 요청을 보낼 수 없을 때 사용되지만 요청이 성공적으로 전송되고 서비스에서 수신한 경우에도 클라이언트가 응답을 수신하기 전에 연결이 끊어지는 경우 사용됩니다. 또한 요청이 성공적으로 수신되었는지 여부를 판단할 수 있는 경우 2가지 일반적인 문제, 즉 분산 시스템에서 잘 알려진 불능성을 해결할 수 있습니다.

따라서 kUnavailable로 실패한 모든 작업을 재시도하는 것은 안전하지 않습니다. 작업의 멱등성도 중요합니다.

기본 백오프 정책

기본적으로 대부분의 라이브러리는 지터와 함께 잘린 지수 백오프 전략을 사용합니다. 초기 백오프는 1초, 최대 백오프는 5분이며, 백오프는 재시도할 때마다 두 배로 증가합니다.

기본 재시도 및 백오프 정책 변경

각 라이브러리는 이러한 정책을 구성하기 위해 *Option 구조체를 정의합니다. *Client 클래스를 만들 때 또는 각 요청에 대해 이러한 옵션을 제공할 수 있습니다.

예를 들어 다음은 Cloud Pub/Sub 클라이언트의 재시도 및 백오프 정책을 변경하는 방법을 보여줍니다.

namespace pubsub = ::google::cloud::pubsub;
using ::google::cloud::future;
using ::google::cloud::Options;
using ::google::cloud::StatusOr;
[](std::string project_id, std::string topic_id) {
  auto topic = pubsub::Topic(std::move(project_id), std::move(topic_id));
  // By default a publisher will retry for 60 seconds, with an initial backoff
  // of 100ms, a maximum backoff of 60 seconds, and the backoff will grow by
  // 30% after each attempt. This changes those defaults.
  auto publisher = pubsub::Publisher(pubsub::MakePublisherConnection(
      std::move(topic),
      Options{}
          .set<pubsub::RetryPolicyOption>(
              pubsub::LimitedTimeRetryPolicy(
                  /*maximum_duration=*/std::chrono::minutes(10))
                  .clone())
          .set<pubsub::BackoffPolicyOption>(
              pubsub::ExponentialBackoffPolicy(
                  /*initial_delay=*/std::chrono::milliseconds(200),
                  /*maximum_delay=*/std::chrono::seconds(45),
                  /*scaling=*/2.0)
                  .clone())));

  std::vector<future<bool>> done;
  for (char const* data : {"1", "2", "3", "go!"}) {
    done.push_back(
        publisher.Publish(pubsub::MessageBuilder().SetData(data).Build())
            .then([](future<StatusOr<std::string>> f) {
              return f.get().ok();
            }));
  }
  publisher.Flush();
  int count = 0;
  for (auto& f : done) {
    if (f.get()) ++count;
  }
  std::cout << count << " messages sent successfully\n";
}

각 라이브러리의 문서를 참조하여 라이브러리의 특정 이름과 예시를 찾습니다.

다음 단계