F1异步schema更新

简介

schema为数据库对象的集合,无论db的底层存储如何实现,对于用户而言,表象层面上所呈现的模式结构都是通过schema定义的。例如graph db的底层可以用kv、kkv等键值关系对实现,而上层可定义节点、属性、边、索引等来适应业务场景。

以前业内往往用中间件+NoSQL|SQL作为分布式场景下的事务解决方案,而现在各家厂商逐渐都在向NewSQL(不仅具有NoSQL对海量数据的存储管理能力,还保持了传统数据库支持ACID和SQL等特性)发力。Google的F1属于NewSQL的领军者,存储层采用Spanner作为分布式KV存储引擎,计算层则是F1团队研发的分布式SQL引擎。本文主要参照 Online, asynchronous schema change in F1 这篇论文来学习面向F1的异步schema更新。

F1具有以下几个特征:大规模分布式关系型schema共享数据存储无状态无全局成员。因此对于其schema的变更也需要做到以下几点:全数据可用、最小化性能影响、异步schema变更。故而在设计异步schema变更的方案时需要考虑以下因素
1. 由于所有数据必须尽可能可用,所以不限制对正在进行重组(Data Reorganization)的数据的访问
2. 允许事务执行过程跨越schema变更
3. 多版本的schema可能被同时使用

由于数据对所有服务器可见,同时使用多个版本的schema会引起数据错乱。因此必须保证在任意时间内不超过2个schema被使用,且这些schema需含有特殊属性。在schema变更过程中存在的所有中间状态如下图所示

Intermediate states used during schema changes

相关背景

Key–value存储

F1是基于Spanner之上的,且在2点上优化了并发控制

  • commit时间戳:每对k-v都含有最近一次更新的时间戳信息(信息更新是原子操作)
  • 支持原子操作test-and-set:多重getput操作具有原子性

关系型schema

F1中table的定义包含:column(包含type)二级索引完整性约束(外键或索引的unique约束)乐观锁。其中乐观锁是必须的且无法被用户事务读取。column的值为基本类型或复杂类型(同protocol buffers),主键的值仅限于基本类型。

Row表示

row在存储层面被表示成k-v对的集合,每个非主键的column均包含一对。每个key在逻辑上包含 table名当前row主键的值被存储的column的名称。row中包含一个特殊的column exists用于表示row是否存在。示例如下

table定义

Example
first_name*last_name*agephone_number
JohnDoe24555-123-4567
JaneDoe35555-456-7890

key-value表示

keyvalue
Example.John.Doe.exists
Example.John.Doe.age24
Example.John.Doe.phone_number555-123-4567
Example.Jane.Doe.exists
Example.Jane.Doe.age35
Example.Jane.Doe.phone_number555-456-7890

F1同时支持二级索引,二级索引的组成为:table名索引名被索引的column的值主键的值。对于上述例子中column age建立名为IndexAge的索引,示例如下

keyvalue
Example.IndexAge.24.John.Doe
Example.IndexAge.35.John.Doe

F1支持增删改查这几种关系型操作,并基于schema转化到k-v存储层,每个操作均保证db关于schema的一致性。

并发控制

F1使用一种基于时间戳的乐观并发控制,类似于Percolator。F1的并发控制与schema变更相关,因为F1的schema在每个table上都包含一个额外的元素:乐观锁。每个table可能含有多个锁,但每个column都只与一个乐观锁关联。每个row都含有自身schema中定义的每个乐观锁的实例,这些实例控制多个事务对column的并发访问。

当客户端读取column值作为事务的一部分时,会从覆盖这些column的锁中收集最后修改的时间戳。在事务提交时,这些时间戳将被提交给服务器并进行验证,以确保它们没有更改。如果验证成功,则事务所修改的column所关联的所有锁的last-modified timestamps将被更新为当前时间。事务限制为最多一次逻辑写操作(可以修改多个单独的row),且写操作具有原子性。

默认情况下,F1实现了行级锁row-level lock。然而,因为用户可以为table添加新的锁并为它们关联任意的column,所以F1的用户可以按需从row ~ column级别中选择一种锁粒度。

schema变更

schema数据是作为特殊的k-v对被存储的,被称为Database Representation。当schema被更新到一个新版本时,触发schema change流程。F1是一个拥有数百台服务器的高度分布式的系统,因此无法在服务器间进行同步操作。只有当所有F1服务器都更新到新版schema后才说明schema变更成功。协议限制所有服务器使用最新或次新版的schema。

schema元素和状态

column、索引、约束、乐观锁被称为schema元素,且每个元素均有状态。其中2种为非中间状态:absentpublic,还有2中内部的中间状态:delete-onlywrite-only

  • delete-only的table、column、index的k-v值无法被用户事务读取,并且
    • table和column仅能被delete操作修改
    • index仅能被delete和update操作修改。其中update操作能删除相关index的k-v对但不能创建新的
  • write-only的column、index的k-v值能被insert、delete、update操作修改,但无法被用户事务读取
  • write-only的约束对所有新的insert、delete、update操作有效,但不保证对所有现存的数据有效

数据一致性

如果db的表现d关于schema S是一致的,则以下条件必须满足

  1. column必须包含在table和row之中
  2. row必须包含public required column相关的k-v对
  3. 每个索引的k-v对必定在schema中有对应的定义
  4. 如果table R中存在public索引 I,则对于R中所有row都有对应的k-v对
  5. 所有的index entries都指向有效的row
  6. 所有public约束都必须被遵从
  7. 不存在未定义的值

定义2种异常:

  • orphan data:含有不该存在的数据
  • integrity:缺失了本该含有的数据

设op(s)为基于schema s的增删改查操作,称schema从s1变更到s2的过程是consistency preserving,当且仅当db的表现d同时关于s1和s2是一致的,需满足以下条件

  • 任意操作op(s1)都保持d关于s2的一致性
  • 任意操作op(s2)都保持d关于s1的一致性

添加|删除schema元素

同上文所述,由于多个schema版本可能被同时使用,为了不破坏一致性,schema中的元素必须含有中间状态。

Optional元素

如果一个schema中的元素E是optional,那么添加E时,状态转换过程为

absentdelete onlypublic a b s e n t → d e l e t e   o n l y → p u b l i c

证明如下:

  • absent -> delete-only:该过程只删除不增加,所以避免了orphan data异常;由于E是optional,s1和s2都不会强制添加任何新的要求或约束,避免了integrity异常
  • delete-only -> public:使用s1和s2的删除操作都能正常运行,避免了orphan data异常;由于E是optional,避免了integrity的异常

删除元素E时的状态转换过程与添加相反。然而,删除元素还需要一个额外的步骤:必须删除与被删除元素相关的k-v数据。为了保持一致,这必须发生在schema变更之前。

Required元素

添加Required元素的状态转换过程如下

absentdelete onlywrite onlydb reorgpublic a b s e n t → d e l e t e   o n l y → w r i t e   o n l y ⟶ d b   r e o r g p u b l i c

删除元素的状态转换过程与添加相反,且数据重组发生在状态转换到absent之前

  • 参照上文所述,从absent到delete-only的状态转换不存在异常。
  • delete-only和write-only都能删除与新增元素E相关的数据,且进入write-only状态后,数据变更会同时更新索引,所以不会出现orphan data异常。由于与E相关的数据不需要对外可见,integrity异常也不存在。
  • 当进行reorg操作时,需要确保没有服务器处于delete-only状态,并开始补全与新元素相关的缺失数据。

约束

F1支持外键和索引的唯一性完整性约束,添加删除这些约束会引起integrity异常。添加约束相关状态的转换过程如下:

absentwrite onlypublic a b s e n t → w r i t e   o n l y → p u b l i c

删除约束的状态转换过程与添加相反

变更锁覆盖范围

添加删除锁等同于column的操作,详见上文。变更schema中锁覆盖范围的操作如果实现不当,会造成non-serializable schedules。为了解决这个问题,我们允许column可以临时被多个锁覆盖。当把一个column的锁换成另一个时,需要插入一个内部状态dual-coverage表示column被新老2个锁覆盖。所有与该column相关地操作必须读取、验证和更新关联的锁。

上述过程可以看做将1次锁覆盖范围的变更转化为2次schema变更,其中1个schema含有dual-coverage作为中间步骤。假设column C被S1的锁L1,S2的L1和L2,S3的L2覆盖。当所有服务器都切换到S2(处于状态dual-coverage)后,执行db重组:对于row(含有处于状态dual-coverage的column)通过原子操作设置锁的时间戳:

timestamp(L2)=max(timestamp(L1),timestamp(L2)) t i m e s t a m p ( L 2 ) = m a x ( t i m e s t a m p ( L 1 ) , t i m e s t a m p ( L 2 ) )

实现

Spanner

Spanner有2个重要的feature:

  • Garbage collection 垃圾回收
  • Write fencing 每个独立的写操作可设置deadline,使得操作基于最新或次新的schema

schema租约

每个F1服务器协定了长达数分钟的schema lease(租约),到期后再从一个已知的地方重载schema来续约。如果无法续约,则终止服务并等待被重启。

用户的事务被允许跨多个租约,但需要限制写操作只能使用具有有效租约的schema。这是十分必要的,因为在提交的时候,写操作就被转换成k-v存储的相关操作。如果一个单独的写操作需要超过2个lease来执行,则违反了基本要求(任何时候只有最新2个版本的schema可被使用)。这些可以用Write fencing来实现。

优化:

  • 使用Spanner的原子操作test-and-set同时复制schema的标准副本到多个位置
  • 在F1服务器上缓存当前schema的commit时间戳。当F1服务器续约时,先读取标准schema的时间戳,如果时间戳和缓存的相同,则无需重新读取schema

数据重组

  • 整个数据重组过程不具有原子性。因此,数据重组操作必须可回滚幂等
  • 重组过程中所有数据必须可用
  • 应尽量避免重复执行用户事务中的数据更改

数据重组需要用到MapReduce框架。每个map任务对于其负责的partition,扫描schema变更开始时的snapshot,更新每一行以符合新schema。在更新每一行的时候判断其在重组开始之后是否被用户事务更新过,如果是的话则不做修改。

schema仓库

因为逾期而被终止并重新提交的写操作会造成资源浪费,所以可以通过尽早续约来减少写失败的可能性。但频繁的续约操作会增加F1和Spanner的系统负载,因此建议在租期还剩一半的时候进行续约。

总结

关于schema状态(例如delete only、write only),通常理解为当所有可用的服务器上的schema均切换到某一状态后才表示可以开始切换到下一状态。

总体上论文所呈现出来的是一个可行且完善的分布式异步schema变更的解决方案,但这个方案中的很多地方都隐含着一个重要的前提:基于时间戳的操作(涉及乐观锁、重组、事务等)必定需要一套可信的时序机制作为基本保障。财大气粗的Google研发出了基于GPS和原子时钟的TrueTime(详见Spanner)来控制时间误差,即硬件级解决方案。软件层面上Cockroachdb使用的Hybrid Logic Clock实现起来非常复杂。所以一种简单可行的方法就是打破 无状态、无全局成员 的限制,用一个节点专门用来提供统一的授时服务。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值