35. バージョニングの内部 — 削除は削除ではなく『削除マーカー』の追記
S3バージョニングの本質は「上書き・削除を一切せず、追記だけで状態を表現する」データ構造です——削除すら『削除マーカー』の追記である理由を、6つの図で追っていきます。
① バージョニング有効バケット=上書きしない「追記型ログ」
- DynamoDB Streamsの「変更を消さずログとして積む」発想を、静的なオブジェクトに適用したもの
- データを書き換えず追記だけで表現する、同じ設計思想の別の現れ
上書きPUTは既存データを書き換えず、同じキーに新しいバージョンIDのオブジェクトを1つ追加するだけです。古い内容は消えず、非現在(noncurrent)バージョンとして同じバケットに残り続けます。
② DELETEの正体=「削除マーカー」という空バージョンの追記
- 削除とは「消えたことを示す新しい現在バージョンの挿入」
- 書き換えも消去もせず追記だけなら、過去のどの時点にも戻せるイミュータブルなデータになる——誤操作や障害から回復できる価値はここから生まれる
バージョンID指定なしの単純なDELETEは物理削除せず、中身のない「削除マーカー」を新しい現在バージョンとして積むだけです(公式: a simple DELETE cannot permanently delete an object)。GETは404を返しますが、実データは下に残ったままです。
③ 現象1:「消したのに課金され続ける」の仕組み
- 追記型構造では、削除(=マーカー追記)は容量を減らす操作ではない
各バージョンは直前との差分ではなくオブジェクト全体として保持されます(公式: Each version of an object is the entire object; it is not just a diff)。削除マーカーはほぼサイズゼロですが、下の非現在バージョンは通常のS3料金で課金され続けます。
④ 現象2:「削除マーカーを消すと復活」の仕組み
削除マーカーもバージョンIDを持つ1つのバージョンなので、そのIDを指定してDELETEするとマーカーが取り除かれ、1つ下の実データが現在バージョンに繰り上がります。データを復元しているのではなく、「消えた印」を剥がしているだけです。
⑤ 本当に消す唯一の方法=バージョンID指定のDELETE
- 手作業で全バージョンを消すのは非現実的なので、ライフサイクルで蓄積を管理する
- DynamoDBのTTLと同じ「不要になった古いデータを期限で自動回収する」発想
DELETE Object versionIdは指定バージョンを恒久的に取り除く唯一の操作で、公式も「the action cannot be undone(取り消せない)」と警告します。追記型の世界で容量とお金を実際に減らせるのはこの物理削除だけです。
⑥ 状態遷移も不可逆——有効化は「宣言」であって書き換えではない
- 有効化は過去のデータを作り変えず、振る舞いを追記的に切り替える——ここでも既存データには手を加えない
- 一意なバージョンIDは、DynamoDBのパーティションキーと同じく「同一キーに複数の実体を共存させる」ための識別子
バージョニング状態はUnversioned/Versioning-enabled/Versioning-suspendedの3つで、一度有効化すると未使用状態には二度と戻せません(it can never return to an unversioned state)。有効化は既存オブジェクトを書き換えず(バージョンIDはnullのまま)、以後の書き込みの扱いを変える宣言です。
サンプルコード(フレームワーク環境が必要なため表示のみ)
# 単純なDELETE: 削除マーカーを追記するだけ(実データは残る)
aws s3api delete-object --bucket my-bucket --key photo.gif
# 削除マーカーを含む全バージョンIDを確認
aws s3api list-object-versions --bucket my-bucket --prefix photo.gif
# バージョンID指定のDELETE: そのバージョンを恒久削除(取り消し不可)
aws s3api delete-object --bucket my-bucket --key photo.gif \
--version-id 3sL4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY+MTRCxf3vjVBH40Nr8X8gdRQBpUMLUo
# 削除マーカーのバージョンIDを消す=1つ下の実データが現在バージョンに復活
aws s3api delete-object --bucket my-bucket --key photo.gif \
--version-id <delete-marker-version-id>