S3条件付き書き込みの仕組み

# はじめに
Pre:Invent2024の発表としてS3の条件付き書き込みの機能拡張がありました！

https://aws.amazon.com/about-aws/whats-new/2024/11/amazon-s3-functionality-conditional-writes/

今回はアップデートで既存の機能で何が拡張されたのか、どのような仕組みなのかを説明していきたいと思います。

# 従来の条件付き書き込み機能
2024/8月に条件付き書き込み機能が初登場しました。

https://aws.amazon.com/about-aws/whats-new/2024/08/amazon-s3-conditional-writes/

こちらは主に、既存のオブジェクトに対して同じ名前で上書きが行われないようにするための機能です。

機能としてはとてもシンプルであり、オブジェクトをPutする前に同名のキーを持つオブジェクトが既に存在するのかどうかを確認してくれる機能です。
この機能によって、予期せぬオブジェクトの更新を防ぐことができます。

**①同じキーに既にオブジェクトがある場合**
既にオブジェクトが存在するため、エラーになります。
これによって、オブジェクおtの上書きを防ぐことができます。

**②同じキーにオブジェクトがない場合**
Objectが正常にPutされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918398/a0d3b2e0-5aca-1668-1480-ad0004c3c838.png)

# Pre:Invent2024発表のS3条件付き書き込みの機能拡張
Pre:InventにてS3の条件付き書き込みの拡張機能が発表されました。
機能を説明していく前に、S3のメタデータが登場するので、先にその解説を行います。

## S3のメタデータとは？

S3のメタデータとはObjectをバケットに格納するタイミングでObjectごとに付与される情報です。
メタデータはタグと異なり、途中で変更することはできないデータとなります。
メタデータには主に2種類・概要は以下の通りです。

| 種類 | 概要 |
| :-- | :-- |
| システム定義メタデータ | AWS側で自動的に付与さえるメタデータ。<br> 一部データはユーザー側で編集が可能 |
| ユーザー定義メタデータ | ユーザーが自由に付与できるメタデータ |

システム定義メタデータの全種類は以下を参照ください。
今回S3の条件付き書き込みではシステム定義メタデータのEtagが使用されます。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/UsingMetadata.html

## 機能概要
機能としては以下の通りです
- AWS CLIからObjectをアップロードする際に、``--if-match``オプションを使用して、オブジェクトのシステム定義メタデータのEtagを指定
- S3側ではオブジェクトに付与されたメタデータと``--if-match``で設定された値を比較し、一致した場合のみPutを許可


図示すると以下のようなフローになります。
**①ユーザーはS3にオブジェクトをPut**
オブジェクトをPutするとシステム定義メタデータのうち、Etagが自動的に計算されます。

**②ユーザーが``--if-match``オプションを使用して、正しいEtagの値を指定してPut**
S3側でObjectのEtag値とオプションでユーザーから指定された値を比較します。
この例では、両者が一致しているため、ObjectのPutが許可され、新しいバージョンのObjectが作成されます。
新しいバージョンが作成されると、Etagは再度計算され、前バージョンと異なる値を持つことになります。

**③ユーザーが``--if-match``オプションを使用して、誤ったEtagの値を指定してPut**
異なる値を指定してしまった場合、Putは失敗します。
以下の例だと、User③はoject V0をベースとして新規ファイルを作成し、Putしようとしています。ただし既にUser②によって新バージョンV1がPutされてしまっているので、Etagが変わり、Putができなくなっています。
**④ユーザーが``--if-match``オプションを使用して、正しいEtagの値を指定してPut**
②と重複しますが、Etag値が変わっても、その値を``get-object-attributes``で取得をすれば問題なくPutは可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918398/0a069916-9e1e-ce72-8396-5f9b007f887d.png)

## マルチパートアップロード時の挙動
マルチパートアップロードでは、アップロードが完了するタイミング（CompleteMultipartUpload）でEtagの評価が行われます。したがって、マルチパートアップロードを開始した後、アップロード完了までの間に、Objectのバージョンが変わる、あるいはオブジェクトが削除されてしまった場合にはエラーが発生します。

## エラーレスポンス

| エラーコード | 理由 |
| :-- | :-- |
| 412 Precondition Failed | Etagの不一致 |
| 409 Conflict | マルチパートアップロードなどで同時に処理が行われた場合のエラー。 |
| 404 Not Found |　マルチパートアップロード中に先にオブジェクトが削除されてしまったときのエラー。|





## 機能によるメリット
この機能によって以下のメリットがあると考えられます
- 複数人で触るObjectに対して、予期せぬバージョン更新を防ぐ
- Gitのようなバージョン管理を模式的に再現できる（仕組みとしては大幅にに異なります）


## 関連アップデート
関連アップデートとして、条件付き書き込みを強制するバケットポリシーを作成することができるようになりました。
こちらを使用することで、上記機能を強制できるので、ガバナンス強化につなげることが期待できます。

https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-writes-enforce.html

## 実際に試してみる
検証用のバケットを作成し、CloudShellからAWS CLIでPutをしてみました！

```Bash:CloudShell
$ aws s3api put-object --bucket s3-conditional-writes-202412 --key dir01/sample.txt --body ./sample.txt
{
    "ETag": "\"3a04f9656b05c91f0622a3207fb34d6f\"",
    "ServerSideEncryption": "AES256"
}
```
Etagの値がレスポンスとして帰ってきました。
コンソールから確認してもObjectは正しくアップロードされているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918398/19f52f0e-f97a-a8e4-8539-4ef5106b4a59.png)

次に取得したEtagを使用して、条件付き書き込みをしてみたいと思います。
まずは``sample.txt``の中身を適当に書き換えます。そのあとに以下を実行しました。

```Bash:CloudShell
$ aws s3api put-object --bucket s3-conditional-writes-202412 --key dir01/sample.txt --body ./sample.txt --if-match 3a04f9656b05c91f0622a3207fb34d6f
{
    "ETag": "\"6a73d93096b0d7cc0d28202bbad2b2f0\"",
    "ServerSideEncryption": "AES256"
}
```
正しいEtagを指定したので、問題なくPutができました。
レスポンスを確認してみるとEtagが変更されているようです。

今後は最新でないEtag値でアップロードを試みてみます。Etagの値は初めにPutしたときの値を使用してみました。
```Bash:CloudShell
$ aws s3api put-object --bucket s3-conditional-writes-202412 --key dir01/sample.txt --body ./sample.txt --if-match 3a04f9656b05c91f0622a3207fb34d6f

An error occurred (PreconditionFailed) when calling the PutObject operation: At least one of the pre-conditions you specified did not hold
```

上記のようにエラーになり、Putができませんでした。

ちなみにEtagの値を調べるためには以下のコマンドで確認可能です。

```Bash:CloudShell
$ aws s3api get-object-attributes --bucket s3-conditional-writes-202412 --key dir01/sample.txt --object-attributes ETag
{
    "LastModified": "2024-12-27T08:00:35+00:00",
    "ETag": "6a73d93096b0d7cc0d28202bbad2b2f0"
}
```

# おわりに
今回初めてS3のメタデータを使用した機能が登場しました！
これまであまり使い勝手よくなかったメタデータであり、あまり注目がされてきませんでしたが、今回のRe:Inventでは他にもS3 metadataといったサービスも登場しており、より一層S3のメタデータを活用する流れが来ているのかなと思いました。

Etagのみならず他のメタデータに対しても制限がかけられるようになると、より活用の幅が広がりそうだなと思いました！