F1 在线异步执行schema change
论文《Online, Asynchronous Schema Change in F1》
总结
对,先来个总结。
F1拥有几百个无状态的F1 server,其内存中会缓存schema信息。不能做到同时让所有的F1 server同时应用某个schema,因此存在不同F1 server访问不同schema的状态。这篇论文就是介绍了如果在两个schema同时存在时,怎么操作而不破坏数据库的完整性。
简介
本论文的目的在于介绍F1在线异步schema change的原理和实现。
F1基于Spanner之上,Spanner是一个KV存储,具有以下特性:
- 大规模分布式。有上百个独立的F1 server;
- 关系型模型。每个F1 server都会存储一份关系型的模型描述,包括表、列、索引和约束;
- 共享数据存储。F1 server不存储数据,都从Spanner访问;
- 无状态。F1 server是无状态的, 不存储数据;
- 不记录全局成员信息。
schema change的设计约束:
- 全部数据可用。不存在停止服务时间;
- 性能影响最小化;
- 异步schema change;没办法做到所有的F1 server同步变更。
不能直接变更的例子:
假如某张表R从schema S1变更到S2,增加了一个索引I。两个不同的F1 server M1和M2做这样的动作:
- M2使用S2插入一行r到表R。因为S2里面包含了索引I,所以会插入索引I;
- M1使用S1删除行r。因为S1中不包含索引I,所以不会删除对应的索引I。
这时如果另一个F1 server通过索引I访问数据时,就会访问到被删除的行r。
背景
KV存储
Spanner是以KV的方式存储数据的,包括索引。次级索引(Secondary Index)在Spanner中存储在单独的表中。具体参考F1和Spanner的论文。
关系模型(Relational Schema)
Spanner中数据按照KV存储,表定义(schema definition)由F1 server解释。表包含列、次级索引、完整性约束(外键和唯一索引),还有乐观锁(wangyl11:乐观锁相关的推论将直接忽略)。
两个重要的概念:
- Required Element: 某一列必须在表的每一行中都存在,则称为required。就是KV中每一行必须存在这一列。主键默认是required。
- Optional Element:与Required对应,如果某个非索引列不要求在每行都要出现,则称为optional。
这两个类型影响到在增加或删除某个元素时,是否要对数据做重组。比如增加一个required字段,那么必须在变更结束前,所有的行的KV信息中都必须增加这一列信息。相反,如果增加一个optional字段,变更后的表结构也不要求必须包含这一列,因此不用重组信息。
关系型操作
关系型操作包括insert、update、delete和query。
oper可以为任意的insert/update/delete/query。
Schema Change
数据库描述(database representation)是指一系列的KV行数据。每个F1 server在内存中保存了schema信息,使用这些schema信息将KV数据解释为关系型操作。
schema change: 当从一个schema信息转换为另一个版本的schema信息时,就称为schema change。因为不同的F1 server不能同步执行变更操作,所以在变更过程中,有些F1 server使用旧的schema,有些使用新的schema。但是他们使用的数据实例是一样的,就是访问同一份KV数据。
为了防止在变更时,数据被破坏,增加了一些中间状态(intermediate state)。通过这些中间状态和设计的执行步骤,可以保证所有操作都是安全的,不会破坏数据完整性。
约束:同一个时刻最多有两个版本的schema。一个F1 server要么使用旧的schema,要么使用新的schema。
Schema元素和状态
Schema Element:表、列、索引、约束(外键和索引唯一性),还有乐观锁(后续不做介绍)。
非中间状态:
- absent: schema中不存在的元素,就称为absent;
- public:模型中存在的,就称为public。public状态的元素可以应用各种操作。
中间状态:
- delete-only:对于表和列只接受删除操作。对于索引,可以接受删除和更新,但是不允许更新操作创建新的索引。delete-only状态不允许读取。
- write-only:列和索引可以插入删除和更新,不能查询。约束可以接受插入删除和更新,但是不能保证覆盖了所有已经存在的数据。
数据库对schema的一致性
一个数据库描述与schema要求一致:
- 不存在这样的列值:没有对应的行和表。
- 所有行都包含了所有public required列的值。public很关键,如果一个元素还不是public,可以不包含对应的值。
- 模型中不存在某个索引,那么也不存在这个索引的数据。
- 所有public索引都是完整的。每行都有对应的索引数据。
- 所有索引都指向有效的行。
- 满足所有约束。
- 没有未知数据。除了1和3中的数据,没有其它额外的数据。
数据库描述与schema不一致性的场景:
- 孤儿数据异常(orphan data anomaly): 数据库中存在与schema不符的数据。
- 完整性异常(integrity anomaly): 缺少应该包含的数据或者违反了public状态的约束。
一致性保护变更:
为了防止某些操作破坏了数据库与schema的一致性,约束了某些操作,保证只允许存在对变更前schema S1和变更后schema S2都符合一致性要求的操作。
做到一致性保护的变更,都不会破坏数据库。
添加或移除schema元素
structural schema element(简称structural element),结构化元素:表、列和索引。
能够避免出现orphan data anomaly(孤儿数据异常)和integrity anomaly(完整性异常)的一致性保护都是对S1和S2来说是一致性的。
Optional structural elements
对可选element的添加和删除可以通过增加一个中间状态delete-only做到一致性保护变更。
增加structural元素(表列索引):
- 增加一个delete-only元素。
假设从schema S1变更到schema S2,增加元素E。E在S2中不是public,S1和S2都是delete-only。这时所有的操作(其实只有删除和更新)都不会破坏S1和S2的一致性。 - 将delete-only元素变更为public。
这时数据库是符合S1的。因为元素E是可选的,所以不要求一定在S2中出现,所以也是符合S2的。
seq | S1 | S2 |
---|---|---|
1 | absent | delete-only |
2 | delete-only | public |
删除structural元素E:
与添加动作类似。
将S1变成delete-only,这时只能删除与E相关数据。使用S2的操作,删除操作会将元素E删除,此时E还是public状态。
seq | S1 | S2 |
---|---|---|
1 | delete-only | public |
2 | delete-only | absent |
删除元素在完成之前要经过数据重组,需要删除所有元素对应的数据。
对于增加optional元素,增加一个中间状态即可(删除元素顺序相反):
absent -> delete-only -> public
Required structural elements
Required元素与Optoinal的区别是增加或删除会破坏schema的完整性(optional的元素在schema中每行数据有没有都可以,但是required要求每行数据都包含对应的元素)。通过增加write-only状态,可以避免这个异常情况。
增加元素的状态变更(删除顺序相反):
absent -> delete-only -> write-only -> public
下面说明从delete-only状态到write-only状态是不会破坏数据库描述与schema的一致性的。
- S1的E是delete-only,S2的E是write-only。这样适用两个schema的操作都不会破坏一致性。
- 从write-only转换到public。在切换为public之前,需要进行数据重组,比如增加一个索引,要把所有行数据对应的索引创建出来。
增加元素的状态变化:
seq | S1 | S2 | 备注
–|--|–
1 | absent | delete-only
2 | delete-only | write-only | 数据重组
3 | delete-only | public
删除元素的状态变化:
seq | S1 | S2 | 备注
–|--|–
1 | write-only | public
2 | delete-only | delete-only | 数据重组
3 | delete-only | absent
约束
这里的约束包括外键和索引唯一性。
增加约束:
absent -> write-only -> public
增加一个约束,从write-only到public也需要做数据重组(或者完整性校验),但可能会失败。
删除约束的状态变化是相反的。
实现
Write fencing
每个写入操作都有一个截止时间(deadline),超过截止时间没有完成就终止。这个限制是为了实现schema的租约机制。
schema租约
最多有两个schema同时存在。每个F1 server有一个schema租约,一般是几分钟。每次从某个位置重新读取schema信息。如果schema信息重新获取失败,直接退出。
这样的话,每次schema变更,只需要等待一个schema租约时间,可以保证没有F1 server使用旧的schema。
如果一个操作在提交时,对应的schema已经过期了,那么事务会被终止。
其它
Spanner实现schema变更使用同步变更,依赖于同步时钟(synchronized clocks)和全局成员信息。
总结
F1拥有几百个无状态的F1 server,其内存中会缓存schema信息。不能做到同时让所有的F1 server同时应用某个schema,因此存在不同F1 server访问不同schema的状态。这篇论文就是介绍了如果在两个schema同时存在时,怎么操作而不破坏数据库的完整性。
问题
- Spanner怎么处理schema变更的?
- schema 的状态在Spanner中怎么管理的?
- schema 修改和状态变更在Spanner执行的是同步操作吗? 即是否所有Spanner同时执行,同时结束。