S3のバージョニングとは、同じキーへの上書きや削除のたびに以前の中身を「バージョン」として残しておく機能です。 有効にしておくと、上書きしたファイルを前の内容に戻したり、削除したファイルを「削除マーカー」を消すことで復元したりできます。

この記事では、有効化 → 上書き → 過去バージョンの取得 → 巻き戻し → 削除 → 復元の流れを AWS CLI と boto3 で順に実行し、各段階の list-object-versions の出力を載せます。削除マーカーの一括除去、バージョン付きバケットを空にするスクリプト、エラー文と原因の対応表もまとめました。

確認環境:Ubuntu 24.04、AWS CLI 1.46.1、Python 3.11.15、boto3 1.43.103、jq 1.7。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しています。掲載するコマンドは、実際の AWS でそのまま使える形(エンドポイント指定なし)で書いています。バージョン ID は S3 が自動で割り振る文字列で、記事中の値は確認環境で発行されたものです。

S3バージョニングの早見表

やりたいこと CLI コマンド
バージョニングを有効にする aws s3api put-bucket-versioning --bucket B --versioning-configuration Status=Enabled
今の状態を確認する aws s3api get-bucket-versioning --bucket B
全バージョンと削除マーカーを一覧する aws s3api list-object-versions --bucket B --prefix KEY
過去バージョンをダウンロードする aws s3api get-object --bucket B --key KEY --version-id ID 保存先
過去バージョンを最新に戻す(巻き戻し) aws s3api copy-object --bucket B --key KEY --copy-source "B/KEY?versionId=ID"
削除したファイルを復元する 削除マーカーを aws s3api delete-object --bucket B --key KEY --version-id マーカーのID で消す
特定のバージョンを完全に消す aws s3api delete-object --bucket B --key KEY --version-id ID
バージョニングを止める aws s3api put-bucket-versioning --bucket B --versioning-configuration Status=Suspended
削除のしかた 起きること 元に戻せるか
バージョン ID を指定しない削除 削除マーカーが最新になり、見えなくなる。中身は残る 戻せる(マーカーを消す)
バージョン ID を指定した削除 その版が完全に消える 戻せない

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「S3バージョニングの有効化と復元 削除マーカーの消し方CLI」

バージョニングを有効にする(put-bucket-versioning)

バケットの状態は「未設定」「Enabled(有効)」「Suspended(停止)」の3つです。新しく作ったバケットは未設定で、get-bucket-versioning は何も表示しません。

aws s3api get-bucket-versioning --bucket example-versioned-bucket
(出力なし)

有効にして、もう一度確認します。

aws s3api put-bucket-versioning --bucket example-versioned-bucket \
  --versioning-configuration Status=Enabled
aws s3api get-bucket-versioning --bucket example-versioned-bucket
{
    "Status": "Enabled"
}

一度有効にしたバケットは、未設定の状態には戻せません。 あとから選べるのは Suspended(停止)だけです。有効化より前からあったオブジェクトは、バージョン ID が null の版としてそのまま残ります。

上書きして、バージョン一覧を見る(list-object-versions)

同じキー report.txt に、内容を変えて3回アップロードします。

echo "version 1" > report.txt
aws s3api put-object --bucket example-versioned-bucket --key report.txt --body report.txt
{
    "ETag": "\"81127ad129dd2249f5ab0667ca0aeb84\"",
    "ChecksumCRC32": "zHmTaw==",
    "VersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823"
}

有効なバケットでは、アップロードのたびに VersionId が返ります。aws s3 cp でも同じようにバージョンが増えます。

echo "version 2 (long)" > report.txt
aws s3 cp report.txt s3://example-versioned-bucket/report.txt
echo "version 3" > report.txt
aws s3 cp report.txt s3://example-versioned-bucket/report.txt

一覧を取ります。そのままだと ETag や所有者なども出て長くなるので、--query で見たい項目に絞っています。以降の一覧はすべてこのコマンドの出力です。

aws s3api list-object-versions --bucket example-versioned-bucket --prefix report.txt \
  --query '{Versions:Versions[].{VersionId:VersionId,IsLatest:IsLatest,Size:Size},DeleteMarkers:DeleteMarkers[].{VersionId:VersionId,IsLatest:IsLatest}}'
{
    "Versions": [
        {
            "VersionId": "cb22e3ac-18bc-4704-9906-1a0b5761b06a",
            "IsLatest": true,
            "Size": 10
        },
        {
            "VersionId": "e26de999-da11-4ee9-93a4-cc6c045c8a41",
            "IsLatest": false,
            "Size": 17
        },
        {
            "VersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823",
            "IsLatest": false,
            "Size": 10
        }
    ],
    "DeleteMarkers": null
}
項目 意味
Versions 中身を持つバージョンの一覧。新しい順に並ぶ
VersionId 版を指定するための ID
IsLatest true が今の最新。バージョン指定なしの GET で返るのはこの版
DeleteMarkers 削除マーカーの一覧。1つもなければ null

過去バージョンを取得する(get-object --version-id)

1回目にアップロードした版(一覧のいちばん下)をダウンロードします。

aws s3api get-object --bucket example-versioned-bucket --key report.txt \
  --version-id 4b320889-9fff-4990-b5eb-ac2d1ab4d823 report_v1.txt \
  --query '{VersionId:VersionId,ContentLength:ContentLength}'
cat report_v1.txt
{
    "VersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823",
    "ContentLength": 10
}
version 1

この時点ではバケットの中は何も変わっていません。中身を確かめてから戻すかどうかを決められます。

上書きを巻き戻す(copy-object で過去版を最新にする)

過去バージョンを最新にするには、その版を同じキーへコピーします。 --copy-source の末尾に ?versionId= でコピー元の版を指定します。

aws s3api copy-object --bucket example-versioned-bucket --key report.txt \
  --copy-source "example-versioned-bucket/report.txt?versionId=4b320889-9fff-4990-b5eb-ac2d1ab4d823" \
  --query '{CopySourceVersionId:CopySourceVersionId,VersionId:VersionId}'
aws s3 cp s3://example-versioned-bucket/report.txt -
{
    "CopySourceVersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823",
    "VersionId": "28de0657-00ba-4c78-912a-507d01e709ae"
}
version 1

一覧を見ると、コピーで作られた新しい版(28de0657…)が最新になり、これまでの3つの版もすべて残っています。

{
    "Versions": [
        {
            "VersionId": "28de0657-00ba-4c78-912a-507d01e709ae",
            "IsLatest": true,
            "Size": 10
        },
        {
            "VersionId": "cb22e3ac-18bc-4704-9906-1a0b5761b06a",
            "IsLatest": false,
            "Size": 10
        },
        {
            "VersionId": "e26de999-da11-4ee9-93a4-cc6c045c8a41",
            "IsLatest": false,
            "Size": 17
        },
        {
            "VersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823",
            "IsLatest": false,
            "Size": 10
        }
    ],
    "DeleteMarkers": null
}

巻き戻しの方法は2つあります。

方法 操作 履歴 向いている場面
コピーで戻す copy-object で過去版を同じキーへコピー すべて残る(1つ増える) 通常はこちら。あとで「戻したこと」も取り消せる
新しい版を消して戻す 最新の版を delete-object --version-id で消す 消した版は残らない 誤ってアップロードした版そのものを残したくないとき

2つ目の方法は、記事後半の「バージョン指定ありの削除」で実行結果を載せます。

削除すると削除マーカーが付く(バージョン指定なしの削除)

バージョン ID を付けずに削除します。

aws s3api delete-object --bucket example-versioned-bucket --key report.txt
{
    "DeleteMarker": true,
    "VersionId": "1d5c534e-ce9c-445f-bff3-19e843af08de"
}

"DeleteMarker": true が返りました。中身は消えておらず、「削除された」という目印(削除マーカー)が最新版として1つ追加された状態です。 一覧はこうなります。

{
    "Versions": [
        {
            "VersionId": "28de0657-00ba-4c78-912a-507d01e709ae",
            "IsLatest": false,
            "Size": 10
        },
        {
            "VersionId": "cb22e3ac-18bc-4704-9906-1a0b5761b06a",
            "IsLatest": false,
            "Size": 10
        },
        {
            "VersionId": "e26de999-da11-4ee9-93a4-cc6c045c8a41",
            "IsLatest": false,
            "Size": 17
        },
        {
            "VersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823",
            "IsLatest": false,
            "Size": 10
        }
    ],
    "DeleteMarkers": [
        {
            "VersionId": "1d5c534e-ce9c-445f-bff3-19e843af08de",
            "IsLatest": true
        }
    ]
}

Versions の4つはすべて IsLatest: false になり、DeleteMarkers に IsLatest: true のマーカーが入りました。この状態で普通に GET すると、オブジェクトが無いときと同じ応答になります。aws s3 ls にも表示されません。

aws s3api get-object --bucket example-versioned-bucket --key report.txt out.txt
An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.

削除マーカーはキー名とバージョン ID だけを持ち、データは持ちません。公式ドキュメントでは、保存サイズはキー名の長さ分として数えられると説明されています。

削除マーカーを消して復元する(delete-object --version-id)

復元は、最新になっている削除マーカーを、バージョン ID を指定して削除するだけです。 マーカーの ID は --query で取り出せます。

DM=$(aws s3api list-object-versions --bucket example-versioned-bucket --prefix report.txt \
  --query 'DeleteMarkers[?IsLatest].VersionId' --output text)
echo "$DM"

aws s3api delete-object --bucket example-versioned-bucket --key report.txt --version-id "$DM"
1d5c534e-ce9c-445f-bff3-19e843af08de
{
    "VersionId": "1d5c534e-ce9c-445f-bff3-19e843af08de"
}

一覧を見ると、マーカーが無くなり、直前の版が最新に戻っています。

{
    "Versions": [
        {
            "VersionId": "28de0657-00ba-4c78-912a-507d01e709ae",
            "IsLatest": true,
            "Size": 10
        },
        …(省略:残り3つは IsLatest が false のまま)…
    ],
    "DeleteMarkers": null
}
aws s3 cp s3://example-versioned-bucket/report.txt -
version 1

--prefix report.txt は前方一致なので、report.txt.bak のようなキーがあると一緒に拾います。1つのキーだけを確実に指定したいときは、--query でキー名も条件に加えます。

aws s3api list-object-versions --bucket example-versioned-bucket --prefix report.txt \
  --query 'DeleteMarkers[?Key==`report.txt` && IsLatest].VersionId' --output text

バージョン指定ありの削除は「完全な削除」

同じ delete-object でも、--version-id に中身を持つ版を指定すると、その版が完全に消えます。削除マーカーは付きません。

aws s3api delete-object --bucket example-versioned-bucket --key report.txt \
  --version-id e26de999-da11-4ee9-93a4-cc6c045c8a41
{
    "VersionId": "e26de999-da11-4ee9-93a4-cc6c045c8a41"
}

一覧から2回目の版(Size 17)が無くなりました。

{
    "Versions": [
        {
            "VersionId": "28de0657-00ba-4c78-912a-507d01e709ae",
            "IsLatest": true,
            "Size": 10
        },
        {
            "VersionId": "cb22e3ac-18bc-4704-9906-1a0b5761b06a",
            "IsLatest": false,
            "Size": 10
        },
        {
            "VersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823",
            "IsLatest": false,
            "Size": 10
        }
    ],
    "DeleteMarkers": null
}

続けて、最新の版(28de0657…)をバージョン指定で消すと、1つ前の版が最新に繰り上がります。 これが「新しい版を消して巻き戻す」方法です。

aws s3api delete-object --bucket example-versioned-bucket --key report.txt \
  --version-id 28de0657-00ba-4c78-912a-507d01e709ae
aws s3 cp s3://example-versioned-bucket/report.txt -
{
    "VersionId": "28de0657-00ba-4c78-912a-507d01e709ae"
}
version 3
{
    "Versions": [
        {
            "VersionId": "cb22e3ac-18bc-4704-9906-1a0b5761b06a",
            "IsLatest": true,
            "Size": 10
        },
        {
            "VersionId": "4b320889-9fff-4990-b5eb-ac2d1ab4d823",
            "IsLatest": false,
            "Size": 10
        }
    ],
    "DeleteMarkers": null
}

2つの削除の違いを整理します。

バージョン指定なし バージョン指定あり
コマンド delete-object --key KEY delete-object --key KEY --version-id ID
応答 "DeleteMarker": true と、マーカーの VersionId 消した版の VersionId
中身 残る その版だけ完全に消える
一覧の変化 DeleteMarkers に1件増える Versions(またはマーカー)から1件減る
取り消し マーカーを消せば戻る 取り消せない
aws s3 rm での操作 こちらの動作になる aws s3 rm では指定できない(s3api を使う)

未設定・Enabled・Suspended の挙動比較表

操作 未設定 Enabled(有効) Suspended(停止)
get-bucket-versioning の出力 出力なし "Status": "Enabled" "Status": "Suspended"
PUT(同じキーに上書き) 上書きされ、以前の中身は残らない 新しいバージョン ID の版が増え、以前の版は残る バージョン ID が null の版として保存。null の版が既にあればそれを上書きする
有効だった頃の版 ― ― そのまま残る
DELETE(バージョン指定なし) 完全に消える 削除マーカーが付く。中身は残る バージョン ID null の削除マーカーが付く。null の版があればその中身は消える
DELETE(バージョン指定あり) ― その版を完全に消す その版を完全に消す
一覧での VersionId null 版ごとの ID 停止後に入れた版は null

未設定と Enabled の列はこの記事の確認環境で実行した結果、Suspended の列は公式ドキュメントの説明に基づいています。未設定のバケットで同じキーに2回 PUT して一覧を取ると、次のように版は1つだけで、ID は null になります。

aws s3api list-object-versions --bucket example-plain-bucket \
  --query 'Versions[].[Key,VersionId,IsLatest]' --output text
a.txt   null    True

Suspended で気を付けたいのは、停止中の削除は null の版の中身を消すという点です。有効だった頃に作られた版は残りますが、停止後にアップロードした内容は、バージョン指定なしの削除で戻せなくなります。

boto3 で同じ流れを実行する

有効化から復元、バージョン指定の削除までを1本にまとめたスクリプトです。

import boto3
from botocore.exceptions import ClientError

s3 = boto3.client("s3", region_name="ap-northeast-1")
BUCKET = "example-versioned-bucket"
KEY = "report.txt"


def show(title):
    """バージョンと削除マーカーを新しい順に表示する"""
    res = s3.list_object_versions(Bucket=BUCKET, Prefix=KEY)
    print(f"--- {title}")
    for v in res.get("Versions", []):
        print("  Version     ", v["VersionId"][:8], "IsLatest =", v["IsLatest"])
    for m in res.get("DeleteMarkers", []):
        print("  DeleteMarker", m["VersionId"][:8], "IsLatest =", m["IsLatest"])
    return res


# 1) 有効化
s3.put_bucket_versioning(
    Bucket=BUCKET, VersioningConfiguration={"Status": "Enabled"}
)
print("Status:", s3.get_bucket_versioning(Bucket=BUCKET).get("Status"))

# 2) 同じキーに3回アップロード
ids = []
for i in (1, 2, 3):
    r = s3.put_object(Bucket=BUCKET, Key=KEY, Body=f"version {i}\n".encode())
    ids.append(r["VersionId"])
show("3回アップロードした直後")

# 3) 過去バージョンの中身を読む
body = s3.get_object(Bucket=BUCKET, Key=KEY, VersionId=ids[0])["Body"].read()
print("1つ目の版の中身:", body.decode().strip())

# 4) 過去バージョンを最新に戻す(コピーして新しい版を作る)
r = s3.copy_object(
    Bucket=BUCKET, Key=KEY,
    CopySource={"Bucket": BUCKET, "Key": KEY, "VersionId": ids[0]},
)
print("戻したあとの最新:", s3.get_object(Bucket=BUCKET, Key=KEY)["Body"].read().decode().strip())

# 5) バージョン指定なしで削除 → 削除マーカーが付く
r = s3.delete_object(Bucket=BUCKET, Key=KEY)
print("DeleteMarker:", r.get("DeleteMarker"), "/ VersionId:", r["VersionId"][:8])
res = show("バージョン指定なしで削除した直後")

try:
    s3.get_object(Bucket=BUCKET, Key=KEY)
except ClientError as e:
    print("GET:", e.response["Error"]["Code"])

# 6) 削除マーカーを消して復元
marker = [m for m in res["DeleteMarkers"] if m["IsLatest"]][0]
s3.delete_object(Bucket=BUCKET, Key=KEY, VersionId=marker["VersionId"])
show("削除マーカーを消した直後")
print("復元後の中身:", s3.get_object(Bucket=BUCKET, Key=KEY)["Body"].read().decode().strip())

# 7) バージョンIDを指定した削除は、その版を完全に消す
s3.delete_object(Bucket=BUCKET, Key=KEY, VersionId=ids[1])
show("2つ目の版をバージョン指定で削除した直後")
try:
    s3.get_object(Bucket=BUCKET, Key=KEY, VersionId=ids[1])
except ClientError as e:
    print("GET:", e.response["Error"]["Code"], "/", e.response["Error"]["Message"])

実行結果です(ID は先頭8文字だけ表示しています)。

Status: Enabled
--- 3回アップロードした直後
  Version      8babfd1c IsLatest = True
  Version      ba972eb9 IsLatest = False
  Version      665a3069 IsLatest = False
1つ目の版の中身: version 1
戻したあとの最新: version 1
DeleteMarker: True / VersionId: 991e0ece
--- バージョン指定なしで削除した直後
  Version      3e34b41b IsLatest = False
  Version      8babfd1c IsLatest = False
  Version      ba972eb9 IsLatest = False
  Version      665a3069 IsLatest = False
  DeleteMarker 991e0ece IsLatest = True
GET: NoSuchKey
--- 削除マーカーを消した直後
  Version      3e34b41b IsLatest = True
  Version      8babfd1c IsLatest = False
  Version      ba972eb9 IsLatest = False
  Version      665a3069 IsLatest = False
復元後の中身: version 1
--- 2つ目の版をバージョン指定で削除した直後
  Version      3e34b41b IsLatest = True
  Version      8babfd1c IsLatest = False
  Version      665a3069 IsLatest = False
GET: NoSuchVersion / The specified version does not exist.

CLI との書き方の違いは、コピー元の指定です。CLI は "バケット/キー?versionId=ID" という文字列、boto3 は CopySource={"Bucket": …, "Key": …, "VersionId": …} という辞書で渡します。

削除マーカーを一括で消して復元するスクリプト

フォルダごと削除してしまったときは、キーごとに1件ずつ戻すより、まとめて処理するほうが確実です。docs/ 以下の3ファイルを aws s3 rm --recursive で消した状態から始めます。

aws s3 ls s3://example-versioned-bucket/ --recursive
2026-10-05 15:07:30         10 img/logo.png
2026-10-05 15:07:27         10 report.txt

--query だけで書く(1,000件まで)

list-object-versions の出力を、delete-objects がそのまま受け取れる形(Objects の配列)に --query で整えます。

aws s3api list-object-versions --bucket example-versioned-bucket --prefix docs/ \
  --query '{Objects: DeleteMarkers[?IsLatest].{Key:Key,VersionId:VersionId}}' \
  --output json > markers.json
cat markers.json
{
    "Objects": [
        {
            "Key": "docs/a.txt",
            "VersionId": "546fdbc9-50bd-43fc-ae65-2e7974f0ea42"
        }
    ]
}

(上の出力は、説明用に docs/a.txt だけを削除した状態で取ったものです。)消す前に markers.json を目で確認できるのがこの方法の良いところです。問題なければ渡します。

aws s3api delete-objects --bucket example-versioned-bucket --delete file://markers.json
{
    "Deleted": [
        {
            "Key": "docs/a.txt",
            "VersionId": "546fdbc9-50bd-43fc-ae65-2e7974f0ea42"
        }
    ]
}

IsLatest で絞っているのは、最新になっているマーカーだけを消せば復元できるからです。同じキーを「削除 → 再アップロード → 削除」と繰り返すと過去のマーカーも残りますが、それらは復元には関係しません。

jq で1,000件ずつに分ける(件数が多いとき)

delete-objects に1回で渡せるのは1,000件までです。件数が多いときは jq で分割します。restore_markers.sh として保存します。

#!/bin/bash
# 使い方: ./restore_markers.sh バケット名 プレフィックス
BUCKET="$1"
PREFIX="$2"

aws s3api list-object-versions --bucket "$BUCKET" --prefix "$PREFIX" --output json \
  | jq -c '[.DeleteMarkers[]? | select(.IsLatest) | {Key, VersionId}]
           | select(length > 0) | _nwise(1000) | {Objects: .}' \
  | while read -r batch; do
      aws s3api delete-objects --bucket "$BUCKET" --delete "$batch" \
        --query 'Deleted[].Key' --output text
    done
echo "done"
bash restore_markers.sh example-versioned-bucket docs/
aws s3 ls s3://example-versioned-bucket/ --recursive
docs/a.txt  docs/b.txt  docs/c.txt
done
2026-10-05 15:07:28         10 docs/a.txt
2026-10-05 15:07:28         10 docs/b.txt
2026-10-05 15:07:29         10 docs/c.txt
2026-10-05 15:07:30         10 img/logo.png
2026-10-05 15:07:27         10 report.txt

3つのファイルが一覧に戻りました。select(length > 0) は、対象が0件のときに空の配列を delete-objects に渡さないための行です。これが無いと、0件のときに次のエラーで止まります。

An error occurred (MalformedXML) when calling the DeleteObjects operation: The XML you provided was not well-formed or did not validate against our published schema

対象が無い状態でもう一度実行すると、done だけが表示されます。

バージョン付きバケットを空にするスクリプト

バージョニングが有効なバケットは、aws s3 rm --recursive で全部消したつもりでも、削除マーカーが付くだけで全バージョンが残っています。そのためバケットの削除はエラーになります。

aws s3 rm s3://example-versioned-bucket/ --recursive --quiet
aws s3api delete-bucket --bucket example-versioned-bucket
An error occurred (BucketNotEmpty) when calling the DeleteBucket operation: The bucket you tried to delete is not empty

空にするには、すべてのバージョンとすべての削除マーカーを、バージョン ID を指定して消します。 empty_bucket.sh として保存します。この操作は取り消せないので、バケット名をよく確かめてから実行してください。

#!/bin/bash
# 使い方: ./empty_bucket.sh バケット名
BUCKET="$1"

aws s3api list-object-versions --bucket "$BUCKET" --output json \
  | jq -c '[(.Versions[]?, .DeleteMarkers[]?) | {Key, VersionId}]
           | select(length > 0) | _nwise(1000) | {Objects: .}' \
  | while read -r batch; do
      aws s3api delete-objects --bucket "$BUCKET" --delete "$batch" \
        --query 'length(Deleted)' --output text
    done

aws s3api list-object-versions --bucket "$BUCKET" \
  --query '{Versions: length(Versions || `[]`), DeleteMarkers: length(DeleteMarkers || `[]`)}'
bash empty_bucket.sh example-versioned-bucket
aws s3api delete-bucket --bucket example-versioned-bucket
10
{
    "Versions": 0,
    "DeleteMarkers": 0
}

5つのバージョンと5つの削除マーカー、合わせて10件が消え、そのあとの delete-bucket はエラーなく完了しました。

boto3 版(一括復元と、空にする処理)

import boto3

BUCKET = "example-versioned-bucket"
s3 = boto3.client("s3", region_name="ap-northeast-1")


def remove_delete_markers(bucket, prefix=""):
    """最新が削除マーカーになっているキーを、マーカーを消して復元する"""
    restored = []
    pages = s3.get_paginator("list_object_versions").paginate(Bucket=bucket, Prefix=prefix)
    for page in pages:
        targets = [
            {"Key": m["Key"], "VersionId": m["VersionId"]}
            for m in page.get("DeleteMarkers", [])
            if m["IsLatest"]
        ]
        if targets:  # 1ページは最大1000件なので、そのまま delete_objects に渡せる
            res = s3.delete_objects(Bucket=bucket, Delete={"Objects": targets})
            restored += [d["Key"] for d in res.get("Deleted", [])]
    return restored


def empty_bucket(bucket):
    """全バージョンと全削除マーカーを消して、バケットを空にする"""
    boto3.resource("s3", region_name="ap-northeast-1").Bucket(bucket).object_versions.delete()


def count(bucket):
    res = s3.list_object_versions(Bucket=bucket)
    return len(res.get("Versions", [])), len(res.get("DeleteMarkers", []))


print("復元したキー:", remove_delete_markers(BUCKET, "docs/"))
print("現在の一覧:", [o["Key"] for o in s3.list_objects_v2(Bucket=BUCKET).get("Contents", [])])
print("空にする前 (Versions, DeleteMarkers):", count(BUCKET))
empty_bucket(BUCKET)
print("空にした後 (Versions, DeleteMarkers):", count(BUCKET))
s3.delete_bucket(Bucket=BUCKET)
print("バケットを削除しました")
復元したキー: ['docs/a.txt', 'docs/b.txt', 'docs/c.txt']
現在の一覧: ['docs/a.txt', 'docs/b.txt', 'docs/c.txt', 'img/logo.png', 'report.txt']
空にする前 (Versions, DeleteMarkers): (5, 0)
空にした後 (Versions, DeleteMarkers): (0, 0)
バケットを削除しました

boto3 の resource には object_versions.delete() があり、全バージョンと全マーカーの削除を1行で書けます。

エラー文と原因の対応表

エラー文 出る場面 対応
NoSuchKey … The specified key does not exist. 最新が削除マーカーのキーを、バージョン指定なしで GET した list-object-versions でマーカーを確認し、消して復元する
NoSuchVersion … The specified version does not exist. 既に消したバージョン ID を指定して GET した 一覧から今ある VersionId を取り直す
(405) … Method Not Allowed 削除マーカーのバージョン ID を指定して取得しようとした マーカーは中身を持たない。Versions 側の ID を指定する
BucketNotEmpty バージョンやマーカーが残ったままバケットを削除した 全バージョンと全マーカーを消してから削除する
MalformedXML(DeleteObjects) Objects が空の配列だった 対象が0件のときは delete-objects を呼ばない

NoSuchVersion は、先ほど完全に消した2回目の版を指定すると再現できます。

aws s3api get-object --bucket example-versioned-bucket --key report.txt \
  --version-id e26de999-da11-4ee9-93a4-cc6c045c8a41 out.txt
An error occurred (NoSuchVersion) when calling the GetObject operation: The specified version does not exist.

削除マーカーの ID を指定した head-object は 405 になります。

aws s3api head-object --bucket example-versioned-bucket --key report.txt --version-id "$DM"
An error occurred (405) when calling the HeadObject operation: Method Not Allowed

公式ドキュメントでは、削除マーカーへのアクセスについて次のように説明されています。

リクエスト 応答
バージョン ID なしの GET / HEAD(最新が削除マーカー) 404 Not Found。応答ヘッダーに x-amz-delete-marker: true
削除マーカーのバージョン ID を指定した GET / HEAD 405 Method Not Allowed。応答ヘッダーに x-amz-delete-marker: true
削除マーカーのバージョン ID を指定した DELETE マーカーが消える(復元の操作)

削除マーカーに対してできる操作は DELETE だけ、と覚えておくと迷いません。

よくある質問

Q. バージョニングを有効にする前に消したファイルは復元できますか? A. 復元できません。バージョンが残るのは、有効にしたあとの上書きと削除だけです。大切なバケットは、使い始める時点で有効にしておくと安心です。

Q. 削除マーカーを消すのと、過去バージョンをコピーするのは、どちらで復元すればよいですか? A. 削除したものを戻すなら、削除マーカーを消すのが手軽です(履歴が削除前の状態に戻ります)。上書きしたものを前の内容に戻すなら、copy-object で過去バージョンをコピーします。

Q. バージョンが増え続けると容量はどうなりますか? A. すべてのバージョンが保存容量として数えられます。ライフサイクル設定の NoncurrentVersionExpiration で「最新でなくなってから N 日たった版を消す」ルールを入れておくと、自動で整理できます。

Q. 有効にしたバージョニングを元の未設定に戻せますか? A. 戻せません。選べるのは Suspended(停止)です。停止しても、それまでに作られたバージョンは残り、バージョン ID を指定すれば取得できます。

まとめ

  • 有効化は put-bucket-versioning --versioning-configuration Status=Enabled。一度有効にすると未設定には戻らず、止めるときは Suspended にする
  • list-object-versions の Versions・DeleteMarkers・IsLatest を見れば、今どの版が最新かが分かる
  • 上書きの巻き戻しは copy-object の --copy-source に ?versionId= を付けて同じキーへコピー。履歴はすべて残る
  • バージョン指定なしの削除は削除マーカーが付くだけ。マーカーを delete-object --version-id で消すと復元できる
  • バージョン指定ありの削除は完全な削除で、取り消せない
  • 一括復元は IsLatest が true の削除マーカーだけを delete-objects に渡す。0件のときは呼ばない
  • バケットを空にするには、全バージョンと全削除マーカーを消す。boto3 なら object_versions.delete() の1行

参考・出典

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「S3バージョニングの有効化と復元 削除マーカーの消し方CLI」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?