存储类改变的不只是每月保存字节的价格,还包括取回方式、故障韧性和计费结构。首先应确定业务允许多久恢复数据,以及丢失后是否有其他可重建副本。Standard-IA 可以立即读取,但取回另行收费;Glacier Flexible Retrieval 和 Deep Archive 则需要先恢复,再执行普通读取。One Zone-IA 还有不同故障边界:它不能抵御所在可用区被物理摧毁。相同的宣传持久性数字,不能替代对实际故障域的判断。

先拆清费用,再比较总额

使用桶所在区域的当前价目表,明确存储类、取回档次与请求种类。存储取决于计费容量和持续时间;取回可能同时按字节及请求计费。逐对象迁移请求和归档元数据则随对象数量增长。不能把所有费用都说成只由对象数量决定,也不能从某一条存储单价直接推出整个工作负载的节省比例。同样大小的数据拆成大量小对象,成本模型可能完全不同。

不要把 Flexible Retrieval 和 Deep Archive 的标准取回统一当成免费。AWS 明确提供 Flexible Retrieval 批量取回及其请求免费,Intelligent-Tiering 则有另外的标准和批量取回免费规则。这些例外不能推导出其他存储类和档次也免费。归档恢复后,原归档对象仍在;临时可读副本还会按选定保留时间收取 Standard 存储费。只计算原归档的低价,会漏掉这个并存阶段。

即使对象提前消失,最短存储期限仍然影响费用:Standard-IA 和 One Zone-IA 为三十天,Glacier 即时与灵活取回为九十天,Deep Archive 为一百八十天。提前删除、覆盖或迁出可能产生剩余期限费用。Standard-IA、One Zone-IA 和 Glacier Instant Retrieval 有 128 KB 的最小计费对象大小;灵活取回与深度归档则每对象增加四十 KB 元数据,分别按 Standard 与归档费率计费。

生命周期规则必须对应工作负载

对象年龄不是距离上次访问的时间。三十天后迁移的规则,也会命中一份已经保存一年、却仍然每周读取的报告。需要同时评估对象尺寸、取回字节数、请求频率、保留时间及可接受的恢复延迟。前缀和标签可以分开不同工作负载,但只有分类长期准确时才有效,不能把存储时间长直接解释为访问已经变少。

二零二四年九月开始的新默认行为,阻止小于 128 KB 对象的生命周期迁移。没有修改的旧配置可能保留此前行为,显式大小过滤器也可以覆盖默认限制。因此,一个小对象仍在 Standard,不能独自证明规则已经坏了。应检查配置实际生效的过滤条件和变更历史,再判断它为何保留原存储类,而不是看到某次查询结果就猜测迁移失败。

坏掉的样子

存储小计下降,可能掩盖取回和请求费用上升。反过来,已经恢复的临时副本在有效期内被多次读取,也不一定每次都重新发起归档恢复。应按真正的“恢复后读取”流程建模,把临时副本存储算进去,不能机械地将归档恢复费用乘上每一次 GET。访问频率与恢复请求频率有关,却不是同一个数。

恢复耗时同样是运维依赖。灵活取回的标准请求通常需要三至五小时;深度归档的标准请求通常在十二小时内完成,批量通常在四十八小时内完成。这是文档描述的典型预期,不是无条件的截止保证。承诺恢复时间目标之前,应实际演练恢复并确认完成;请求已接受、恢复正在进行和内容已经可读,是不同阶段。

快速验证

用 Python 3 运行下面独立脚本;本轮以 Python 3.12.14 执行。价格是刻意虚构的教学输入,不是当前 AWS 报价,也不是完整账单。模型比较一个 GiB 保存十二个模型月份,假定每次读取都取回整个对象。把参数说清楚之后,才可以讨论为什么访问量变化会让选择反转。

from decimal import Decimal as D

# Invented rates, not an AWS quote; one GiB held for twelve modeled months.
warm_storage = D("0.02")
cold_storage = D("0.01")
retrieval_per_gib = D("0.01")
transition_per_object = D("0.001")


def annual_cost(full_reads):
    warm = 12 * warm_storage
    cold = 12 * cold_storage + full_reads * retrieval_per_gib + transition_per_object
    return warm, cold


assert annual_cost(0) == (D("0.24"), D("0.121"))
assert annual_cost(52) == (D("0.24"), D("0.641"))
assert annual_cost(11)[1] < annual_cost(11)[0]
assert annual_cost(12)[1] > annual_cost(12)[0]
small_bytes, minimum_bytes = 10 * 1024, 128 * 1024
assert D(max(small_bytes, minimum_bytes)) / small_bytes == D("12.8")
assert max(10, 180) == 180  # Illustrative minimum-duration billing boundary.
for reads in (0, 11, 12, 52):
    print(reads, "full reads:", tuple(map(str, annual_cost(reads))))
print("10 KiB under a 128 KiB floor: 12.8x billable size")
print("Excluded: GET requests, transfer, restored copies, tax, and negotiated rates")

零次读取时,较冷模型的 0.121 低于 0.24;五十二次完整读取后,0.641 反而高于 0.24。在这组输入下,十一次仍便宜,十二次就不再便宜。尺寸计算展示小对象起计门槛为何需要独立处理。这些结果都不是通用的存储类切换阈值,也没有验证云服务上的实际迁移或计费。

下面保留原有 AWS CLI 诊断命令。需要已授权桶的读取权限,并替换 B 与 K;本轮没有执行这些命令:

aws s3api list-objects-v2 --bucket B --query 'Contents[].StorageClass' --output text | tr '\t' '\n' | sort | uniq -c
aws s3api get-bucket-lifecycle-configuration --bucket B --query 'Rules[].[ID,Status,Transitions[0].Days,Transitions[0].StorageClass]' --output table
aws s3api head-object --bucket B --key K --query 'StorageClass'

清单成功且非空时,第一条按存储类统计当前对象个数,不统计字节或非当前版本。空清单和 CLI 错误需要另外处理;管道退出成功,本身不证明 AWS 调用成功。第二条只显示每条规则的第一个迁移,遗漏过滤条件、后续迁移与非当前版本迁移,决策前必须检查完整响应。HeadObject 对 Standard 不返回 StorageClass,所以投影为 null 本身不是故障。这些查询都不能测量访问频率,也不证明尚待处理的迁移或恢复已经完成。应结合完整策略、清单、访问证据与账单之后再改变生命周期行为。

来源