詳説 Amazon S3 ストレージ料金

Amazon S3 は AWS のサービスの中でも代表的なサービスの 1 つで、AWS を使っている人のほとんどが触れたことのあるサービスじゃないかと思いますが、料金体系の詳細を知っている人は意外に少ないんじゃないんでしょうか。
年末年始に個人アカウントにあるデータの整理をする際に料金体系の説明を隅から隅まで読んで完璧に理解したつもりの上でストレージクラスを S3 Standard - Infrequent Access から S3 Glacier Deep Archive に変更したにも関わらず想定外のコストがかかったので、自分が理解したことをメモとして残します。リージョンは Asia Pasific (Tokyo) を前提としますが、他のリージョンでも同様の考え方を適用できるはずです。

S3 Standard のストレージ料金

ドキュメントに書いてあるとおり、ストレージクラスが S3 Standard の場合、最初の 50 TB / Month までは毎月かかる料金は $0.025 per GiB です。まずこの時点で躓く人もいるんじゃないかと思います。

「月の途中でオブジェクトを追加・削除した場合どうなるか?」

その問いに対する答えは FAQ の “Q: How will I be charged and billed for my use of Amazon S3?” に記載されています。

Assume you store 100 GB (107,374,182,400 bytes) of data in Amazon S3 Standard in your bucket for 15 days in March, and 100 TB (109,951,162,777,600 bytes) of data in Amazon S3 Standard for the final 16 days in March.

At the end of March, you would have the following usage in Byte-Hours: Total Byte-Hour usage = [107,374,182,400 bytes x 15 days x (24 hours / day)] + [109,951,162,777,600 bytes x 16 days x (24 hours / day)] = 42,259,901,212,262,400 Byte-Hours. Please calculate hours based on the actual number of days in a given month. For example, in our example we are using March which has 31 days or 744 hours.

Let’s convert this to GB-Months: 42,259,901,212,262,400 Byte-Hours / 1,073,741,824 bytes per GB / 744 hours per month = 52,900 GB-Months

This usage volume crosses two different volume tiers. The monthly storage price is calculated below assuming the data is stored in the US East (Northern Virginia) Region: 50 TB Tier: 51,200 GB x $0.023 = $1,177.60 50 TB to 450 TB Tier: 1,700 GB x $0.022 = $37.40

Total Storage cost = $1,177.60 + $37.40 = $1,215.00

3 月に 100 GiB と 100 TiB の S3 Standard オブジェクトをそれぞれ 15 日間と 16 日間保管した場合にかかる料金について説明されています。
その場合、1 時間単位で保存されているオブジェクトの合計サイズが 42,259,901,212,262,400 Byte-Hours になり、それを 31 日の 744 時間で割ったサイズを基に料金を求めていることがわかります。
つまり、3 月に 1 日だけ 31 GiB の S3 Standard オブジェクトを保存した場合、$0.025/GiB x 31 GiB x 1 day / 31 days = $0.025 の料金がかかり、4 月の場合は $0.025/GiB x 31 GiB x 1 day / 30 days = $0.02583… の料金がかかることになるみたいですね。

なお、FAQ やドキュメントには 1 時間だけ保管した場合の料金については言及されていない気がします。Cost Explorer を見る限りどうも日単位で請求されるようです。後述する S3 Standard - Infrequent Access の実験結果と合わせると、1 秒でも存在していれば 1 日分の料金がかかるのではなく、1 日のある時点に存在しているオブジェクトのサイズに対して料金が請求されものと予想されます。

S3 Standard - Infrequent Access のストレージ料金

ドキュメントに書いてあるとおり、ストレージクラスが S3 Standard - Infrequent Access(以降 Standard IA)の場合、毎月かかる料金は一律 $0.0138 per GiB です。ただし、次のような注釈があります。

** S3 Standard-IA and S3 One Zone-IA storage have a minimum billable object size of 128 KB. Smaller objects may be stored but will be charged for 128 KB of storage at the appropriate storage class rate. S3 Standard-IA, and S3 One Zone-IA storage are charged for a minimum storage duration of 30 days, and objects deleted before 30 days incur a pro-rated charge equal to the storage charge for the remaining days. Objects that are deleted, overwritten, or transitioned to a different storage class before 30 days will incur the normal storage usage charge plus a pro-rated charge for the remainder of the 30-day minimum. This includes objects that are deleted as a result of file operations performed by File Gateway. Objects stored for 30 days or longer will not incur a 30-day minimum charge.

つまりこういうことですね

  • 128 KiB 未満のオブジェクトは 128 KiB であるものとして料金が算出される
  • オブジェクトが作成されてから 30 日未満で削除されたり別のストレージクラスに変換された場合でも 30 日分の料金がかかる

128 KiB 未満のオブジェクトのストレージ料金は Cost Explorer 等では APN1-TimedStorage-SIA-SmObjects に計上され、30 日未満で削除されたり別のストレージクラスに変換された場合のストレージ料金は APN1-EarlyDelete-SIA に計上されるようです。
APN1-TimedStorage-SIA-SmObjects や APN1-EarlyDelete-SIA については次のページに説明が載っています。

Understanding your AWS billing and usage reports for Amazon S3 - Amazon Simple Storage Service

ここで、理解を深めるために、次のようなライフサイクルルールを設定したバケットに 100 MB のオブジェクトを Standard IA で put してみます。

% aws s3api get-bucket-lifecycle --bucket $bucket
{
    "Rules": [
        {
            "ID": "transit-to-glacier",
            "Status": "Enabled",
            "Transition": {
                "Days": 1,
                "StorageClass": "GLACIER"
            }
        }
    ]
}

put するコマンドは次のとおりです。 

dd if=/dev/zero of=output.dat bs=1024k count=100
aws s3 cp --storage-class STANDARD_IA output.dat s3://$bucket/transit-to-glacier/

put 直後の結果は記録してないんですが、今オブジェクトの内容を確認すると次のような結果になりました。

% aws s3api list-objects-v2 --bucket $bucket --prefix transit
{
    "Contents": [
        {
            "Key": "transit-to-glacier/output.dat",
            "LastModified": "2022-01-04T17:22:13+00:00",
            "ETag": "\"0c4af570340e15f2be9924e70bc34257-13\"",
            "Size": 104857600,
            "StorageClass": "GLACIER"
        }
    ]
p}

Cost Explorer で service を S3 に絞り、Usage Type で group by した結果の CSV ファイルをダウンロードして関係のあるコストだけを見てみると次のようになりました。

Date Usage Cost ($) Note
2022-01-04 APN1-Requests-SIA-Tier1 0.00013 ETag の末尾の 13 からわかるとおり、13 parts の multipart upload でアップロードされているので 13 PUT の料金
2022-01-05 APN1-TimedStorage-SIA-ByteHrs 0.0000434728 0.0138/GiB x 100 MiB / 1024 MiB/GiB x (24 hours/day x 1 day / 744 hours)
2022-01-06 APN1-TimedStorage-GlacierByteHrs 0.0000141803 32 KiB のメタデータの料金も考慮すると 0.0045/GiB x 102432 KiB / 1048576 KiB/GiB x (24 hours/day x 1 day / 744 hours)
2022-01-07 APN1-Requests-Tier3 0.00003426 Lifecycle Transition requests into S3 Glacier Flexible Retrieval ($0.03426 per 1,000 requests)
2022-01-07 APN1-TimedStorage-GlacierByteHrs 0.0000141803  
2022-01-08 APN1-EarlyDelete-SIA 0.0011302928 0.0138/GiB x 100 MiB / 1024 MiB/GiB x (24 hours/day x 26 days / 744 hours)(4 日に put して 8 日に請求されたから、5 日〜7 日は APN1-TimedStorage-SIA-ByteHrs が 0 だけど 26 日分っぽい)
2022-01-08 APN1-TimedStorage-GlacierByteHrs 0.0000141803  

なお、ストレージクラスが Standard IA や S3 Glacier Flexible Retrieval のオブジェクトはこれ以外に存在していないので、上記の料金はこの検証にかかった料金のみのはずです。

上記の結果からわかるように、Standard IA のオブジェクトを生成すると最低 30 日分のストレージ料金がかかるのかと思いきや、若干安く済むようです。単なるバグかもしれないですが。

また、S3 Standard の按分計算の考え方がわかっていれば、オブジェクトが作成されてから 30 日経過した後に月の途中でオブジェクトを削除しても 1 ヶ月丸々分の料金がかからないことは想像が付きます。

S3 Glacier のストレージ料金

S3 Glacier Flexible Retrieval と S3 Glacier Deep Archive ストレージクラスでは、ストレージ料金算出の際にオブジェクトそのもののサイズに加えてメタデータのサイズも考慮する必要があります。

For each object that is stored in S3 Glacier Flexible Retrieval or S3 Glacier Deep Archive, Amazon S3 adds 40 KB of chargeable overhead for metadata, with 8 KB charged at S3 Standard rates and 32 KB charged at S3 Glacier Flexible Retrieval or S3 Deep Archive rates.

cf. Amazon S3 Simple Storage Service Pricing - Amazon Web Services

Standard IA の APN1-TimedStorage-GlacierByteHrs の補足説明でも書いたように、例えば 100 MiB の S3 Glacier Flexible Retrieval オブジェクトがあった場合、メタデータのうち 32 KiB は S3 Glacier Flexible Retrieval の料金がかかり、8 KiB は S3 Standard の料金がかかります。

それ以外は S3 Standard と Standard IA のストレージ料金の算出方法さえわかれば他のストレージクラスでも同様に計算できるはずなので詳細は割愛します。

ストレージクラスの変更にかかる料金

S3 オブジェクトのストレージクラスを変えるには次の 2 つの方法があるようです。

  • ライフサイクルでストレージクラスを変更する
    • Lifecycle Transition requests into に対する料金がかかる
  • PUT Object - Copy API で同じキーに別ストレージクラスでコピーする

PUT Object - Copy API でストレージクラスを変更する場合に data retrieval に対する料金もかかることは FAQ の “Q: What charges will I incur if I change the storage class of an object from S3 Standard-IA to S3 Standard with a COPY request?” で次のように言及されています。

You will incur charges for an S3 Standard-IA COPY request and an S3 Standard-IA data retrieval.

この回答ではコピー元のストレージクラスの COPY request とコピー元のストレージクラスの data retrieval 料金がかかることになってますが、年始に 4,000 以上の Standard IA オブジェクトを Glacier Deep Archive にコピーしたところ、APN1-Requests-GDA-Tier1 が $0.2834243(約 4360 オブジェクト分)、APN1-Requests-SIA-Tier1 が $0.000020(2 オブジェクト分)かかっていることからも、COPY request はコピー先のストレージクラスの料金がかかるようです。
また、FAQ では Standard IA についてしか言及されていませんがおそらく Glacier Instant Retrieval も同様です。Glacier Instant Retrieval で言及がないのはこのストレージクラスが比較的新しいストレージクラスだからだと思います。他の Glacier ストレージクラスで言及がないのは直接 PUT Object - Copy API を使うことはできず、一度 restore しないといけないからでしょう。

Standard IA のオブジェクトをライフサイクルで Glacier に変更する検証にかかった料金からもわかるとおり、ライフサイクルでの変更であれば data retrieval に対する料金はかからないようです。
例えば Standard IA のオブジェクトを S3 Glacier Deep Archive に変更する場合、ライフサイクルで変更する場合は 1,000 オブジェクトに対して $0.065 かかるのに対して、COPY request は 1,000 リクエストに対して $0.065 なので、安く済ませたい場合はライフサイクルでの変更の方が data retrieval に対する料金がかからない分安いと言えます。Standard から Glacier Deep Archive に変更するのであれば data retrieval の料金がかからないのでどちらの方法を取っても料金は同じです。
もし Standard ストレージクラスの COPY request の料金がかかるのであれば、Standard ストレージクラスのオブジェクトを定期的に Glacier Deep Archive に変更する際に PUT Object - Copy API を使った方が、ライフサイクルを使うよりも安くなるということなので、このことからも COPY request はコピー先のストレージクラスに対してかかるものと思われます。

おまけ 〜 ETag の計算方法 〜

100 MiB の Standard IA オブジェクトを put する際にかかった料金の補足説明に次のように書きました。

ETag の末尾の 13 からわかるとおり、13 parts の multipart upload でアップロードされているので 13 PUT の料金

ローカルファイルと S3 オブジェクトの内容が同じかどうかチェックしたくて、etag がどのようなロジックで算出されているのか調べていた時に末尾が part の数であることを知ったんですが、Ruby だと次のようなメソッドで算出できるようです。

def calculate_digest(filename, part_size: nil, multipart_threshold: nil)
  file_size = File.size(filename)
  return Digest::MD5.file(filename).hexdigest if file_size < multipart_threshold

  part_size ||= Aws::S3::MultipartFileUploader.new.send(:compute_default_part_size, file_size)
  md5 = Digest::MD5.new
  digests = []
  File.open(filename, 'rb') do |f|
    buf = ''
    while f.read(part_size, buf)
      digests << md5.update(buf).digest!
    end
  end

  "#{md5.update(digests.join).hexdigest}-#{digests.size}"
end

part_sizemultipart_threshold はオプションで変えられるので環境によって異なります。awscli と aws-sdk-ruby でデフォルト値も違います。
算出ロジックについての AWS の公式ドキュメントは見つからなかったんですが、このロジックは例えば次のページで言及されています。

s3cmd - What is the algorithm to compute the Amazon-S3 Etag for a file larger than 5GB? - Stack Overflow

なお、multipart upload でアップロードしたオブジェクトを PUT Object - Copy API でコピーすると etag の値はコンテンツ全体の MD5 値になるようです。