Jepsen 分析报告:Amazon RDS for PostgreSQL 17.4 的一致性问题
JEPSEN
BlogAnalysesTalksConsistencyServicesEthics
Amazon RDS for PostgreSQL 17.4
Kyle Kingsbury 2025-04-29
Amazon RDS for PostgreSQL 是一个由 Amazon Web Services (AWS) 提供的托管 PostgreSQL 数据库实例的服务。 我们的测试表明,Amazon RDS for PostgreSQL 的多可用区 (multi-AZ) 集群违反了 Snapshot Isolation 隔离级别,即所有端点支持的最强一致性模型。 在健康集群中,偶尔会出现 Long Fork 和其他 G-nonadjacent 循环现象。 这些现象出现在我们测试的每一个版本中,从 13.15 到 17.4 均存在。 Amazon RDS for PostgreSQL 可能实际上提供的是 Parallel Snapshot Isolation。 这项工作由 Jepsen 独立完成,没有接受任何报酬,并遵守 Jepsen 伦理政策。
1 背景
PostgreSQL 是一种流行的开源通用 SQL 数据库。 它使用多版本并发控制 (MVCC) 来提供 三个级别的事务隔离。 PostgreSQL 的 “Read Uncommitted” 和 “Read Committed” 实际上都是 Read Committed 隔离级别。 “Repeatable Read” 级别实际上提供的是 Snapshot Isolation,而不是 Repeatable Read 隔离级别。“Serializable” 提供的是 Serializability。
Amazon RDS for PostgreSQL 是一项由 AWS 提供的托管 PostgreSQL 集群的服务。 RDS 可以自动完成配置、存储管理、复制、备份、升级等操作。 Multi-AZ deployments 将数据库节点分布在多个可用区中,从而降低了关联故障的可能性。 RDS 使用同步复制来确保事务在主实例和(至少一个)辅助实例上持久化后才确认。
从用户的角度来看,Amazon RDS for PostgreSQL 提供了一对 URL,它们使用 PostgreSQL wire 协议进行通信:一个用于读写事务的主端点,以及一个用于只读事务的读取器端点。 主端点支持所有 PostgreSQL 隔离级别,而辅助端点不支持 Serializable。 因此,所有节点支持的最强级别是 Snapshot Isolation(PostgreSQL 称之为“Repeatable Read”)。
2 测试设计
我们修改了 Jepsen 的 PostgreSQL 测试库,以便与 Amazon RDS for PostgreSQL 一起使用,并使用了一个 小型包装程序。 对于每一轮测试,我们都使用 AWS 的 CreateDBCluster
API 配置了一个 RDS 集群, 使用 gp3
存储和 db.m6id.large
实例。 然后,我们启动一个 EC2 节点来运行我们的测试,并向其提供 RDS 集群的主端点和只读端点。 我们没有进行任何故障注入,也没有触发任何故障转移。
正如我们在 之前对 PostgreSQL 的研究 中所做的那样,我们的 主要工作负载 由对唯一整数列表进行的事务组成。 我们将每个列表存储在单行中,编码为以逗号分隔值的 TEXT
字段。 事务可以按主键读取列表,也可以使用 CONCAT
将唯一整数附加到列表。 这种工作负载允许我们的 Elle checker 验证各种隔离级别,主要是通过推断事务之间的数据流依赖关系,并在结果图中查找循环。
3 结果
在健康状况下,以适度的并发性,Amazon RDS for PostgreSQL 17.4 每隔几分钟就会出现 G-nonadjacent 循环。 考虑 这个为时两分钟的测试运行,它执行了大约每秒 150 个写事务,以及每秒 1600 个只读事务。 它包含以下四个事务的循环:
从上到下,将这些事务分别称为 T 1、T 2、T 3 和 T 4。 T 1 将 9 附加到第 89 行,得到列表 [4 9]
,T 2 观察到了这一点。 T 3 将 11 附加到第 90 行,得到列表 [11]
。 该版本被 T 4 覆盖,后者将 3
附加到第 90
行,并读取了结果列表 [11, 3]
。 虽然 T 2 观察到了 T 1 对第 89 行的附加操作,但它未能观察到 T 3 对第 90 行的附加操作。 类似地,T 4 观察到了 T 3 对第 90 行的附加操作,但未能观察到 T 1 对第 89 行的附加操作。
由于此循环包含 read-write dependencies,并且这些依赖关系彼此不相邻,因此该循环是 G-nonadjacent 的,违反了 Snapshot Isolation。 这种情况不应该在标准 PostgreSQL 的 “Repeatable Read” 级别中发生,而且我们也没有在那里观察到它。
为了理解为什么这个循环是不合法的,请回想一下,在 Snapshot Isolation 中,每个事务(显然)都作用于在某个开始时间戳 s 拍摄的数据库快照。 该事务的效果在稍后的提交时间戳 c 时对其他事务可见。 为了使 T 2 读取 T 1 的附加操作,其开始时间戳必须跟随 T 1 的提交时间戳:c 1 < s 2。 由于 T 2 没有观察到 T 3 的附加操作,因此 s 2 < c 3。 由于 T 4 覆盖(并观察)了 T 3,因此 c 3 < s 4。 但是 T 4 没有观察到 T 1 的附加操作,所以 s 4 < c 1。 我们有矛盾! 这些时间戳不可能彼此都在对方之前。
这个循环也是 Long Fork 的一个例子。 第一个和第二个事务组成了状态的一个逻辑分支。 第三个和第四个事务组成了第二个分支。 每个分支更新不同的行,但任何一个分支都没有观察到对方的效果。 奇怪的是,我们 没有 观察到 Short Fork,也称为 Write Skew。 这表明 Amazon RDS for PostgreSQL 可能提供 Parallel Snapshot Isolation,这是一种稍微弱一些的一致性模型。
我们观察到了各种 G-nonadjacent 异常,包括那些仅通过写-读边连接的异常,以及一些包含四个以上事务的异常。 它们出现在我们测试的每个 PostgreSQL 版本中,从 13.15(AWS 支持的最旧版本)到 17.4(最新版本)。
4 讨论
从 Long Fork 和其他 G-nonadjacent 循环的存在,我们得出结论,Amazon RDS for PostgreSQL 多可用区集群不能保证 Snapshot Isolation。 相反,它们可能提供 Parallel Snapshot Isolation,这是一种稍微弱一些的模型。 在这方面,Amazon RDS for PostgreSQL 多可用区集群提供的安全性语义比单节点 PostgreSQL 系统弱,因为 在我们之前的测试中,单节点 PostgreSQL 系统似乎提供了 Strong Snapshot Isolation。
Amazon RDS for PostgreSQL 的用户可能希望检查其事务结构,以便发现 Long Fork,或者设计实验来验证其预期的不变性是否得到保留。 一个读取事务可能与其他事务在事务执行的顺序上存在分歧。 由于这些异常似乎涉及到针对只读辅助节点的查询,因此可能可以通过仅使用写入器端点或确保每个安全关键型事务都包含至少一个写入操作来恢复 Snapshot Isolation。
本报告是对 Amazon RDS for PostgreSQL 行为的一次粗略探索的结果,我们没有详细研究。 与往常一样,Jepsen 采用实验方法进行安全验证:我们可以证明错误的存在,但不能证明错误的缺失。 尽管我们尽了最大的努力去发现问题,但我们无法证明正确性。
感谢 Irene Kannyo 的编辑支持。 这项工作由 Jepsen 独立完成,没有接受任何报酬,并遵守 Jepsen 伦理政策。
Copyright © Jepsen, LLC. 我们会尽最大努力提供准确的信息,但如果您发现错误,请告知我们。