ForkBase: An Efficient Storage Engine for Blockchain and Forkable Applications
Wang S , Dinh T T A , Lin Q , et al. ForkBase: An Efficient Storage Engine for Blockchain and Forkable Applications[J]. Proceedings of the VLDB Endowment, 2018.
doi>10.14778/3231751.3231762*
VLDB 与 ACM 主办的 SIGMOD、IEEE 主办的 ICDE 合称数据库领域三大顶级会议
ForkBase:一种面向区块链和分叉应用的高效存储引擎
摘要
现有的数据存储系统提供了多种的功能,以适应多样化的应用程序。然而,新的应用程序类型已经出现,例如区块链和协作分析,featuring data versioning, fork semantics, tamper-evidence或它们的任意组合。它们为存储系统提供了新的机会,通过将上述需求嵌入到存储中来有效地支持此类应用程序。
在这篇文章中,提出ForkBase,一个为区块链和分叉应用程序设计的存储引擎。通过将核心应用程序属性集成到存储中,ForkBase不仅提供了高性能,还减少了开发工作。该存储管理多版本数据并支持两种不同的fork语义,从而支持不同的fork工作流。ForkBase速度快,空间效率高,因为它有一个新的索引类,支持高效的查询以及跨数据对象、分支和版本的重复内容的有效检测。我们使用三个应用程序演示了ForkBase的性能:区块链平台、wiki引擎和协作分析应用程序。我们针对各自的最新解决方案进行了广泛的实验评估。结果表明,ForkBase在显著降低开发工作量的同时,取得了较好的性能。
引言
尽管有这么多数据库可选择,但作者发现,在现代应用程序的需求和现有存储系统必须提供什么之间存在着差距。许多现代应用程序(比特币、以太坊等)需要属性(或特性)并不适合现有的存储系统。
若没有适当的存储支持,应用程序必须在通用存储(如键值存储或文件系统)之上实现上述属性。该方法存在的问题;
1.额外的开发成本。
2.所得到的实现可能不能推广到其它应用。
定制对应的属性实现可能会带来不必要的性能开销。
例如,当前的块链平台(例如,Ethereum、Hyperledger)在键值存储的基础上构建了它们tamper evident的数据结构。然而,这种实现扩展性能差,并且当前的块链数据结构没有对分析性查询进行优化。
另一个例子是,大型关系数据集上的协作应用程序使用基于文件的版本控制系统,如git。但是它们不扩展到大数据集,只提供有限的查询功能。
显然,统一这些属性并将它们向下推到存储层是有好处的。
1.减少了对需要任何组合体的应用程序的开发工作。
2.可以帮助应用程序更好地泛化其他特性,而无需付出额外的努力。
3.存储引擎可以发开优化,而应用层难以开发优化。
ForkBase
ForkBase是Ustore2.0版本,具有丰富语义的分布式存储,提供三个关键属性:不变性、共享性、安全性。
提出Forkbase面临的挑战(即,本论文解决的两个问题):
- 在维护大量数据版本也应该保持较小的存储开销。
- 为许多类的应用程序提供灵活的语义。
解决方案概述:
- 定义了一类新的索引,称为结构不变的可重用索引(SIRI)。这便于快速查找和重复内容的有效识别。
- 提供了一个通用的分叉语义,使应用程序可灵活拥有隐式和显式分叉。
本主要贡献:
• 综合现代应用程序中共同的属性(即版本控制、伪造和篡改证据)。验证存储集成所有这些属性的好处。
• 介绍新的索引类SIRI,提出POSTree、通用分叉语义
• 实现ForkBase,为高级系统和应用程序提供了强大的构建模块,接口和丰富的数据类型。
• 通过实现三个有代表性的应用程序,即块链平台、wiki服务和协作分析应用程序来演示ForkBase的可用性。
背景和动机
data version 是指应用每一次数据更新都会产生的一个数据版本,版本历史可以是线性/非线性的。
像file system、temporal database是支持线性历史版本;Git、SVN、mercurial for files 和像Decibel、OrpheusDB是支持非线性历史。区块链也可以视为版本化系统,其中每个块都可以代表一个全局状态的版本。
针对多版本数据的重复数据消除
减少存储开销
在数据集版本控制系统中最常用的方法,
Decibel and OrpheusDB, is record-level
delta encoding. 在这种方法中,新版本只存储从前一个版本修改的记录。因此,当连续版本之间的差异很小时,它是非常有效的。但是需要组合多个增量以重构版本的内容。
OrphusDB通过将增量平铺成记录引用的完整列表来优化重构。但是,对于大型表来说,这种方法不能很好地扩展。
Delta encoding 增量编码存在的问题是,无论何时修改版本,都会创建新的副本,即使新内容与某些较旧版本的版本或不同版本的版本相同安奇。换言之,对于非连续版本、发散分支或数据集而言,增量编码不是有效的。例如,在像DataHub这样的多用户应用中。
基于块的重复检测和删除
与增量编码不同,此方法应用于独立对象。
file systems , and is a core feature of git. 在这种方法中,文件被划分为称为块的单元,每个单元都被赋予一个唯一的标识符用于检测相同的块。基于块的去重对于很少被修改的大型文件来说是非常有效的。当更新导致更改现有块时,可以使用基于内容的切片,避免昂贵的重切问题,即边界偏移问题。
在本工作中,我们对结构化数据集(如关系表)采用基于块的去重复。我们不像在增量编码中那样对单个记录进行重复数据消除,而是在主索引中的data pages上应用重复数据删除。好处我们不再需要从记录中重建它,并且可以直接访问索引和数据页,以便快速查找和扫描。
data page:数据库中的空间被划分为逻辑8KB页面。这些页面从开始就连续编号。它们可以通过指定文件ID和页码来引用。页码是始终是连续的,这样当SQL Server增长数据库文件时,新页面就会开始编号从文件的最高页码加1。
DESIGN
SIRI Indexes
Structurally-Invariant Reusable Indexes (SIRI)
数据库中现有的主要索引侧重于提高读写性能。它们不考虑数据页共享,这使得页面级数据无法去重。提出了一类新的索引,结构不变可重用索引(SIRI),它便于不同索引实例之间的页面共享。
定义一个索引结构 I ,具有以下特性
1.索引实例的内部结构由唯一一组记录确定。为了避免由修改顺序造成的结构差异,两个逻辑上相同的索引实例之间的所有页面都可以一对一地共享。
2.索引实例可以递归地由较小的实例来表示,并且开销很小。(重复数据远大于非重复数据)
3.a page 可以被许多索引实例重用。
POS-Tree
We propose an instance of SIRI indexes called Pattern-Oriented-Split Tree (POS-Tree).
除了以上SIRI的特性之外还包含一下3个特征:
- it is a probabilistically balanced search tree;
- it is efficient to find differences and to merge two instances;
- it is tamper evident.
类似于B+树和Merkle树的组合,定义节点(页)边界为从包含的条目中检测到模式,以避免结构上的差异。具体来说,为了构造一个节点,我们扫描目标条目直到出现预定义的模式,然后创建一个新的节点来保存被扫描的条目。由于叶节点和内部节点的特点不同,我们为它们定义了不同的模式。
类似于B+树,索引节点包含每个子节点的一个条目。每个条目都由一个子节点的标识符id和相应的拆分键key组成。pos-Tree也是Merkle树,因为子节点的标识符是子节点的加密散列值而不是存储器或文件指针。
Leaf Node Split
为了避免叶节点的结构差异,我们定义了类似于文件去重复系统中使用的基于内容的切片的模式。这种模式有助于将节点分解为大小相似的小节点。
给定一个k字节序列(b1,.,bk),设P是一个函数,以k字节作为输入,并返回至少q位的伪随机整数。此模式可通过滚动哈希(支持在序列窗口上进行连续计算,并具有令人满意的随机性)实现。
最初,整个数据条目列表被视为一个字节序列,模式检测过程从一开始就对其进行扫描。当模式出现时,从最近扫描的字节创建叶节点。如果一个模式发生在条目的中间,页面边界将被扩展到覆盖整个条目,使得没有条目被存储在多个页面上。以这种方式,如图2所示,每个叶节点(除了最后一个节点之外)都以模式结束。
用于分割叶节点的滚动散列具有良好的随机性,这使结构与skewed application data保持平衡。
Index Node Split
对于索引节点,使用一个更简单的函数Q,它利用作为子节点id的加密散列的内在随机性。Q检查每个子节点id直到模式出现:
当检测到一个模式时,所有扫描的索引条目都存储在一个新的索引节点中。
Construction and Updata
构建步骤:
1.数据记录按key排序,并视为序列。
2.模式函数P用于创建叶节点和相应索引项的列表。
3.函数Q重复应用于索引条目的每个级别,以构造索引节点,直到到达根节点为止。
算法1演示了这个自下而上的结构。期望的节点大小由模式函数中的参数q和r控制。
要确保节点不会无限增长,则会执行额外的约束:
更新
更新单个条目,找到目标节点应用更改,最后将其传播到路径中的索引节点,返回到根节点。使用Copy-on-write以确保旧节点未被删除。修改后,如果出现新模式,节点可能会分裂,如果模式被破坏,则可能与下一个节点合并。在任何情况下,每个级别最多有两个节点被影响。因此,更新复杂性是O(log(n)),因为树在概率上是平衡的。为了进一步从许多变化中摊销成本,支持多更新,其中索引节点为UDAT仅在将所有更改应用于多个数据节点之后。
多条目更新时,所有需要更新的叶子节点更新后再更新索引。
POS-Tree支持一种特殊类型的更新,一个完整的记录集被导出,外部修改,然后重新导入。尤其是,从给定的记录中重建一棵树,该树与旧树共享大部分节点,此时由于SIRI的特性重建树与在旧树上更新具有相同的结果。
Diff and Merge
diff:pos-Tree支持快速的diff操作,它可以识别两个POS-Tree实例之间的差异。因为两个内容相同的子树必须具有相同的根id,可以通过追踪具有不同ID的子树递归地执行Diff操作,并且修剪具有相同ID的子树。
merge :Pos-tree支持由diff阶段和合并阶段组成的三路合并。
1.第一阶段找不同
2.合并,将差异应用于两个对象中的一个
3.不需要在diff阶段到达叶节点,因为合并阶段可以直接在覆盖差异的最大不相交的子树上执行,而不是在单个叶节点上执行,如图3所示。
Sequence POS-Tree
Generic Fork Semantics
论文提出了一个通用的分叉语义,它既支持按需分叉(FoD),也支持冲突分叉(FoC)。应用程序选择要使用的语义,而存储则侧重于优化与操作相关的分叉。
Fork on Demand
因为需求(建立一个独立可修改的数据副本)。每一个分支都有用户定义的标签,分支的最新版本称为branch head,它是唯一可修改的状态。
如下图中S2成立branch head
主要操作如下(git,Decibel遵循这个语义并提供相同的操作集):
Fork on Conflict
分支是从并发和冲突的修改中隐式创建的,以避免操作阻塞和解决延迟冲突。这种分支只能通过它们的头部版本来识别,因此我们将它们称为无标记的分支。
主要操作如下:
Data Model and APIS
FNode
ForkBase采用扩展的键值对数据模型:每个对象都由一个关键字标识,并包含特定类型的值。一个键可能有多个分支。
FNode的唯一标识符uid,连接关系表示其派生关系;
context字段储存应用元数据,例如git中的提交信息,区块链中PoW的nonce值。
Tamper Evident Version
uid是对象值及其派生历史的唯一标识,uid相同时说明FNodes等效具有相同的值和推导历史。这是因为使用POS-Tree(一种结构不变的Merkle树)来存储值。此外,派生历史本质上是通过连接bases域形成的哈希链,因此两个相等的FNode必须具有相同的历史。
Value Type
ForkBase提供了许多内置数据类型。它们可分为两类:primitive and chunkable.基本类型和块状类。
Primitive基本类型包括简单的值——字符串、元组和整数。它们是为快速访问而优化的原子值。这些值没不去重,因为共享开销不大。除了基本的get和set操作之外,还提供了许多特定于类型的操作,比如对字符串和元组执行append、insert和对数值类型执行add、multiply。
Chunkable为复杂的数据结构-– Blob, List, Map and Set。每个分块值都存储为POS树(或序列POS-Tree),因此会出现重复。适用更新频繁、海量数据。细粒度访问方法自然由POS-tree支持,如查找、插入、更新和删除。
APIs
表1列出了ForkBase支持的基本操作。
ForkBase中的数据可以在两种粒度下操作:在单个对象或对象分支处。ForkBase公开了易于使用的接口,这些接口结合了对象操作和分支管理。图6显示了Forking和编辑Blob对象的示例:
put用于插入与更新
value field 可存放一个全新值、经过一系列更新的基本对象。
在批处理中对同一个对象提交多个更新,ForkBase只保留最终版本。
SYSTEM IMPLEMENTATION
在本节中,我们将介绍ForkBase的实现细节。系统既可以作为嵌入式存储运行,也可以作为分布式服务运行。
图7显示了由四个主要组件组成的ForkBase群集的结构:master, dispatcher, servlet and chunk storage.主服务器、分派程序、服务程序和区块存储。
主服务器维护集群运行时信息,而请求分派程序则接收请求并将请求转发给相应的servlet。每个servlet管理由路由策略确定的密钥空间的不相交子集。servlet还包含三个子模块,用于执行请求:
1.access controller:访问控制器在执行前验证请求权限;
2.branch table : 分支表维护标记和未标记分支的分支头;
3.object manager : 对象管理器处理对象操作,将内部数据表示形式隐藏在主执行逻辑中。
chunk storage 区块存储给数据块提供访问,所有块存储实例都形成一个共享存储池,远程servlet可以访问它。实际上,每个servlet都与本地块存储共存,以支持快速数据访问和持久性。当使用ForkBase作为嵌入式存储时,例如在块链节点中,只实例化一个servlet和一个块存储。
Internal Data Representation
数据对象以数据块的形式存储。原始对象由单个块组成,而块对象则包含多个块。
Chunk and cid chunk是ForkBase中存储的基本单元,它包含一个字节序列。每个块有唯一标识cid,它是根据块中的字节序列使用加密散列函数计算出来的。
FNode and POS-tree 一个FNode被序列化并存储为一个meta chunk。 uid == cid 存储在索引条目中的子节点id是相应块的cid。
Data Types对于原始对象,它的值嵌入meta chunk的数据字段中,以便快速访问。对于块状对象,数据字段包含一个cid,它指向相应POS-Tree的根。按需求访问对应的POS-Tree节点,而不是直接获取整棵树。
Chunk Storage
块存储保存数据块并支持使用cid检索,它公开了一个键值接口,其中密钥是所提交的cid。当请求包含现有块时,存储将检测它并立即返回。
Branch Management
每个键都有一个分支表,用于保存所有分支的“头”。分支表包含两个分别用于标记和未标记分支的结构。
TB-table 标记的分支保留在TB-table的映射结构中,其中每个条目由标记和头cid组成。
UB-table未标记的分支保留在UB-table的集合结构中,其中每个条目只是冲突分支的头ID。其中每个条目只是一个冲突分支的头部cid。
Conflict Resolution在合并(M7-M9)操作中使用了一种三路合并策略。如果合并失败,则返回冲突列表,要求解决冲突。这可以在应用层处理,合并的结果会返回到存储。另外,简单的冲突可以使用内置的解析函数(such as append, aggregate and choose-one)来解决。在发生冲突时,ForkBase还允许用户挂起定制的解析函数。
Cluster Management
当ForkBase作为分布式服务部署时,它使用基于哈希的两层分区,在集群中的节点之间均匀地分配工作负载:
Request dispatcher to servlet调度器接收的请求并划分发送到相应的servlet(划分依据: keys’ hash)。
Servlet to chunk storage在servlet中创建的块根据cid进行分区,然后转发到相应的块存储。
APPLICATION DEVELOPMENT
在本节中,我们使用ForkBase构建三个应用程序: a
blockchain platform, a wiki engine and a collaborative analytics
application.描述如何满足应用程序的需求,并减少了开发工作。
Blockchain
简单介绍区块链。本文主要从两个方面对ForkBase与Hyperledger的集成进行了研究。
- Hyperledger是最受欢迎的区块链之一,它支持图灵完备智能合约,易于评估存储组件。2
- 该平台针对企业应用程序,其对数据处理和分析的需求比诸如密码之类的公共块链应用程序更加明显。
Data Model in Hyperledger
状态由Merkle树保护,任何修改都会导致新树的出现;state delta(单独结构)中保存旧的值和树,只有更新后的交易存储在块中。写操作被缓冲在存储器内结构中。系统打包多个交易并提交一个请求,提交操作首先创建一个新的Merkle树,然后创建一个新的状态和一个新的块,最后将所有更改写入存储。
Blockchain Analytics区块链系统的一个初始目标是安全地记录状态,因此,设计侧重于防伪和数据版本控制。随着区块链应用的发展,存储在分类账上的海量数据为分析提供了宝贵的机会。
在本工作中,我们考虑了关于BlockChain数据的两个代表性分析查询:
- state scan第一个查询是全局扫描,它返回给定状态的历史记录,即当前值是如何产生的。
- block scan第二个查询是块扫描,它返回特定块的状态值。
当前的超级分类账数据结构是为快速访问最新的状态而设计的。当前的Hyperledger数据结构是为快速访问最新的状态而设计的。然而,上面的两个查询需要遍历到前面的状态,并涉及到使用状态增量进行计算。我们通过添加一个预处理步骤来实现这两个查询,这个预处理步骤解析所有块的所有内部结构并构造一个内存索引
Hyperledger on ForkBase
一个FNode完全捕获了一个块的结构及其相应的状态。我们将Merkle树和状态增量替换为两个级别组织的Map对象。
该模型的好处,用于维护数据历史和数据完整性的代码十分简单。特别是,对于使用ForkBase的18行代码,可以从Hyperledger代码中删除了1900行代码。另一个好处是,这些数据现在可以很容易地用于分析。
对于state scan查询,我们只跟踪存储在最新块中的uid,以获取所请求密钥的最新Blob。同时,可以按照以前的版本检索以前的值。
对于block scan查询,我们按照存储在请求块中的uid来检索二级Map,然后我们遍历所有条目以获得相应的Blobs。
Wiki Engine
wiki引擎允许协同编辑文档(or wiki pages)每个条目都包含线性版本的线性链。wiki引擎可以构建在多版本键值存储的基础上,其中每个条目都映射到一个wiki页面。
eg.维基百科,这些版本被存储为独立的记录,因此需要额外的机制来捕获和利用它们的关系。
Data Model in ForkBase ForkBase适合这种多版本的键值模型。读取和写入一个条目直接映射到在默认分支上。使用Blob类型而不是字符串,因为它有助于消除版本中的重复项。
在访问连续版本时,ForkBase客户端可以重用共享数据chunks以更快速地服务。
Collaborative Analytics
协作分析是一种新兴的应用程序,它允许一组科学家(或分析师)处理具有不同分析目标的共享数据集。例如,在客户购买记录的数据集中,一些分析师可以执行客户行为分析,而其他分析师则使用它来改进库存管理。同时,其他分析人员可能会不断地清理和丰富数据集。由于分析人员同时处理不同版本,因此显然需要版本控制和按需分叉语义。
Data Model in ForkBase ForkBase拥有丰富的内置数据类型集合,为构建不同的数据模型提供了灵活性。我们针对关系数据集执行了两个布局:
- 面向行,记录被存储为元组,嵌入在由其主键键入的映射中
- 面向列,列的值被存储为列表,嵌入在由列名键入的映射中。
其他数据集管理系统需要在访问时进行版本重建,但是ForkBase可以直接访问任何版本中的任何数据页,此外,POS-Tree的数据集去重复工作,从而大大降低了存储需求的规模。
EVALUATION
30K行C++代码实现了ForkBase。
首先评估ForkBase的操作性能,再接下来,我们从存储消耗和查询效率两个方面对§6中讨论的三个应用程序进行了评估。
附
ForkBase是一种数据存储系统,可支持数据版本控制、分叉和防篡改等功能,可以针对应用需求任意组合上述功能,已减少开发工作。并提供入高效历史查询等额外功能,无需额外开销。
ForkBase为建立在底层对象存储系统上的键值存储系统,直接支持版本控制、分叉和篡改证据。ForkBase的核心是一种新颖的索引结构,名为POS-Tree