세대 번호 및 전제조건

사용자는 객체 세대 번호를 통해 데이터 리소스를 식별하고 다단계 트랜잭션의 원자성 보장을 위한 전제조건을 적용할 수 있습니다.

세대

객체 버전 지정을 활성화하지 않아도 모든 Cloud Storage 객체는 세대 번호 및 메타 세대 번호를 갖습니다. 세대 번호는 객체를 덮어쓸 때마다 바뀌고, 메타 세대 번호는 객체의 메타데이터를 업데이트할 때마다 바뀝니다.

객체 메타 세대 번호는 새로운 객체 세대마다 1로 재설정되므로 세대 번호와 쌍으로 연결된 경우에만 의미가 있습니다.

버킷은 메타 세대 번호도 유지함으로써 사용자가 버킷 메타데이터 상태를 고유하게 식별할 수 있도록 합니다. 버킷에는 페이로드 데이터가 없으므로 세대 번호도 없습니다. 따라서 메타 세대 번호로 버킷을 식별할 수 있습니다.

예시: 동시 업로드

동시 업로드에서는 객체를 여러 조각으로 나누고, 여러 조각을 임시 위치에 동시에 업로드하고, 이러한 임시 조각에서 원래 객체를 compose합니다. 독립 프로세스가 임시 업로드된 하나 이상의 조각에 같은 이름을 사용할 경우에는 객체를 compose하려고 할 때 잘못된 구성요소가 사용되고 객체가 손상됩니다.

세대 번호를 사용하면 이러한 손상이 발생하는 것이 방지됩니다. compose 요청 시 업로드된 각 조각의 세대 번호를 포함하면 올바른 조각과 함께 compose가 수행되거나 404 Not Found 응답과 함께 요청이 실패합니다.

전제조건

전제조건은 영향을 받는 객체의 세대 번호나 메타 세대 번호가 전제조건을 충족하는 경우에만 Cloud Storage가 요청을 수행하도록 합니다. 세대 번호 및 메타 세대 번호를 확인하는 이 작업은 객체가 예상된 상태인지 확인함으로써 객체에 대해 안전한 읽기-수정-쓰기 업데이트와 조건부 작업을 수행할 수 있도록 합니다.

match 전제조건이 특정 세대 또는 메타 세대 번호를 사용할 때는 요청이 적용되는 Cloud Storage 객체에 같은 세대/메타 세대 번호가 있어야 합니다. 그래야만 요청이 성공하며, 그렇지 않으면 요청이 실패하고 412 Precondition Failed 응답이 반환됩니다.

match 전제조건이 세대 번호 대신 값 0을 사용할 때는 요청에서 지정된 이름의 실시간 객체가 Cloud Storage 버킷에 없는 경우에만 요청이 성공합니다. 그러한 객체가 있을 경우에는 요청이 실패하고 412 Precondition Failed 응답이 반환됩니다.

전제조건은 경합 상태를 방지하기 위해 변형 요청(업로드, 삭제, 복사, 메타데이터 업데이트)에서 종종 사용됩니다. 경합 상태는 동일 요청이 반복적으로 전송되거나 독립적인 프로세스가 서로 간에 충돌할 때 발생할 수 있습니다. 예를 들어 네트워크 중단 후에 여러 요청을 재시도하거나 사용자가 동일한 객체에서 읽기-수정-쓰기 작업을 수행하면 경합 상태가 발생할 수 있습니다.

세대 및 메타 세대 번호를 사용하는 전제조건 외에도 ETag를 사용하는 전제조건이 있을 수도 있습니다. 비복합 객체의 XML API ETag는 콘텐츠가 변경될 때만 변경되는 반면, 복합 객체 및 JSON API 리소스의 ETag는 콘텐츠나 메타데이터가 변경될 때마다 변경됩니다. ETag에 대한 자세한 내용은 해시 및 ETag: 권장사항을 참조하세요.

전제조건의 비용

전제조건은 성능 및 청구 비용을 수반합니다. 변형 작업을 실행할 때마다 객체의 세대/메타 세대 번호를 파악하기 위해 유료 GET 메타데이터 요청도 실행합니다. 전제조건은 왕복이 한 번 더 추가되어 네트워크로 인한 전체 작업 지연 시간이 두 배 증대될 수 있습니다. 이는 지연 시간에 민감한 작업의 경우 큰 영향을 미칠 수 있습니다. 전제조건을 사용하도록 허용하는 GET 메타데이터 요청 가격은 작업 10,000건당 $0.004의 요금이 청구됩니다.

아래 방법을 이용하면(애플리케이션에 따라 다름) 전제조건 사용으로 인한 성능 및 비용 청구를 막을 수 있습니다.

  • 전제조건에서 사용할 정확한 번호를 미리 알 수 있도록 객체의 세대 및 메타 세대 번호를 로컬에 저장합니다.
  • 동일 객체 이름을 한 번만 변형하는 명명 방식을 사용하면 전제조건을 사용할 필요가 없습니다.
  • if-generation-match:0 전제조건을 사용할 시점을 미리 알 수 있도록 새로 생성되는 객체를 애플리케이션에 알립니다.
  • 변형 전에 수행된 GET 호출의 결과를 기억합니다.

XML API의 전제조건

XML API에서는 세대 및 메타 세대 번호가 x-goog-generationx-goog-metageneration 응답 헤더를 통해 노출됩니다. 이 헤더는 객체의 HEAD 요청 응답에서 반환됩니다.

객체의 상태에 따라 요청을 조건부로 설정하기 위해서는 전제조건 요청 헤더의 전체 목록은 HTTP 헤더 참조를 확인하세요. 관련 예시는 아래와 같습니다.

  • 전제조건의 세대 번호가 요청된 객체의 세대 번호와 일치하는 경우에만 x-goog-if-generation-match 전제조건을 사용하여 요청을 실행합니다. 세대 번호 대신 0을 사용하면 요청에서 이름이 지정된 객체와 일치하는 실시간 객체가 버킷에 없을 때만 요청이 성공합니다.

  • 헤더의 메타 세대 번호가 요청된 객체의 메타 세대 번호와 일치하는 경우에만 x-goog-if-metageneration-match 전제조건을 사용하여 요청을 실행합니다.

  • If-Modified-Since 전제조건을 GET 또는 HEAD 요청에 사용합니다. 이 요청은 객체의 최근 세대(객체의 마지막 수정본)가 생성된 시간이 전제조건에서 지정된 시간보다 이후인 경우에만 실행됩니다.

  • ETag와 If-Match 또는 If-None-Match 전제조건을 GET 또는 HEAD 요청에 사용합니다. 이 요청은 요청된 객체가 전제조건에서 지정된 ETag와 일치하거나 일치하지 않는 경우에만 실행됩니다.

  • 같은 요청에서 여러 전제조건을 사용합니다. 예를 들어 x-goog-if-generation-match를 사용하는 경우 x-goog-if-metageneration-match도 사용할 수 있습니다.

JSON API의 전제조건

JSON API에서는 객체 또는 버킷 리소스가 있는 응답의 generationmetageneration 속성을 통해 세대 및 메타 세대 번호를 가져올 수 있습니다. 객체 또는 버킷 리소스는 객체 또는 버킷GET 요청 응답 본문에서 반환됩니다.

전제조건을 사용해 요청하려면 전제조건을 URL 끝에 쿼리 매개변수로 추가하세요. 각 전제조건과 같은 이름으로 쿼리 매개변수를 추가합니다. 예를 들어 다음은 ifGenerationMatch를 사용하는 요청입니다. https://www.googleapis.com/storage/v1/b/testgrid-triage-testing/o/test?ifGenerationMatch=1122334455

이 예시에서 API는 객체의 세대 번호가 1122334455인 경우에만 이 요청을 수행합니다.

JSON API는 버킷, 객체, ACL 등 모든 리소스에 대해 HTTP 1.1 ETag와 해당 HTTP If-MatchIf-None-Match 헤더도 지원합니다. ETag는 리소스가 반환될 때마다 응답 헤더의 일부분으로 반환될 뿐만 아니라 리소스 자체에도 포함되어 있습니다.

다음은 객체의 상태에 따라 요청을 조건부로 설정하는 전제조건의 몇 가지 예입니다.

  • ifGenerationMatchifGenerationNotMatch 전제조건을 사용하여 속성의 세대 번호가 요청된 객체의 세대 번호와 일치하는 경우 또는 일치하지 않는 경우에만 요청을 실행합니다. ifGenerationMatch에 세대 번호 대신 0을 사용하면 요청에서 이름이 지정된 객체와 일치하는 실시간 객체가 버킷에 없을 때만 요청이 성공합니다.

  • ifMetagenerationMatchifMetagenerationNotMatch 전제조건을 사용하여 버킷 또는 객체에 대한 요청을 실행합니다. 속성의 메타 세대 번호가 요청된 버킷 또는 객체의 메타 세대 번호와 일치하거나 일치하지 않는 경우에만 요청이 성공합니다.

  • ETag와 If-Match 또는 If-None-Match 전제조건을 요청의 헤더로 사용합니다. 이 요청은 요청된 객체가 전제조건에서 지정된 ETag와 일치하거나 일치하지 않는 경우에만 실행됩니다.

  • 같은 요청에서 여러 전제조건을 사용합니다. 예를 들어 객체를 요청하면서 ifGenerationMatch를 사용하는 경우 ifMetagenerationMatch도 사용할 수 있습니다.

ACL 작업에 대해서는 세대 및 메타 세대 전제조건이 허용되지 않습니다. 액세스 제어 항목 리소스 ETag를 대신 사용하세요. 이 ETag는 각 액세스 제어 항목 리소스 내에서 찾을 수 있습니다. 이 리소스도 포함 객체 또는 버킷 리소스에서 액세스할 수 있습니다.

경합 상태의 예

동시 읽기-수정-쓰기

버킷 또는 객체 메타데이터를 업데이트하는 일반적인 패턴은 현재 상태를 읽고, 로컬에 수정을 적용하고, 수정된 메타데이터를 Cloud Storage에 다시 보내 작성하는 것입니다. 이 패턴은 두 개 이상의 독립적 프로세스가 시퀀스를 동시에 시도할 경우 불안정할 수 있습니다.

예를 들어, 공동작업자가 나의 버킷에 액세스할 수 있도록 공동작업자의 ACL 항목을 추가하려 합니다. 이때 다른 동료는 더 이상 버킷에 액세스할 필요가 없는 다른 공동작업자를 삭제하려 합니다.

이를 위해 나와 동료는 동일한 초기 상태의 버킷 메타데이터를 읽고, 각각 버킷의 ACL 항목을 원하는 대로 수정합니다. 그리고 내가 수정사항을 Cloud Storage에 다시 작성하면 메타데이터가 올바르게 업데이트됩니다. 하지만 동료가 자신의 수정사항을 업로드하는 즉시 내 변경사항은 손실됩니다. 내가 업데이트한 내용은 동료가 업로드한 내용에 반영되어 있지 않기 때문입니다. 결과적으로, 내가 추가하려 했던 공동작업자의 ACL 항목은 손실되고 해당 공동작업자는 버킷에 액세스할 수 없게 됩니다. 그리고 ACL 항목을 다시 살펴보지 않는 한, 이와 같은 변화가 생겼다는 것을 어느 누구도 알지 못합니다.

경합 상태 방지

나와 동료는 각각 쓰기 작업에 if-metageneration-match 전제조건을 추가함으로써 이러한 경합 상태를 방지할 수 있습니다. 전제조건에서는 나와 동료, 둘 다 버킷의 메타 세대 번호를 사용합니다. 메타 세대 번호는 초기 읽기 작업에서 받는 메타데이터에 포함되어 있습니다.

수정사항으로 ACL 항목이 추가될 때 버킷의 메타 세대 번호가 변경됩니다. 이제 전제조건을 사용하고 있기 때문에 동료가 자신의 ACL 항목 버전을 작성하려고 할 때는 버킷의 메타 세대 번호가 전제조건의 번호와 일치하지 않으며 동료는 응답 코드 412 Precondition Failed와 함께 업데이트 실패 메시지를 받게 됩니다. 이 응답 코드를 받음으로써 동료는 업데이트된 메타데이터를 사용하여 새로운 읽기-수정-쓰기 주기를 수행하는 등 적절히 대응할 수 있습니다.

여러 번 요청 재시도

Cloud Storage는 분산형 시스템입니다. 네트워크 또는 서비스 상태로 인해 요청이 실패할 수 있으므로 실패를 재시도할 때 지수 백오프를 적용하는 것이 좋습니다. 그러나 분산형 시스템의 특성으로 인해 이러한 재시도가 예기치 못한 동작을 일으키는 경우도 있습니다.

예를 들어 Cloud Storage에 저장된 file.txt라는 파일을 삭제하려 합니다. 그 이후에 같은 이름인 새 파일을 Cloud Storage에 추가하려 합니다.

이를 위해 객체를 삭제하는 삭제 요청을 실행합니다. 하지만 중간 라우터의 연결이 일시적으로 끊기는 등 네트워크 상태로 인해 요청이 Cloud Storage에 도달하지 못함으로써 응답을 받지 못할 수 있습니다.

첫 번째 요청에 대한 응답을 받지 못했기 때문에 객체에 대한 두 번째 삭제 요청을 실행합니다. 이번에는 성공하고 삭제되었다는 응답을 받습니다. 1분 후에 새로운 file.txt를 업로드하기로 결정하고 업로드에 성공합니다.

연결이 끊겼던 라우터가 나중에 다시 연결되고 손실된 것처럼 보였던 첫 번째 삭제 요청을 Cloud Storage에 보내면 경합 상태가 발생합니다. 이 요청이 Cloud Storage에 도착하면 새로운 file.txt가 있기 때문에 실제로 삭제가 이루어집니다. Cloud Storage는 삭제에 성공했다는 응답을 보내는데, 클라이언트는 더 이상 이 응답을 수신하기 위해 대기하지 않기 때문에 응답이 수신되지 않습니다. 결국 의도와는 달리 새로 업로드한 파일도 삭제될 뿐만 아니라, 두 번째 삭제가 이루어졌다는 사실조차 인지하지 못합니다.

다음의 다이어그램은 이 상황을 한눈에 보여줍니다.

경합 상태 방지

위와 같은 상황이 발생하는 것을 방지하려면 현재 세대 번호를 확인하기 위해 먼저 file.txt의 메타데이터를 가져와야 합니다. 그런 다음에 세대 번호를 사용하는 if-generation-match 전제조건과 함께 삭제 요청을 보냅니다. 전제조건을 사용하면 삭제 요청이 언제 Cloud Storage에 도달하는지 또는 전제조건을 포함하는 삭제 요청이 몇 차례 전송되는지에 관계없이 오직 해당 세대 번호의 객체만 삭제됩니다. if-generation-match 전제조건을 사용하면 다른 세대 번호의 file.txt를 변형하려는 의도치 않은 시도는 응답 코드 412 Precondition Failed와 함께 실패합니다.

비슷한 네트워크 중단이 삭제 요청 이후의 업로드 요청에 대해 경합 상태를 일으킬 수 있기 때문에 업로드 요청에 적용되는 if-generation-match:0을 통해 이러한 경합 상태를 다수 방지할 수 있습니다. 이 전제조건을 사용하면 업로드 재시도가 실수로 객체를 두 번 작성하는 것이 방지됩니다. 이 전제조건은 현재 세대 번호의 객체가 없는 경우에만 요청이 성공하도록 허용하기 때문입니다.

이 전제조건을 사용하면 삭제 및 업로드 요청을 수행할 때 데이터가 실수로 손실되는 것을 방지할 수 있습니다. 다음 다이어그램에서 확인할 수 있습니다.

if-generation-match:0의 제한사항

if-generation-match:0은 첫 번째 객체가 삭제되어도 객체의 부재를 식별할 수 없으므로 객체 생성이 두 번 이루어지는 상황을 방지할 수 없습니다. 예를 들어 데이터는 손실되지 않지만 원하지 않은 파일이 생겼다고 가정해 봅니다.

  1. 우선 세대 번호를 알아내기 위해 file.txt의 메타데이터에 대한 GET 요청을 보냅니다. 응답에서 file.txt가 없다는 것을 발견합니다.

  2. 이를 확인한 상태에서 if-generation-match:0 전제조건과 함께 file.txt 업로드 요청을 보냅니다. 그런데 중간 라우터가 일시적으로 연결이 끊겨 요청 시간이 초과됩니다.

  3. 첫 번째 시도가 실패한 후 다시 한 번 if-generation-match:0 전제조건과 함께 업로드 요청을 재시도합니다. 이번에는 요청이 성공합니다.

  4. 잠시 후 file.txt 삭제 요청을 보내고 요청이 성공합니다.

  5. 이때 연결이 끊겼던 라우터가 다시 연결되어 첫 번째 업로드 요청을 Cloud Storage로 보냅니다. 요청과 함께 보낸 전제조건이 여전히 일치하므로 file.txt 가 다시 생성됩니다. 전제조건 사용 여부와 관계없이 file.txt가 예기치 않게 다시 업로드됩니다.

이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...

도움이 필요하시나요? 지원 페이지를 방문하세요.