공통 디자인 패턴

비어있는 응답

표준 Delete 메서드는 google.protobuf.Empty반환해야 합니다. 단, '소프트' 삭제를 수행할 때는 삭제가 진행 중임을 나타내도록 상태가 업데이트된 리소스를 반환해야 합니다.

커스텀 메서드는 비어있더라도 자체 XxxResponse 메시지가 있어야 합니다. 커스텀 메서드 기능은 시간이 지나면서 더욱 커져 추가 데이터를 반환해야 할 가능성이 매우 높기 때문입니다.

범위 표현

범위를 표현하는 필드는 이름 지정 규칙 [start_xxx, end_xxx)과 함께 절반 개방 간격(예: [start_key, end_key) 또는 [start_time, end_time))를 사용해야 합니다. 절반 개방 간격 시맨틱스는 C++ STL 라이브러리와 자바 표준 라이브러리에서 공통적으로 사용됩니다. API는 범위를 표현하는 다른 방식(예: (index, count) 또는 [first, last]) 사용을 방지해야 합니다.

리소스 라벨

리소스 중심 API에서 리소스 스키마는 API를 통해 정의됩니다. 클라이언트에서 작은 크기의 단순 메타데이터를 리소스에 연결하려면(예: 가상 머신 리소스를 데이터베이스 서버로 태그 지정) API가 google.api.LabelDescriptor에서 설명하는 리소스 수준 디자인 패턴을 사용해야 합니다.

이렇게 하려면 API 디자인에서 리소스 정의에 map<string, string> labels 필드를 추가해야 합니다.

message Book {
  string name = 1;
  map<string, string> labels = 2;
}

장기 실행 작업

API 메소드가 완료하는 데 오랜 시간이 걸리는 경우에는 장기 실행 작업 리소스를 클라이언트에게 반환하도록 디자인할 수 있습니다. 그러면 클라이언트가 장기 실행 작업 리소스를 사용해 진행 상황을 추적하고 결과를 수신할 수 있습니다. 이 작업에서는 함께 사용할 표준 인터페이스를 정의합니다. API마다 장기 실행 작업에 사용할 인터페이스를 따로 정의해서는 안 됩니다. 불일치가 발생할 수 있기 때문입니다.

작업 리소스는 응답 메시지로 직접 반환되어야 하며 작업의 즉각적인 결과는 API에 반영되어야 합니다. 예를 들어 리소스를 만들 때는 리소스가 아직 사용할 수 없는 것으로 나타나야 하더라도 LIST 및 GET 메서드에는 표시되어야 합니다. 작업이 완료되면 메서드가 오래 실행되지 않았더라면 직접 반환되었을 메시지가 Operation.response 필드에 포함되어야 합니다.

작업은 Operation.metadata 필드를 사용하여 진행 상황에 대한 정보를 제공할 수 있습니다. API는 초기 구현에서 metadata 필드가 채워지지 않더라도 이 메타데이터에 대한 메시지를 정의해야 합니다.

목록 페이지 나누기

목록으로 만들 수 있는 컬렉션은 결과가 작더라도 페이지 나누기를 지원해야 합니다.

이론적 근거: API가 처음부터 페이지 나누기를 지원하지 않고 나중에 지원할 경우 페이지 나누기를 추가하면 API의 동작이 끊어져서 문제가 될 수 있습니다. 또한 API가 이제 페이지 나누기를 사용한다는 사실을 모르는 클라이언트는 실제로 첫 페이지만 수신하였는데도 전체 결과를 수신하였다고 잘못 판단할 수 있습니다.

List 메서드에서 페이지 나누기(목록 결과를 페이지 단위로 반환)를 지원하려면 API가 다음과 같아야 합니다.

  • List 메서드 요청 메시지에서 string 필드 page_token을 정의합니다. 클라이언트는 이 필드를 사용해 목록 결과의 특정 페이지를 요청합니다.
  • List 메서드 요청 메시지에서 int32 필드 page_size를 정의합니다. 클라이언트는 이 필드를 사용해 서버에서 반환되는 결과의 최대 수를 지정합니다. 서버는 단일 페이지로 반환되는 결과의 최대 수를 추가로 제한할 수 있습니다. page_size0이면 서버는 반환할 결과 수를 결정합니다.
  • List 메서드의 응답 메시지에서 string 필드 next_page_token을 정의합니다. 이 필드는 다음 결과 페이지를 가져오기 위한 페이지 나누기 토큰을 표현합니다. 이 값이 ""이면 값이 추가 요청 결과가 없습니다.

클라이언트가 다음 결과 페이지를 가져오려면 다음과 같이 이후 List 메서드 호출에서 요청 메시지의 page_token 필드에 응답의 next_page_token 값을 전달해야 합니다.

rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);

message ListBooksRequest {
  string name = 1;
  int32 page_size = 2;
  string page_token = 3;
}

message ListBooksResponse {
  repeated Book books = 1;
  string next_page_token = 2;
}

클라이언트가 페이지 토큰에 더해 쿼리 매개변수까지 전달할 경우 쿼리 매개변수가 페이지 토큰과 일치하지 않으면 서비스가 요청에 실패해야 합니다.

페이지 토큰 내용은 url-safe base64로 인코딩된 프로토콜 버퍼가 되어야 합니다. 그래야만 내용이 호환성 문제 없이 진화할 수 있습니다. 페이지 토큰에 민감할 수 있는 정보가 포함되어 있으면 이 정보를 암호화해야 합니다. 서비스는 다음 중 한 가지 방법을 통해 페이지 토큰 조작으로 의도하지 않은 데이터가 노출되는 것을 방지해야 합니다.

  • 후속 요청에서 쿼리 매개변수를 다시 지정하도록 요구합니다.
  • 페이지 토큰에서 서버 측 세션 상태만 참조합니다.
  • 페이지 토큰에서 쿼리 매개변수를 암호화 및 서명하고 호출할 때마다 이 매개변수를 다시 검증하여 승인합니다.

페이지 나누기 구현에서 total_size라는 int32 필드에 전체 항목 수를 제공할 수도 있습니다.

하위 컬렉션 목록 만들기

API는 간혹 클라이언트가 하위 컬렉션에서 List/Search를 실행하도록 허용해야 합니다. 예를 들어 Library API는 선반 컬렉션이 있고, 각 선반은 도서 컬렉션이 있습니다. 이때 클라이언트는 모든 선반에서 도서 한 권을 검색하려고 합니다. 이러한 경우에는 하위 컬렉션에서 표준 메서드인 List를 사용하고 상위 컬렉션에 와일드카드 컬렉션 ID "-"를 지정하는 것이 좋습니다. 예를 들어 Library API에서는 다음과 같은 REST API 요청을 사용할 수 있습니다.

GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx

하위 컬렉션에서 고유 리소스 가져오기

간혹 하위 컬렉션의 리소스가 상위 컬렉션에서 고유한 식별자를 갖는 경우가 있습니다. 이러한 경우에는 Get을 사용하여 리소스가 포함된 상위 컬렉션을 모르는 상태에서 리소스를 가져오는 방법이 효과적입니다. 이러한 경우에는 리소스에서 표준 메서드인 Get을 사용하고 리소스가 고유한 모든 상위 컬렉션에 와일드카드 컬렉션 ID "-"를 지정하는 것이 좋습니다. 예를 들어 Library API에서는 모든 책장에 꽂혀있는 도서들 중에 고유한 도서가 있다면 다음과 같은 REST API 요청을 사용할 수 있습니다.

GET https://library.googleapis.com/v1/shelves/-/books/{id}

이 호출에 대한 응답에서는 상위 컬렉션마다 "-" 대신 실제 상위 컬렉션 식별자와 함께 표준 리소스 이름을 사용해야 합니다. 예를 들어 위 요청은 shelves/-/books/book8141이 아닌 shelves/shelf713/books/book8141과 같은 이름의 리소스를 반환해야 합니다.

정렬 순서

API 메소드에서 클라이언트가 목록 결과의 정렬 순서를 지정할 수 있다면 요청 메시지에 다음과 같은 필드가 포함되어야 합니다.

string order_by = ...;

문자열 값은 SQL 구문, 즉 쉼표로 구분된 필드 목록을 따라야 합니다. "foo,bar"). 기본 정렬 순서는 오름차순입니다. 필드에서 내림차순으로 지정하려면 서픽스 " desc"를 필드 이름에 추가해야 합니다. 예를 들면 "foo desc,bar"입니다.

구문에서 중복되는 공백 문자는 중요하지 않습니다. "foo,bar desc""  foo ,  bar  desc  "은 동일합니다.

요청 유효성 검사

API 메소드에 부작용이 있어서 부작용을 유발하지 않고 요청의 유효성을 검사해야 한다면 요청 메시지에 다음과 같은 필드가 포함되어야 합니다.

bool validate_only = ...;

이 필드가 true로 설정되면 서버는 어떠한 부작용도 실행해서는 안 되고 전체 요청을 따라 구현별 유효성 검사만 실시해야 합니다.

유효성 검사에 성공하면 google.rpc.Code.OK반환되어야 하고 동일한 요청 메시지를 사용하는 전체 요청이 google.rpc.Code.INVALID_ARGUMENT를 반환해서는 안 됩니다. 단, 요청이 google.rpc.Code.ALREADY_EXISTS와 같은 다른 오류나 경합 상태로 인해 계속해서 실패할 수도 있습니다.

요청 중복

네트워크 API라면 멱등성을 갖는 API 메소드의 사용을 강력하게 권장합니다. 이러한 메소드는 네트워크 장애가 발생해도 안전한 재시도가 가능하기 때문입니다. 하지만 리소스를 만드는 등 일부 API 메소드는 멱등성을 갖기 쉽지 않을 뿐만 아니라 불필요한 중복도 피해야 합니다. 이러한 사용 사례에서는 요청 메시지에 UUID 같은 고유 ID가 포함되어야 합니다. 그러면 서버가 고유 ID를 사용해 중복을 감지해 요청을 한 번만 처리할 수 있기 때문입니다.

// A unique request ID for server to detect duplicated requests.
// This field **should** be named as `request_id`.
string request_id = ...;

중복 요청이 감지되면 서버는 이전에 성공한 요청에 대한 응답을 반환해야 합니다. 클라이언트가 이전 응답을 수신하지 못했을 가능성도 있기 때문입니다.

열거형 기본값

모든 열거형 정의는 값 항목 0부터 시작해야 합니다. 이 항목은 열거형 값이 명시적으로 지정되지 않은 경우에 사용되어야 합니다. API는 0 값을 처리하는 방식을 명시해야 합니다.

공통 기본 동작이 있을 경우에는 열거형 값 0사용해야 하고 API는 예상 동작을 명시해야 합니다.

공통 기본 동작이 없을 경우에는 열거형 값 0의 이름을 ENUM_TYPE_UNSPECIFIED지정해야 하고 이 값을 사용하면 오류 메시지 INVALID_ARGUMENT와 함께 거부되어야 합니다.

enum Isolation {
  // Not specified.
  ISOLATION_UNSPECIFIED = 0;
  // Reads from a snapshot. Collisions occur if all reads and writes cannot be
  // logically serialized with concurrent transactions.
  SERIALIZABLE = 1;
  // Reads from a snapshot. Collisions occur if concurrent transactions write
  // to the same rows.
  SNAPSHOT = 2;
  ...
}

// When unspecified, the server will use an isolation level of SNAPSHOT or
// better.
Isolation level = 1;

0 값에는 관용구 이름을 사용할 수 있습니다. 예를 들어 google.rpc.Code.OK는 오류 코드 부재를 관용구 방식으로 지정한 것입니다. 이때는 OK가 열거형 컨텍스트의 UNSPECIFIED와 동일한 의미를 갖습니다.

근본적으로 실용적이고 안전한 기본값이 존재한다면 이 값을 '0' 값 대신에 사용할 수 있습니다. 예를 들어 리소스 뷰 열거형에서는 BASIC이 '0' 값입니다.

문법 구문

API 디자인에서는 허용되는 텍스트 입력 등 일부 데이터 형식에 간단한 문법을 정의해야 하는 일이 종종 있습니다. API 디자이너가 API에서 일관적인 개발자 경험을 제공하여 학습 곡선을 줄이기 위해서는 다음과 같이 EBNF(Extended Backus-Naur Form) 구문의 변형을 사용하여 문법을 정의해야 합니다.

Production  = name "=" [ Expression ] ";" ;
Expression  = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term        = name | TOKEN | Group | Option | Repetition ;
Group       = "(" Expression ")" ;
Option      = "[" Expression "]" ;
Repetition  = "{" Expression "}" ;

정수형

API 디자인에서는 uint32fixed32 같은 무부호 정수형을 사용해서는 안 됩니다. 자바, 자바스크립트, OpenAPI 등 일부 중요한 프로그래밍 언어 및 시스템은 무부호 정수형을 효과적으로 지원하지 못하고 오히려 오버플로 오류를 일으킬 가능성이 더욱 높기 때문입니다. 그 밖에도 API에 따라 동일한 경우에 부호형과 무부호형을 사용해 서로 일치하지 않을 수도 있다는 문제가 있습니다.

음의 값이 중요하지 않은 상황(예: 크기 또는 제한 시간)에서 부호 정수형을 사용할 경우에는 -1 값(-1한함)을 사용하여 파일 끝(EOF), 무한 제한 시간, 무제한 할당량 또는 알 수 없는 연령과 같은 특별한 의미를 나타낼 수 있습니다. 이러한 사용법은 혼동을 방지하도록 명확하게 작성되어야 합니다. 또한 API 작성자는 사용법이 명확하지 않을 경우 암시적인 기본값 0의 동작을 작성해야 합니다.

부분 응답

API 클라이언트가 응답 메시지에서 데이터의 특정 하위 집합만 필요한 경우가 있습니다. 일부 API 플랫폼은 이러한 사용 사례를 위해 부분 응답을 기본적으로 지원합니다. Google API 플랫폼은 응답 필드 마스크를 통해 부분 응답을 지원하고 있습니다. REST API를 호출할 때는 암시적인 시스템 쿼리 매개변수인 $fields가 있습니다. 이 매개변수는 google.protobuf.FieldMask 값을 JSON으로 표현한 것입니다. 응답 메시지가 클라이언트로 다시 전송되기 전에 $fields에서 필터링됩니다. API 플랫폼에서는 이러한 로직이 모든 API 메소드에서 자동으로 처리됩니다.

GET https://library.googleapis.com/v1/shelves?$fields=name

리소스 보기

네트워크 트래픽을 줄이려면 클라이언트가 서버 응답으로 반환되는 리소스에서 일부를 제한하여 전체 리소스 표현이 아닌 리소스 보기를 반환하도록 허용하는 것이 좋을 때가 있습니다. API의 리소스 보기 지원은 클라이언트가 응답으로 수신할 리소스의 보기를 지정할 수 있는 매개변수를 메소드 요청에 추가하여 구현할 수 있습니다.

매개변수는 다음과 같습니다.

  • 유형이 enum이어야 합니다.
  • view 이름이 지정되어야 합니다.

각 열거형 값은 서버 응답으로 반환되는 리소스 부분(필드)을 정의합니다. view 값마다 반환되는 응답은 구현에 따라 명확하게 정의되며 API 문서에서 지정되어야 합니다.

package google.example.library.v1;

service Library {
  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
    option (google.api.http) = {
      get: "/v1/{name=shelves/*}/books"
    }
  };
}

enum BookView {
  // Not specified, equivalent to BASIC.
  BOOK_VIEW_UNSPECIFIED = 0;

  // Server responses only include author, title, ISBN and unique book ID.
  // The default value.
  BASIC = 1;

  // Full representation of the book is returned in server responses,
  // including contents of the book.
  FULL = 2;
}

message ListBooksRequest {
  string name = 1;

  // Specifies which parts of the book resource should be returned
  // in the response.
  BookView view = 2;
}

위 구문은 아래와 같은 URL로 매핑됩니다.

GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC

메소드, 요청 및 응답 정의에 대한 자세한 내용은 본 디자인 가이드의 표준 메소드 장에서 찾아볼 수 있습니다.

ETag

ETag는 클라이언트가 조건부 요청을 할 수 있는 불투명 식별자입니다. ETag를 지원하려면 API의 리소스 정의에 문자열 필드 etag포함되어야 하며 시맨틱스는 ETage의 공통 사용법과 일치해야 합니다. 일반적으로 etag에는 서버에서 계산된 리소스의 지문이 포함됩니다. 자세한 내용은 WikipediaRFC 7232를 참조하세요.

ETag의 유효성 검사는 강력하거나 약할 수 있습니다. 유효성 검사가 약할 때는 ETag에 프리픽스 W/가 첨부됩니다. 여기에서 강력한 유효성 검사란 똑같은 ETag를 가지고 있는 리소스 2개에 바이트 단위의 동일 내용과 동일한 추가 필드, 즉 Content-Type가 모두 있다는 것을 말합니다. 이 말은 강력한 유효성 검사에 성공한 ETag는 나중에 부분 리소스의 캐싱에 대한 어셈블리가 가능하다는 것을 의미합니다.

반대로 동일하지만 약한 유효성 검사에 성공한 ETag 값을 가지고 있는 리소스는 각 표현의 의미는 같지만 바이트 단위의 동일 내용이 아닐 수도 있기 때문에 바이트 범위 요청의 응답 캐시에 적합하지 않다는 것을 의미합니다.

예:

// This is a strong ETag, including the quotes.
"1a2f3e4d5b6c7c"
// This is a weak ETag, including the prefix and quotes.
W/"1a2b3c4d5ef"

따옴표는 실제로 ETag 값에 포함된다는 점을 알고 있어야 할 뿐만 아니라 RFC 7232를 준수하려면 반드시 필요합니다. 이 말은 ETag를 JSON으로 표현할 때는 따옴표에 이스케이프 문자를 사용해야 한다는 것을 의미합니다. 예를 들어 JSON 리소스 본문에서는 ETag가 다음과 같이 표현됩니다.

// Strong
{ "etag": "\"1a2f3e4d5b6c7c\"", "name": "...", ... }
// Weak
{ "etag": "W/\"1a2b3c4d5ef\"", "name": "...", ... }

ETag에서 허용되는 문자에 대한 요약

  • 출력 가능한 ASCII로 제한됩니다.
    • RFC 2732에서는 ASCII가 아닌 문자도 허용되지만 이러한 문자는 개발자 친화성이 비교적 떨어집니다.
  • 공백은 사용할 수 없습니다.
  • 위와 같은 위치가 아닌 다른 곳에는 큰따옴표를 사용할 수 없습니다.
  • RFC 7232에서 권장하는 대로 이스케이프 문자에 대한 혼동을 피할 수 있도록 역슬래시는 사용하지 마세요.

출력 필드

API는 특정 리소스 출력에서 클라이언트가 입력값으로 제공하는 필드와 서버가 반환하는 필드를 구분해야 할 경우도 있습니다. 출력 전용 필드일 때는 해당 필드 속성을 작성해야 합니다.

출력 전용 필드가 요청에 설정되거나 google.protobuf.FieldMask에 포함되면 서버는 오류 없이 요청을 수락해야 합니다. 서버는 출력 전용 필드의 존재와 이 존재를 나타내는 기타 표시를 무시해야 합니다. 이러한 방법을 권장하는 이유는 클라이언트가 서버에서 반환된 리소스를 다른 요청의 입력으로 재사용하는 경우가 많기 때문입니다. 예를 들어 반환된 Book은 나중에 UPDATE 메서드에서 재사용됩니다. 출력 전용 필드에 대한 유효성을 검증하는 경우에는 클라이언트에서 출력 전용 필드를 소거하는 작업이 추가로 발생합니다.

message Book {
  string name = 1;
  // Output only.
  Timestamp create_time = 2;
}

싱글톤 리소스

싱글톤 리소스는 상위 리소스에(상위 리소스가 없는 경우에는 API에) 단일 리소스 인스턴스만 존재할 때 사용할 수 있습니다.

싱글톤 리소스에서는 표준 메서드인 CreateDelete생략되어야 합니다. 상위 리소스를 만들거나 삭제할 때 싱글톤 리소스도 암시적으로 생성되거나 삭제되기 때문입니다. 상위 리소스가 없는 경우에는 암시적으로 존재합니다. 표준 메서드인 GetUpdate 메서드와 사용 사례에 적합한 커스텀 메서드를 사용하여 이 리소스에 액세스해야 합니다.

예를 들어 User 리소스가 있는 API는 사용자 별 설정을 Settings 싱글톤으로 제공할 수 있습니다.

rpc GetSettings(GetSettingsRequest) returns (Settings) {
  option (google.api.http) = {
    get: "/v1/{name=users/*/settings}"
  };
}

rpc UpdateSettings(UpdateSettingsRequest) returns (Settings) {
  option (google.api.http) = {
    patch: "/v1/{settings.name=users/*/settings}"
    body: "settings"
  };
}

[...]

message Settings {
  string name = 1;
  // Settings fields omitted.
}

message GetSettingsRequest {
  string name = 1;
}

message UpdateSettingsRequest {
  Settings settings = 1;
  // Field mask to support partial updates.
  FieldMask update_mask = 2;
}

스트리밍 절반 종료

양방향 또는 클라이언트 API에서는 서버가 RPC 시스템에서 제공하는 대로 클라이언트에서 시작된 절반 종료를 이용해 클라이언트 측 스트림을 완료해야 합니다. 명시적인 완료 메시지를 정의할 필요는 없습니다.

클라이언트가 절반 종료 이전에 전송해야 하는 정보는 요청 메시지에 포함시켜 정의해야 합니다.

도메인 범위 이름

도메인 범위 이름은 DNS 도메인 이름을 프리픽스로 사용하여 이름 충돌을 방지하는 항목 이름입니다. 이 디자인 패턴은 서로 다른 조직에서 독립적으로 항목 이름을 정의할 때 유용합니다. 구문은 스키마 없는 URI와 유사합니다.

도메인 범위 이름은 다음과 같은 Google API 및 Kubernetes API에 널리 사용됩니다.

  • Protobuf Any 유형 표현: type.googleapis.com/google.protobuf.Duration
  • Stackdriver 측정항목 유형: compute.googleapis.com/instance/cpu/utilization
  • 라벨 키: cloud.googleapis.com/location
  • Kubernetes API 버전: networking.k8s.io/v1
  • x-kubernetes-group-version-kind OpenAPI 확장의 kind 필드

부울, 열거형, 문자열 간의 비교

API 메서드를 설계할 때 추적 기능 사용 설정 또는 캐싱 중지와 같이 선택한 일련의 특정 기능을 제공하는 것이 일반적입니다. 이를 위한 일반적인 방법은 bool, enum 또는 string 유형의 요청 필드를 사용하는 것입니다. 특정 사용 사례에 사용할 수 있는 올바른 유형은 항상 명확하지 않습니다. 다음과 같이 선택하는 것이 좋습니다.

  • 고정된 설계를 원하고 의도적으로 기능을 확장하지 않으려는 경우에는 bool 유형을 사용합니다. 예를 들면 bool enable_tracing 또는 bool enable_pretty_print입니다.

  • 유연한 디자인을 원하지만 디자인이 자주 변경되지 않을 것으로 예상되면 enum 유형을 사용합니다. 일반적으로 열거형 정의는 1년에 한 번 이하로 변경됩니다. 예를 들면 enum TlsVersion 또는 enum HttpVersion입니다.

  • 개방형 디자인이 있거나 외부 표준에 따라 디자인을 자주 변경할 수 있는 경우에는 string 유형을 사용합니다. 지원되는 값은 명확하게 명시되어야 합니다. 예:

데이터 보관

API 서비스를 설계할 때 데이터 보관은 서비스 신뢰도에 있어 중요한 요소입니다. 소프트웨어 버그나 사람의 실수로 인해 사용자 데이터가 실수로 삭제되는 경우가 많습니다. 데이터 보관 및 해당 삭제 취소 기능이 없으면 단순한 실수가 비즈니스에 심각한 영향을 미칠 수 있습니다.

일반적으로 API 서비스에 다음과 같은 데이터 보관 정책을 사용하는 것이 좋습니다.

  • 사용자 메타 데이터, 사용자 설정, 기타 중요한 정보의 경우 데이터 보관 기간은 30일이어야 합니다. 모니터링 측정 항목, 프로젝트 메타데이터, 서비스 정의가 이에 해당됩니다.

  • 대용량 사용자 콘텐츠의 경우 데이터 보관 기간은 7일이어야 합니다. 바이너리 blob과 데이터베이스 테이블이 이에 해당됩니다.

  • 임시 상태 또는 비싼 스토리지의 경우 데이터 보관 기간은 1일이어야 합니다(가능한 경우). Memcache 인스턴스와 Redis 서버가 이에 해당됩니다.

데이터 보관 기간 동안에는 데이터 손실 없이 데이터를 삭제 취소할 수 있습니다. 데이터 보관을 무료로 제공하는 서비스가 비쌀 경우 유료 옵션으로 데이터 보관을 제공할 수 있습니다.