苹果FoundationDB事务宣言

在分布式数据库领域中,高性能 + 强一致性事务是代表数据库水平高低的重要象征,苹果的开源数据库FoundationDB是媲美Google Cloud Spanner和Azure Cosmos DB,他们发布的事务宣言说明了如何在性能和事务之间做到了最好平衡的设计思路。

什么是事务?
事务是一组数据库读写操作,它能够将一个涉及一些关键属性的操作过程作为一个单元操作进行处理。首先,事务中的所有读取都会立即看到相同的数据库快照(但是不会看到其他同时执行的事务实现的数据更改结果);其次,事务中的写入操作要么全部成功要么全部失败。(失败可能是由网络连接丢失引起的。)最后,事务成功(提交)后,写入结果将永久存储。事务的这些属性(称为原子性、一致性、隔离性和持久性)是“ACID”保证的基础。我们FoundationDB认为支持ACID事务不仅仅是一个很好的附加功能,而且事务对于以高效地、以简单地方式构建稳健系统是至关重要。

大家都需要事务
每个需要支持并发客户端的应用程序都应该使用具有ACID属性的事务来构建。事务是处理并发性的最简单和最强大的编程模型,随着NoSQL技术的成熟,它将扮演越来越重要的角色。

一个常见的误解是,事务仅适用于处理金融交易的电子商务或银行业务。但是,事务的力量来自于他们对应用程序构建的工程影响,而不是来自任何特定应用程序的细节。由于ACID属性,可以组合事务以创建新的抽象,支持更高效的数据结构,并执行完整性约束。因此,使用事务构建应用程序比任何其他方法都更容易,更可靠,更具可扩展性。

也许您的系统设计上的平衡折衷迫使您放弃事务的优势以获得速度,可扩展性和容错性。以这种方式构建的系统通常很脆弱,难以管理,并且通常几乎不可能适应不断变化的业务需求。这种牺牲事务的做法很少会带来收益,特别是如果竞争者的方案可以提供大规模的事务完整性。

事务使并发性变得简单
只要多个客户端、用户或应用程序的某些部分同时读取和写入相同的数据,就会产生并发。事务使开发人员可以轻松管理并发。这通常可以通过事务很简单地实现,主要是由事务的特性隔离性完成,即ACID中的“I”。当一个系统保证事务是完全隔离的(称为“可序列化”)时,开发人员可以将每个事务看作是按顺序执行的,尽管它可能实际上是同时执行的。来自不同事务的操作之间潜在相互影响的推理与担心的负担就没有了。

略胜于无:本地交易
一些系统为有限的一组预定义操作提供ACID事务,通常受到数据模型结构的限制。例如,文档数据库可以允许在事务中更新单个文档。

但是,事务的真正威力来自于应用程序开发人员可以通过任何一组数据元素来定义它们。使用键值存储的应用程序开发人员应该能够定义读取和写入任意数量的键值对的事务。当开发人员可以自由定义事务而没有任何限制时,他们可以使用事务作为应用程序的基本构建模块。

事务激活抽象
应用程序定义的事务是可组合的。隔离性与原子性(ACID中的“A”)一起确保一个事务的执行不会影响另一个事务的可见性。这种保证使事务可以相互组合,从而可以将它们组合起来以建立新的抽象。抽象可以分层封装。例如,一个常见的抽象是维护一个索引以及主数据,以便快速查找匹配某个约束的数据项。在任何键值存储中,只要数据第二个副本(指定期望的索引字段作为key)即可轻松实现此功能。 但是,并发更新数据可能会使这个简单的设计复杂化。如果没有事务,很难确保随着数据的变化,数据和索引都会一致地更新。通过事务处理,索引层可以在一个事务中更新数据和索引,从而保证数据和索引的一致性并允许强大的抽象。

事务允许简单高效地构建抽象,提供高度可扩展的功能来支持多种数据模型。针对分层文档,列导向数据或关系数据进行优化的数据模型都可以在有序的键值存储之上分层实现。在大多数情况下,高级模型中的单个数据对象将映射到多个键值对。事务通过以原子单位包装多个键值更新来可靠地实现这些映射。

事务处理实现高效的数据表示
事务的好处不只是可以更灵活选择高级数据模型,它们还可以在指定的模型中实现更高效的数据表示。例如,想想在面向文档的数据库中常用的嵌入数据的设计模式。(来自关系数据库领域的人可能会认为这是非规范化)。在嵌入模式中,您将数据嵌套在单个文档的层次结构中(通常是复制它),而不是存储对其他可共享文档的引用。

采取嵌入做法往往是因为缺乏全局事务支持。由于大多数面向文档的数据库只允许对单个文档进行原子操作,因此开发人员必须将数据“挤”到单个文档中以启用原子更新。生成的嵌入数据文档将更大,更复杂,并且通常访问和更新的效率更低。

通过事务处理,不需要使用嵌入这种方式来实现原子更新。可以对数据元素进行建模,以优化访问效率,并在适当的时候使用多个文档,并在一个事务中进行更新以保证一致性。而且,数据可以被多个客户共享并且同时更新。同样,事务提供安全管理共享状态所需的并发控制。

事务可实现灵活性
支持重要业务功能的大多数应用程序都会遇到需求变化 (当然,他们通常需要做更多的事情,而不是更少!)您可能会想把事务完整性看作是一个很好的功能,但这对于您的应用程序来说并不重要。可以肯定的是,通过仔细设计特定的简单访问模式的数据模式,可以构建许多应用程序以避免全局事务的需要。但是,当这些应用程序发展时,灵活地修改数据模型和全局事务的能力可以使简单更改和重新设计之间的区别变得容易。

想象一下,您可以运行一个Web应用程序,在该应用程序中用户既可以生成帖子,也可以接收其他用户的帖子 所有帖子都保存在后端数据库中。管理层要求您建立一个用于做基本分析的仪表板。虽然您的数据存储是可读写的,但仪表板最初旨在执行只读查询。这些数据仅用于分析,因此如果结果有时会过时,则用户界面中没有人会注意到。总的来说,你不需要事务。您将一些优秀的数据可视化放在一起,让您的公司以新的方式分析和查看帖子。

另一方面,仪表盘证明非常受欢迎。不利的一面是,您开始听到对新功能的需求:用户希望仪表板支持使用分析找到的帖子的编辑和审核。另外,广告投放部门希望通过API访问仪表板数据来驱动他们的计费系统。您现在有一种情况,仪表板的简单只读模型已经消失,您的新API需要能够为其提供的结果提供强有力的可靠性保证。

这种需求的演变是一种自然且频繁发生的模式。事务处理能够轻松添加这些功能。

事务并不像您想象的那么昂贵
您可能不愿意使用事务,因为认为这是基于一种在性能和事务之间取舍的技术权衡,特别是针对NoSQL数据库所针对的各种高性能应用程序。但是,当您更仔细地检查这种权衡时,用户的成本是非常低。(但是,数据库工程师的成本相当高;分布式事务系统难以建立!)

性能和可扩展性?
我们知道对支持事务的系统实际上是没有可扩展性或性能的限制的。当朝着NoSQL数据库的方向发展时,早期的系统(例如Google Bigtable)采用了最小化的设计,并将重点放在可伸缩性和性能上。从关系数据库中熟悉的特点已经大量流失,并且总是假设放弃的特点对于追求可伸缩性和性能的目标是不必要的或者甚至是有害的。

这些设想其实是错误的。支持事务是工程努力的问题,而不是设计领域的折衷结果,这一点正变得日益明显。维护事务完整性的算法可以像许多其他问题一样进行分发和扩展。事务完整性确实出现在CPU成本上,但根据我们的经验,成本低于总系统CPU的10%。这是事务完整性的一个小代价,可以很容易地在其他地方得到补偿。

写延迟?
事务保证中有写入的持久性(ACID中的“D”)。这个保证伴随着写入延迟的增加。持久性意味着已经提交的写入必须保持提交状态,即使后续遭遇硬件故障。因此,持久性是容错的重要组成部分。不支持持久性的NoSQL系统在容错方面必然较弱。由于真实容错的重要性,持久事务所需的写入延迟通常是值得的。对于那些要求尽量减少写入延迟的应用,可以在不牺牲“ACI”属性的情况下关闭耐久性。

事务是NoSQL的未来
随着NoSQL数据库越来越广泛地用于各种各样的场合,构建于其上的更多应用程序可以应对大量客户端的并发。如果没有足够的并发控制,所有传统的并发问题都会重新出现,并给应用程序开发人员带来沉重的负担。ACID事务通过提供可串行化的操作来简化并发性,这些操作可以用来正确设计应用程序软件。如果您构建的应用程序需要扩展并且您没有事务,那么您最终会被毁掉。幸运的是,NoSQL数据库的可伸缩性,容错性和性能仍然可以通过事务来实现。随着技术的成熟,对事务的选择最终将不是设计基本权衡问题,而是实施工程的问题。

Transaction Manifesto — FoundationDB 5.1

相关文章:
YugaByte DB:高性能的分布式ACID事务的开源数据库

苹果开源其分布式强一致性数据库FoundationDB