世代番号と前提条件

オブジェクトの世代番号を使用してユーザーはデータリソースを一意に識別し、前提条件を適用して複数ステップのトランザクションのアトミック性を保証できます。

世代

オブジェクトのバージョニングが有効でない場合であっても、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 を使用すると、ヘッダーのメタ世代番号がリクエストされたオブジェクトのメタ世代番号と一致する場合にのみリクエストが実行されます。

  • GET リクエストまたは HEAD リクエストで前提条件 If-Modified-Since を使用します。これらのリクエストは、最新のオブジェクト作成時点(オブジェクトの最終変更)が、前提条件で指定された時点よりも後である場合にのみ実行されます。

  • GET リクエストまたは HEAD リクエストで ETag と前提条件 If-Match または If-None-Match を使用します。これらのリクエストはそれぞれ、リクエストされたオブジェクトが前提条件で指定された ETag と一致する場合または一致しない場合にのみ実行されます。

  • 同じリクエストで複数の前提条件を使用します。たとえば、x-goog-if-generation-match を使用する場合、x-goog-if-metageneration-match も使用できます。

JSON API での前提条件

JSON API では、オブジェクトバケットのリソースを含むレスポンスの generation プロパティと metageneration プロパティを介して、世代番号とメタ世代番号を取得できます。オブジェクトやバケットのリソースは、オブジェクトまたはバケットGET リクエストに対するレスポンスの本文で返されます。 .

次の例は、前提条件として機能するクエリ パラメータと JSON API リクエストを使用してオブジェクトの情報を取得する方法を示しています。作成挿入書き換えなどのオペレーションには、ifGenerationMatchifGenerationNotMatchifMetagenerationMatchifMetagenerationNotMatch クエリ パラメータを使用できます。詳細については、JSON API オブジェクトのリファレンス ページをご覧ください。

ifGenerationMatch

この JSON API リクエストでは、ifGenerationMatch 前提条件が使用されます。API は、指定された世代番号を持つオブジェクトに対してのみこのリクエストを実行します。

  1. OAuth 2.0 Playground から承認アクセス トークンを取得します。固有の OAuth 認証情報を使用するように Playground を設定します。
  2. cURL を使用して、GET Object リクエストで JSON API を呼び出します。

      curl \
      'https://storage.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?ifGenerationMatch=GENERATION' \
      --header 'Authorization: Bearer OAUTH2_TOKEN' \
      --header 'Accept: application/json' \
      --compressed
    

    ここで

    • BUCKET_NAME は、オブジェクトが配置されているバケットの名前です。例: my-bucket
    • OBJECT_NAME は、情報を取得するオブジェクトの名前です。例: dog.png
    • OAUTH2_TOKEN は、手順 1 で生成したアクセス トークンです。
    • GENERATION は、情報を取得するオブジェクトの世代番号です。例: 1122334455667788

      指定された前提条件(この場合は世代番号)と一致するオブジェクトが見つからない場合、レスポンス本文は次のエラー メッセージを返します。

      "message": "Precondition Failed"

ifMetagenerationNotMatch

ifMetagenerationNotMatch 前提条件を使用するリクエストを次に示します。この例の場合、API は、指定したメタ世代番号を持たないオブジェクトに対してのみこのリクエストを実行します。

  1. OAuth 2.0 Playground から承認アクセス トークンを取得します。固有の OAuth 認証情報を使用するように Playground を設定します。
  2. cURL を使用して、GET Object リクエストで JSON API を呼び出します。

      curl \
      'https://storage.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?ifMetagenerationNotMatch=METAGENERATION' \
      --header 'Authorization: Bearer OAUTH2_TOKEN' \
      --header 'Accept: application/json' \
      --compressed
    

    ここで

    • BUCKET_NAME は、オブジェクトが配置されているバケットの名前です。例: my-bucket
    • OBJECT_NAME は、情報を取得するオブジェクトの名前です。例: dog.png
    • OAUTH2_TOKEN は、手順 1 で生成したアクセス トークンです。
    • METAGENERATION は、検索から除外するオブジェクトのメタ世代番号です。例: 5

      この操作により、クエリから特定のオブジェクトを除外できます。他のオブジェクトが存在しない場合、レスポンスは返されません。前の例と異なり、前提条件を満たしていなくても、呼び出しは成功しています。

制限事項

世代番号やメタ世代番号の前提条件は、ACL オペレーションでは受け付けられません。代わりに access-control エントリ リソース ETag を使用してください。この ETag は、保持されているオブジェクトまたはバケットのリソースからアクセス可能な各 access-control エントリ リソース内にあります。

HTTP 1.1 ETags

JSON API は、バケット、オブジェクト、ACL などのすべてのリソースのための HTTP 1.1 ETag や HTTP If-Match ヘッダーと If-None-Match ヘッダーをサポートしています。ETag は、リソースが返されると必ずレスポンス ヘッダーの一部として返され、リソースそのものにも含まれます。

リクエストのヘッダーとして、ETag と前提条件 If-Match または If-None-Match を使用します。これらのリクエストはそれぞれ、リクエストされたオブジェクトが前提条件で指定された ETag と一致する場合または一致しない場合にのみ実行されます。

競合状態の例

以下のセクションでは、考慮しなければならない競合状態について説明します。

読み取り - 変更 - 書き込みの同時実行

バケットやオブジェクトのメタデータ更新のための一般的なパターンには、現在の状態の読み取り、ローカルでの変更の適用、変更されたメタデータの Cloud Storage への送信と書き込みが含まれます。2 つ以上の独立したプロセスがシーケンスを同時に試行する場合、このパターンは不安定になることがあります。

たとえば、共同編集者がバケットにアクセスできるようにするための ACL エントリをユーザーが追加しようとしている状況を想定してください。さらに、バケットへのアクセスが必要なくなった別の共同編集者を同僚が削除しようとしているものとします。

それぞれの目的を果たすため、ユーザーとユーザーの同僚は同じ初期状態のバケットのメタデータを読み取り、双方ともバケットの ACL エントリに必要な変更を加えます。ユーザーが Cloud Storage に変更を書き込むと、メタデータは適切に更新されます。しかし、この変更は同僚が同僚の変更をアップロードした時点で失われます。これは、同僚がユーザーの更新を把握する手段を持たないためです。その結果、共同編集者の ACL エントリは失われ、共同編集者はバケットにアクセスできなくなります。また、遡って ACL エントリを確認しない限り、誰も何が起きたかを把握できません。

競合状態の防止

ユーザーと同僚は、各自の書き込みオペレーションに前提条件 if-metageneration-match を追加することでこの競合状態を防止できます。ユーザーと同僚は、最初の読み取りオペレーションで受信したメタデータの一部であるバケットのメタ世代番号を前提条件に使用します。

変更オペレーションによって ACL エントリが追加されると、バケットのメタ世代番号が変化します。前提条件が使用されるため、同僚が自身のバージョンの ACL エントリを書き込もうとすると、バケットのメタ世代番号が前提条件の番号と一致せず、レスポンス コード 412 Precondition Failed とともに更新の失敗が通知されます。このレスポンス コードを受信することで、同僚は、更新されたメタデータを使用して新しい読み取り - 変更 - 書き込みサイクルを実行するなど、状況に応じて対処できます。

複数のリクエストの試行

Cloud Storage は分散型システムです。リクエストは、ネットワークやサービスの状態によって失敗することがあるため、Google ではエラーの再試行指数バックオフを使用することをおすすめしています。しかし、分散型システムの性質上、これらの再試行によって予期せぬ動作が生じることがあります。

たとえば、Cloud Storage に保存されているファイル file.txt を削除する必要がある状況を想定してください。その後、同じ名前の新しいファイルを Cloud Storage に追加する必要があるものとします。

この操作を行うため、削除リクエストを発行してオブジェクトを削除します。しかし、中間ルーターが一時的に接続不良を起こしているなどのネットワーク状態により、リクエストが Cloud Storage に到達せず、レスポンスがありません。

最初のリクエストへのレスポンスがないため、オブジェクト削除のための 2 つめのリクエストを発行します。このリクエストは成功し、削除を確認するレスポンスが返されます。1 分後、新しい file.txt をアップロードすることになり、アップロードが成功します。

後からルーターの接続不良が回復し、失われたと思われていた元の削除リクエストが Cloud Storage に送信されると、競合状態が生じます。新しい file.txt が存在するため、Cloud Storage に送信されたリクエストは成功します。Cloud Storage はレスポンスを送信しますが、このレスポンスは受信されません。これはクライアントがリスニングを停止しているためです。新しいファイルが削除されるだけでなく、意図に反して 2 度目の削除が行われたことに気づけないことになります。

次の図に、この状況の詳細を示します。

競合状態の防止

上記の状況を回避するには、file.txt のメタデータを取得して、現在の世代を確認する必要があります。 その後、世代番号を使用する前提条件 if-generation-match で削除リクエストを送信します。前提条件を使用すると、削除リクエストが Cloud Storage に到達するタイミングや削除リクエストが送信された回数にかかわらず、指定の世代番号を持つオブジェクトのみが削除されるようになります。前提条件 if-generation-match により、世代の異なる file.txt を変更しようとする意図せぬ試行は失敗し、レスポンス コード 412 Precondition Failed が返されるようになります。

削除リクエストの後のアップロード リクエストでも同様のネットワーク中断が競合状態を生じさせる可能性があります。これらの競合状態は、多くの場合アップロード リクエストに if-generation-match:0 を適用することで防止できます。前提条件を使用すると、アップロードの再試行によって誤ってオブジェクトが 2 回書き込まれることがないようになります。これは、前提条件によって、現在の世代のオブジェクトが存在しない場合にのみリクエストが成功するようになるためです。

これらの前提条件を使用することで、削除リクエストやアップロード リクエストを実行する際にデータが誤って失われることがなくなります。次の図に詳細を示します。

if-generation-match:0 の制限事項

最初のオブジェクトが削除された場合、if-generation-match:0 でオブジェクトの作成が 2 度行われることを防ぐことはできません。これは、オブジェクトの不在を一意に識別できないためです。たとえば次のような場合、データは失われていないものの、ファイルの状態は想定と異なるものになります。

  1. 最初に、file.txt のメタデータに対する GET リクエストによって世代番号を確認します。レスポンスには file.txt が存在しないことが示されます。

  2. ファイルが存在しないことを確認したので、前提条件 if-generation-match:0file.txt のアップロードをリクエストします。しかし、中間ルーターが一時的に接続不良を起こしているためにリクエストがタイムアウトします。

  3. 一度失敗したため、アップロード リクエストを前提条件 if-generation-match:0 で再試行します。このリクエストは成功します。

  4. すぐに file.txt の削除リクエストを送信し、リクエストが成功します。

  5. ルーターの接続不良が回復し、Cloud Storage への最初のアップロード リクエストが送信されると、リクエストの前提条件は引き続き適合するため、file.txt が再度作成されます。前提条件の有無に関係なく、file.txt が意図せずもう一度アップロードされます。