このページでは、予期しない状態のリクエストがリソースに適用されないようにするリクエストの前提条件について説明します。
はじめに
Cloud Storage へのリクエストで前提条件を使用すると、対象のリソースが定義された前提条件を満たす場合にのみ、リクエストが処理されます。前提条件のチェックにより、バケットまたはオブジェクトが想定された状態にあることが保証されるので、読み取り / 変更 / 書き込みの更新オペレーションと条件付きオペレーションを安全に実行できます。
前提条件は、アップロード、削除、メタデータの更新などの変更リクエストで競合状態の発生を防ぐためによく使用されます。競合状態は、同じリクエストが繰り返し送信されたり、独立したプロセスが同じリソースを変更しようとした場合に発生します。詳しくは、競合状態とデータ破損の例をご覧ください。前提条件は、連続するリクエストでオブジェクトのメタデータやデータを取得するときにも使用されます。これにより、2 つのリクエスト間でのオブジェクトの変更を防ぐことができます。
前提条件で使用できる条件
Cloud Storage では、次のような変更不可の複数のリソース プロパティを前提条件として使用できます。
- 世代番号とメタ世代番号
- ETag
Last-Modified
の日付(XML API を使用してオブジェクト データまたはメタデータを取得する場合のみ)
次の表に、JSON API と XML API でサポートされている前提条件を示します。
JSON API | XML API | 説明 |
---|---|---|
ifGenerationMatch クエリ パラメータ |
x-goog-if-generation-match ヘッダー |
ターゲット リソースの generation が前提条件で使用されている値と一致する場合にリクエストが処理されます。値が一致しない場合、リクエストは失敗し、412 Precondition Failed レスポンスが返されます。 |
ifMetagenerationMatch クエリ パラメータ |
x-goog-if-metageneration-match ヘッダー |
ターゲット リソースの metageneration が前提条件で使用されている値と一致する場合にリクエストが処理されます。値が一致しない場合、リクエストは失敗し、412 Precondition Failed レスポンスが返されます。 |
ifGenerationNotMatch クエリ パラメータ |
該当なし | ターゲット リソースの generation が前提条件で使用されている値と一致しない場合にリクエストが処理されます。値が一致する場合、リクエストは失敗し、304 Not Modified レスポンスが返されます。 |
ifMetagenerationNotMatch クエリ パラメータ |
該当なし | ターゲット リソースの metageneration が前提条件で使用されている値と一致しない場合にリクエストが処理されます。値が一致する場合、リクエストは失敗し、304 Not Modified レスポンスが返されます。 |
If-Match ヘッダー |
If-Match ヘッダー |
データを取得するリクエストに適用されます。ターゲット リソースの ETag が前提条件で使用されている値と一致する場合にリクエストが処理されます。値が一致しない場合、リクエストは失敗し、412 Precondition Failed レスポンスが返されます。 |
If-None-Match ヘッダー |
If-None-Match ヘッダー |
データを取得するリクエストに適用されます。ターゲット リソースの ETag が前提条件で使用されている値と一致しない場合にリクエストが処理されます。値が一致する場合、リクエストは失敗し、304 Not Modified レスポンスが返されます。 |
該当なし | If-Modified-Since ヘッダー |
ターゲット リソースに前提条件で使用されている値より後の Last-Modified 日付がある場合、リクエストが処理されます。ターゲット リソースがこの前提条件を満たしていない場合、リクエストは失敗し、304 Not Modified レスポンスが返されます。 |
該当なし | If-Unmodified-Since ヘッダー |
ターゲット リソースに前提条件で使用されている値より以前の Last-Modified 日付がある場合、リクエストが処理されます。ターゲット リソースがこの前提条件を満たしていない場合、リクエストは失敗し、412 Precondition Failed レスポンスが返されます。 |
オブジェクト作成の前提条件
オブジェクトの作成を行うときに、JSON API と XML API の両方で次のものがサポートされます。
宛先オブジェクトの generation-match と metageneration-match 前提条件。
各ソース オブジェクトの generation-match 前提条件。この前提条件を使用することで、独立したプロセスによって構成の目的のコンポーネントが上書きされたときに、誤ったコンポーネントの使用を防ぐことができます。前提条件を使用して上書きを行った場合、
compose
オペレーションは412 Precondition Failed
レスポンスで失敗します。
オブジェクト コピーの前提条件
Cloud Storage 内でオブジェクトをコピーまたは書き換える場合、JSON API と XML API の両方で、宛先オブジェクトの標準の前提条件を使用できます。各 API には、ソース オブジェクトに対する前提条件の追加サポートがあります。
JSON API は、ソース オブジェクトの世代条件とメタ世代前提条件をサポートしています。これは、接頭辞
ifSource
が付いたクエリ パラメータで指定します。XML API でサポートされている前提条件は、すべてソース オブジェクトに使用できます。これらの前提条件は、接頭辞
x-goog-copy-source-
を付けてヘッダーで指定します。
generation-match 前提条件の 0
値
generation-match 前提条件は、特殊なケースとして値 0
を受け入れます。値が 0
の generation-match 前提条件がリクエストに含まれている場合、バケット内に指定された名前のオブジェクトが存在しないとき、またはバケット内にオブジェクトの非現行バージョンしか存在しないときにのみ、リクエストを処理します。指定した名前のライブ バージョンが存在する場合、リクエストは失敗し、ステータス コード 412 Precondition Failed
が返されます。
ベスト プラクティスと注意点
1 つのリクエストで複数の前提条件を使用できます。前提条件のいずれかが満たされない場合、リクエスト全体が失敗します。
バケットにはメタ世代番号がありますが、世代番号はありません。バケット リクエストで世代番号を指定する前提条件は使用しないでください。
オブジェクト リクエストでメタ世代の前提条件を使用する場合は、常に世代の前提条件を使用する必要があります。これにより、前提条件を渡すメタ世代番号が偶然違う別のオブジェクトでリクエストが成功するのを防ぐことができます。
オブジェクトのライブ バージョンと非現行バージョンの両方があるバケットの場合、オブジェクトのリクエストは、世代番号が明示的にリクエストに含まれていない限り、非現行バージョンに適用されません。つまり、前提条件を使用する一般的なリクエストでは、非現行バージョンが前提条件と一致するかどうかにかかわらず、ライブ バージョンが前提条件と一致しない場合、リクエストは失敗します。
通常は、前提条件 ETag の代わりに generation / metageneration の前提条件を使用してください。generation 番号と metageneration 番号により、メタデータの変更を含むすべてのオブジェクトの更新が追跡されるため、ETag よりも正確な処理が可能となります。また、generation と metageneration は API 全体で一貫していますが、ETag は一致しません。
XML API マルチパート アップロードでは、前提条件を使用できません。このような操作を行うと、
400 NotImplemented
エラーになります。
前提条件の料金
前提条件を使用する多くのアーキテクチャでは、現在の世代番号またはメタ世代番号を判断するために、メイン リクエストの前にオブジェクト メタデータ リクエストを行う必要があります。
- 追加のリクエストにより、ラウンド トリップが追加され、ネットワークのオペレーション全体のレイテンシが最大で 2 倍になる可能性があります。レイテンシの影響を受けやすいオペレーションの場合、これは重要な要因となります。
アプリケーションによっては、次のような方法で、前提条件の使用による影響を軽減できます。
- 前提条件で使用する正しい番号をあらかじめ把握しておけるように、オブジェクトの世代番号 / メタ世代番号をローカルで保存します。
- 前提条件
if-generation-match:0
を使用するタイミングをあらかじめ把握しておけるように、アプリケーションでどのオブジェクトが新たに作成されたかを確認します。
例: 前提条件の使用
次の例では、リクエストで generation-match 前提条件を使用してオブジェクトをアップロードします。リクエストを処理するには、既存のオブジェクトが指定された名前でバケットに保存されている必要があります。また、既存のオブジェクトの世代番号は、前提条件で指定された番号と一致している必要があります。
コマンドライン
--if-generation-match
フラグと通常のコマンドを使用します。
gcloud storage cp OBJECT_LOCATION gs://DESTINATION_BUCKET_NAME --if-generation-match=GENERATION
ここで
GENERATION
は、置き換えるオブジェクトの世代番号です。例:1122334455667788
OBJECT_LOCATION
は、オブジェクトへのローカルパスです。例:Desktop/dog.png
DESTINATION_BUCKET_NAME
は、オブジェクトをアップロードするバケットの名前です。例:my-bucket
JSON API
gcloud CLI のインストールと初期化を行います。これにより、
Authorization
ヘッダーのアクセス トークンを生成できます。cURL
を使用して、POST
Object リクエストで JSON API を呼び出します。curl -X POST --data-binary @OBJECT_LOCATION \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: OBJECT_CONTENT_TYPE" \ "https://storage.googleapis.com/upload/storage/v1/b/BUCKET_NAME/o?uploadType=media&name=OBJECT_NAME"&ifGenerationMatch=GENERATION"
ここで
OBJECT_LOCATION
は、オブジェクトへのローカルパスです。例:Desktop/dog.png
OBJECT_CONTENT_TYPE
は、オブジェクトのコンテンツ タイプです。例:image/png
BUCKET_NAME
は、オブジェクトをアップロードするバケットの名前です。例:my-bucket
OBJECT_NAME
は、オブジェクトに付ける名前です。例:dog.png
GENERATION
は、置き換えるオブジェクトの世代番号です。例:1122334455667788
XML API
gcloud CLI のインストールと初期化を行います。これにより、
Authorization
ヘッダーのアクセス トークンを生成できます。cURL
を使用して、PUT
Object リクエストで XML API を呼び出します。curl -X PUT --data-binary @OBJECT_LOCATION \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: OBJECT_CONTENT_TYPE" \ -H "x-goog-if-generation-match: GENERATION" \ "https://storage.googleapis.com/BUCKET_NAME/OBJECT_NAME"
ここで
OBJECT_LOCATION
は、オブジェクトへのローカルパスです。例:Desktop/dog.png
OBJECT_CONTENT_TYPE
は、オブジェクトのコンテンツ タイプです。例:image/png
GENERATION
は、置き換えるオブジェクトの世代番号です。例:1122334455667788
BUCKET_NAME
は、オブジェクトをアップロードするバケットの名前です。例:my-bucket
OBJECT_NAME
は、オブジェクトに付ける名前です。例:dog.png
前提条件を使用する場合のシナリオ
次のシナリオでは、競合状態とキャッシュ保存の例について説明します。これは、前提条件を使用する場合に役立ちます。
複数のリクエストの試行
Cloud Storage は分散型システムです。リクエストはネットワークやサービスの状態によって失敗することがあるため、再試行では指数バックオフを使用することをおすすめします。しかし、分散型システムの性質上、これらの再試行によって予期せぬ動作が生じることがあります。
たとえば、バケットの 1 つに保存されているオブジェクト file.txt
を削除する場合について考えてみましょう。削除後に、同じ名前で新しいオブジェクトをバケットに追加します。この操作を行うには、削除リクエストを発行してオブジェクトを削除します。しかし、中間ルーターが一時的に接続不良を起こしているなどのネットワーク状態により、リクエストが Cloud Storage に到達せず、レスポンスがありません。
最初のリクエストへのレスポンスがないため、オブジェクト削除のための 2 つめのリクエストを発行します。このリクエストは成功し、削除を確認するレスポンスが返されます。1 分後に新しい file.txt
をアップロードし、アップロードが成功します。
後からルーターの接続不良が回復し、失われたと思われていた元の削除リクエストが Cloud Storage に送信されると、競合状態が生じます。新しい file.txt
が存在するため、Cloud Storage に送信されたリクエストは成功します。Cloud Storage はレスポンスを送信しますが、このレスポンスは受信されません。これはクライアントがリスニングを停止しているためです。新しいファイルが削除されるだけでなく、意図に反して 2 度目の削除が行われたことに気づけないことになります。
次の図に、この状況の詳細を示します。
競合状態の防止
上記の状況を回避するには、file.txt
のメタデータを取得して、現在の世代を確認する必要があります。次に、削除リクエストの一部として生成した generation-match 前提条件で世代を使用します。前提条件を使用すると、削除リクエストが Cloud Storage に到達するタイミングや削除リクエストが送信された回数にかかわらず、指定の世代番号を持つオブジェクトのみが削除されるようになります。世代の異なる file.txt
を削除しようとすると、レスポンス コード 412 Precondition Failed
で失敗します。
削除リクエストの後のアップロード リクエストでも同様のネットワーク中断が競合状態を生じさせる可能性があるため、アップロード リクエストに含まれている generation-match 前提条件で 0
値を使用することで、このような多くの競合状態を回避できます。前提条件を使用すると、アップロードの再試行によって誤ってオブジェクトが 2 回書き込まれることがないようになります。これは、前提条件によって、現在の世代のオブジェクトが存在しない場合にのみリクエストが処理されるようになるためです。
これらの前提条件を使用することで、削除リクエストやアップロード リクエストを実行する際にデータが誤って失われることがなくなります。次の図に詳細を示します。
オブジェクト メタデータの関連付け
オブジェクトのデータとメタデータは、Cloud Storage でオブジェクトを一緒に定義する別々のエンティティです。オブジェクト メタデータは個別に存在するため、オブジェクト メタデータの操作中にオブジェクト データが変更されることがあります。
次のような場合を考えてみましょう。
オブジェクトのメタデータとデータをダウンロードするとします。これらのデータは、2 つの別々のリクエストで Cloud Storage から取得する必要があります。まず、オブジェクト メタデータをリクエストしますが、その後、オブジェクト データをリクエストする前に、独立したプロセスまたはユーザーがオブジェクトを置換します。オブジェクト データのリクエストは引き続き成功しますが、古いオブジェクトのメタデータと新しいオブジェクトのデータが存在します。
オブジェクトのメタデータを更新する場合は、オブジェクトの現在のメタデータを取得して、その現在の状態を判断します。変更でメタデータを更新するリクエストを送信する前に、独立したプロセスまたはユーザーがオブジェクトを置き換えます。新しいオブジェクトのメタデータを変更するリクエストは成功しますが、これで意図したものとは異なるオブジェクト データが関連付けられます。
競合状態の防止
これらの状況を回避するには、オブジェクト メタデータの最初のリクエストで返される世代番号を 2 番目のリクエストの generation-match 前提条件で使用します。これにより、メタデータとデータが一致するようになります。また、2 番目のリクエストが 412 Precondition Failed
レスポンス コードで失敗し、新しいオブジェクトの正しいメタデータをリクエストできるようになります。
オブジェクトのメタデータが最初のリクエストと 2 番目のリクエストの間で変更される可能性がある場合は、最初のリクエストで見つかったメタ世代番号をコピーして、2 番目のリクエストの metageneration-match 前提条件で使用することもできます。
ローカルコピーの鮮度
Cloud Storage に保存されているオブジェクトのローカルコピーを保持している場合は、バケットに保存されているコピーに合わせて、ローカルコピーを最新の状態にしておく必要があります。ただし、バケットに保存されているオブジェクトが変更されていない場合(特にオブジェクトのサイズが大きい場合)には、オブジェクトのダウンロードで時間とリソースを浪費する必要がありません。
最新でないコンテンツが不必要にダウンロードされないようにするには、ダウンロード リクエストに generation-not-match 前提条件を使用して、その値としてローカルコピーの世代番号を指定します。
バケット内のデータがローカルコピーと一致している間は世代番号が一致するため、前提条件が失敗します。その結果、リクエスト全体が
304 Not Modified
レスポンスで失敗し、不要なデータがダウンロードされなくなります。バケット内のデータが変更された場合は、世代番号は一致しないため、前提条件が成功します。リクエスト全体が通常どおり処理され、コンテンツの更新されたバージョンがダウンロードされます。
次のステップ
- 世代番号とメタ世代番号について確認する。
- オブジェクトのメタデータを取得する(オブジェクトの世代番号など)。
- Cloud Storage の整合性について確認する。
- 前提条件を使用する条件付きべき等オペレーションについて確認する。