1. 前言
随着云计算浪潮的席卷,云化存储服务已经是云的标准能力,云存储也成为了云端应用的第一选择。相比本地存储,云存储拥有如极具弹性的扩展能力、按量付费的定价模式和高可用的服务能力等显著优势。
与本地存储类似,云存储服务也具有不同种类的:云端块存储、云端对象存储及云端文件存储等,以面向不同的存储需求。但是云存储带来的弹性的能力是本地盘没有的。
作为云原生数据库,PolarDB构建于具有超低延迟和高可用能力的分布式文件系统PolarFS,其底层存储采用的是云分布式块存储PolarStore。基于PolarFS并进行了大量内核优化,PolarDB在拥有云原生数据库优点的同时依旧能够发挥出极致的性能。然而,对于如大数据量、低频访问的冷数据场景,用户可能希望基于更低成本的云端对象存储,云原生数据库也应该拥有面向不同云存储兼容的能力。本文主要介绍PolarDB基于云对象存储OSS实现智能冷热分离存储引擎的技术方案。
2. 多样化云存储特性
本节简单介绍不同云存储服务的特性,这里主要讨论云上常用的两种标准存储服务:块存储服务(如Ali ESSD 、AWS EBS、Azure Block Storage)和对象存储(如Ali OSS、AWS S3、Azure Storage)。
值得注意的是通常所说的存储服务还包括了在存储基座上部署的相应的(分布式)文件系统、API接口等。块存储主要是旨在提供低延时、高可靠的块级数据随机访问能力,对象存储则是提供海量、低成本、高可靠的云存储服务,这两类存储格式各有各的功能和限制,因此在硬件和软件栈两方面都有所不同。
在底层硬件支持上,由于用户期望块存储提供低延时的块级数据随机访问能力,所以能够发现云服务厂商在块存储服务中提供更高性能的网络和存储,如RDMA网络和高性能NVMe SSD盘(这点类似传统块存储会采用SAN架构组网,提升传输速度,但是需要额外成本的光纤通道卡和交换机);而对于对象存储,云服务厂商更多的是采用标准网络及HDD盘(一般也会采用SSD作为中间Cache层)。
在软件接口方面,使用块存储时需要关联计算和存储(mount),且需要挂载文件系统提供(往往是支持POSIX语义)文件接口,这里就可以简单分为两类:本地文件系统和分布式文件系统,搭建本地文件系统的服务通常同一时间只能挂载到单个计算节点上(传统块存储一般有无法共享的问题),而搭建分布式文件系统的服务可以挂载到多个计算节点(多个节点可能是对等也可能不对等的);而对象存储直接面向最终用户,可直接当成云网盘来访问且具备原子性,天然支持多用户并发访问。总的来说,对象存储更偏向于共享与存储能力,块存储更偏向于读写性能(当然,云端块存储也通过分布式、多副本等来提升共享、存储能力)。由于前述的使用功能差异,两者在软件栈深度上也完全不同,对象存储具有更高的用户访问兼容性但同时也有更长的软件处理栈,而块存储服务则会尽量将软件栈做浅以实现更高的访问效率。
讨论了上述这么多特性差异,下面通过两者的成本及性能数据给出更直观的对比。
表. 块存储和对象存储对比(以ESSD和OSS为例)
块存储(ESSD PL2/3型) | 对象存储(OSS 标准型) | |
容量范围 | 1TB ~ 32TB | 文件总和无上限 |
IOPS | 100K / 1,000K | 顺序读写:2,000非顺序读写:10,000 |
带宽 | 750 MB/s(单盘PL2)4000MB/s(单盘PL3) | 单账号总限制:10 Gbit/s(实际受IOPS和IO大小限制) |
IO延时 | 约260 us(16KB write@50%BW)约540 us(64KB write@50%BW) | 约55 ms(64KB upload)约130 ms(1MB upload) |
成本 | 2元/GB/月(PL2) 4元/GB/月(PL3) |
0.12元/GB/月+ put-get访问费用 |
访问模式 | 块数据In-place修改 | 单对象Put /Get /Append /Delete,不支持部分修改 |
持久性 可用性 |
99.9999999%(9个9) 99.99% |
99.9999999999%(12个9) 99.995% |
可见,云对象存储和云块存储都具有高可用、弹性存储等能力,但对象存储服务更匹配低成本、低频率或延时不敏感、大IO的数据应用,块存储服务则更适合高性能、延时敏感、高IOPS的数据应用。
3. PolarDB on OSS技术方案
3.1 系统架构
对于某些数据库用户会存在如下类似的案例场景:随着时间或业务的变迁,数据库中部分数据的访问频次下降,成为低频访问的冷数据,比如归档历史相关数据, 但是仍存在(潜在的)在线访问需求。若仍保持原有的存储模式,则无可避免的维持了较高的存储成本;若将数据迁移至另外一套冷系统存储中,则需要额外的迁移代价且需要兼容并维护多套存储系统。为了降低这类数据的存储成本且兼容原有访问模式,PolarDB 在存储引擎InnoDB 上对接对象存储(OSS) 。对用户而言, 不会有任何感知, 使用的依然是存储引擎InnoDB。只是PolarDB 会将InnoDB 的数据文件存储到OSS 中,使用户能够更好的享受多样化的云原生服务。系统的整体架构如图1所示。我们在原有PolarDB-InnoDB存储引擎中新增内置的OSS 支持, 使PolarDB支持面向OSS的数据表操作。
另外一种思路是存储层去实现多层的Tier,也就是存储层面去感知数据的冷热,数据库的存储引擎无需感知。但是我们认为存储引擎本身对数据的了解程度远大于存储,在存储看来这些数据都是block 的信息,但是存储引擎有库,表相关信息等等。用户的归档等等操作倾向于将某一个库,或者某一个表转存到OSS,而存储层是不感知这一层信息,因此我们最后选择先在存储引擎层面去实现,可靠性更高。
图. 面向多样存储的冷热分离引擎
如下图所示,用户相关操作如下:
1. 用户通过原有操作创建基于PolarDB-InnoDB引擎的表,此时引擎路径与原有路径一致,用户进行基于块存储(PolarStore)的表操作;
2. 当用户认为表数据为冷数据对延时不敏感,或访问集中可被缓存且尾访问延迟不敏感时可以将块存储表转换为对象存储表,实际DDL语句为:ALTER TABLE table_name SECONDARY_ENGINE = S3;
3. 采用上述DDL操作,内置的OSS引擎将表数据并发拷贝到OSS对象存储上并释放块存储上的表空间(仅保留极少量表数据内容在块存储上,用于meta查询效率),其后用户可进行基于OSS的数据表DML操作,操作保持innodb存储引擎等兼容性;
4. 支持将对象存储数据表切换回块存储模式,实际DDL语句为:ALTER TABLE table_name SECONDARY_ENGINE = NULL。
图. 存储切换模式
3.2 技术实现
元数据/数据字典维护:由于表数据存在两种存储方式(PolarStore或OSS),因此需要增加额外的元数据描述符表征数据的存储位置。首先,OSS二级引擎完全内置于PolarDB-InnoDB引擎中,因此复用原有的数据字典并增加额外的持久化标志,保证重启时能正确的识别表的存储方式。其次,引擎在内存中构建了相应的元信息内存结构,其在重启时通过持久化的元数据构建,在执行时相应DDL语句进行修改。
OSS访问及存储模式:OSS对象存储的访问特性与块存储完全不同,它支持单对象的增删查及尾追加,但不支持部分修改。对于OSS来说,64KB以下对象占用64KB对象传输模式(包括访问费用),这与InnoDB原生页IO模式(通常为16KB)不适应。默认OSS对象大小为1MB(可配置),在进行DDL将数据切换到OSS上时,会先将数据表在BP中的残留页刷脏,然后对目标表分片进行多线程拷贝任务,每个工作线程进行大IO文件读取并写入OSS目标。对OSS上的表数据进行修改时,需要完整写入一个OSS对象大小,因此存在写放大问题,因此需要使用场景匹配(如非IO-bound、业务延时不敏感)。进行读取时OSS支持限定范围读,即比如读取一个1MB对象中的某16KB。
DDL原子性实现:PolarDB在实现OSS引擎能力时结合MySQL8.0的原子DDL特性,Server Layer以及Storage Engine使用同一份数据字典来存储元数据,通过DDL LOG结合Prepare、Perform、Commit、Post DDL四阶段处理实现存储种类切换DDL语句的原子性。
一写多读架构支持:PolarDB采用的是基于share storage的一写多读架构,通过物理复制方案实现RW节点和RO节点的同步。我们同样是通过物理复制实现在一写多读架构上支持存储切换能力。在进行相应DDL操作时,RW节点将相应的OSS元数据维护日志写入share storage,RO节点应用相应日志更新内存元信息。同时,在HA过程中结合DDL LOG及元数据实现HA操作正确性。
3.3 特性分析
因为兼容性问题,OSS引擎也是采用与innodb相同的BTree结构,由于page页写放大、OSS的IO远比块存储的IO慢等原因,引擎基于OSS时的性能注定比不上基于块存储时的。
由于,因此设计IO的运行路径会有极大的不同。对于读场景,对于超出buffer pool的访问数据集,性能会随着访问数据集的增大(cache miss增加)而下降。对于大批量暂态/小批量写入:
(以下为完全不开优化情况下的分析)
对于CPU-bound情况,暂态下主要影响写入性能的是WAL的落盘,如果不存在访问冲突OSS场景和PoalrStore场景一致,不存在cache miss,因此基于OSS和PolarStore时两者性能接近;对于存在访问冲突的,由于刷写page时持有page sx-lock且刷写时间较长,因此对于存在page访问冲突时(多线程小范围更新),写入性能下降;
对于IO-bound,存在cache miss,使用OSS读取数据较慢,因此性能只有使用poalrstore的1/100(不开逻辑预读或预读无效情况下)。
对于持续大批量写入:持续写入情况下,WAL不断快速写入(redo位于块存储上),checkpoint的推进相对缓慢(表文件位于对象存储上),redo不断堆积增加,两端差距不断增加,因为DB限制两者lsn的差距(避免redo文件的大量堆积、过长的recovery过程等问题),此时制约性能的变成了OSS的刷写。目前OSS表不适合于大量写入场景。
后续,为了提升性能会进行进一步性能提升:1)对于同一个OSS对象中不同页(可间隔)的IO进行merge减小写放大;2)对于AP场景提供可开关的预读选项;3)对于OSS的page IO提供shadow page能力缓解IO延时问题;4)调整IO pattern进一步利用OSS带宽能力,等等。
1. 前言
随着云计算浪潮的席卷,云化存储服务已经是云的标准能力,云存储也成为了云端应用的第一选择。相比本地存储,云存储拥有如极具弹性的扩展能力、按量付费的定价模式和高可用的服务能力等显著优势。
与本地存储类似,云存储服务也具有不同种类的:云端块存储、云端对象存储及云端文件存储等,以面向不同的存储需求。但是云存储带来的弹性的能力是本地盘没有的。
作为云原生数据库,PolarDB构建于具有超低延迟和高可用能力的分布式文件系统PolarFS,其底层存储采用的是云分布式块存储PolarStore。基于PolarFS并进行了大量内核优化,PolarDB在拥有云原生数据库优点的同时依旧能够发挥出极致的性能。然而,对于如大数据量、低频访问的冷数据场景,用户可能希望基于更低成本的云端对象存储,云原生数据库也应该拥有面向不同云存储兼容的能力。本文主要介绍PolarDB基于云对象存储OSS实现智能冷热分离存储引擎的技术方案。
2. 多样化云存储特性
本节简单介绍不同云存储服务的特性,这里主要讨论云上常用的两种标准存储服务:块存储服务(如Ali ESSD 、AWS EBS、Azure Block Storage)和对象存储(如Ali OSS、AWS S3、Azure Storage)。
值得注意的是通常所说的存储服务还包括了在存储基座上部署的相应的(分布式)文件系统、API接口等。块存储主要是旨在提供低延时、高可靠的块级数据随机访问能力,对象存储则是提供海量、低成本、高可靠的云存储服务,这两类存储格式各有各的功能和限制,因此在硬件和软件栈两方面都有所不同。
在底层硬件支持上,由于用户期望块存储提供低延时的块级数据随机访问能力,所以能够发现云服务厂商在块存储服务中提供更高性能的网络和存储,如RDMA网络和高性能NVMe SSD盘(这点类似传统块存储会采用SAN架构组网,提升传输速度,但是需要额外成本的光纤通道卡和交换机);而对于对象存储,云服务厂商更多的是采用标准网络及HDD盘(一般也会采用SSD作为中间Cache层)。
在软件接口方面,使用块存储时需要关联计算和存储(mount),且需要挂载文件系统提供(往往是支持POSIX语义)文件接口,这里就可以简单分为两类:本地文件系统和分布式文件系统,搭建本地文件系统的服务通常同一时间只能挂载到单个计算节点上(传统块存储一般有无法共享的问题),而搭建分布式文件系统的服务可以挂载到多个计算节点(多个节点可能是对等也可能不对等的);而对象存储直接面向最终用户,可直接当成云网盘来访问且具备原子性,天然支持多用户并发访问。总的来说,对象存储更偏向于共享与存储能力,块存储更偏向于读写性能(当然,云端块存储也通过分布式、多副本等来提升共享、存储能力)。由于前述的使用功能差异,两者在软件栈深度上也完全不同,对象存储具有更高的用户访问兼容性但同时也有更长的软件处理栈,而块存储服务则会尽量将软件栈做浅以实现更高的访问效率。
讨论了上述这么多特性差异,下面通过两者的成本及性能数据给出更直观的对比。
表. 块存储和对象存储对比(以ESSD和OSS为例)
块存储(ESSD PL2/3型) | 对象存储(OSS 标准型) | |
容量范围 | 1TB ~ 32TB | 文件总和无上限 |
IOPS | 100K / 1,000K | 顺序读写:2,000非顺序读写:10,000 |
带宽 | 750 MB/s(单盘PL2)4000MB/s(单盘PL3) | 单账号总限制:10 Gbit/s(实际受IOPS和IO大小限制) |
IO延时 | 约260 us(16KB write@50%BW)约540 us(64KB write@50%BW) | 约55 ms(64KB upload)约130 ms(1MB upload) |
成本 | 2元/GB/月(PL2) 4元/GB/月(PL3) |
0.12元/GB/月+ put-get访问费用 |
访问模式 | 块数据In-place修改 | 单对象Put /Get /Append /Delete,不支持部分修改 |
持久性 可用性 |
99.9999999%(9个9) 99.99% |
99.9999999999%(12个9) 99.995% |
可见,云对象存储和云块存储都具有高可用、弹性存储等能力,但对象存储服务更匹配低成本、低频率或延时不敏感、大IO的数据应用,块存储服务则更适合高性能、延时敏感、高IOPS的数据应用。
3. PolarDB on OSS技术方案
3.1 系统架构
对于某些数据库用户会存在如下类似的案例场景:随着时间或业务的变迁,数据库中部分数据的访问频次下降,成为低频访问的冷数据,比如归档历史相关数据, 但是仍存在(潜在的)在线访问需求。若仍保持原有的存储模式,则无可避免的维持了较高的存储成本;若将数据迁移至另外一套冷系统存储中,则需要额外的迁移代价且需要兼容并维护多套存储系统。为了降低这类数据的存储成本且兼容原有访问模式,PolarDB 在存储引擎InnoDB 上对接对象存储(OSS) 。对用户而言, 不会有任何感知, 使用的依然是存储引擎InnoDB。只是PolarDB 会将InnoDB 的数据文件存储到OSS 中,使用户能够更好的享受多样化的云原生服务。系统的整体架构如图1所示。我们在原有PolarDB-InnoDB存储引擎中新增内置的OSS 支持, 使PolarDB支持面向OSS的数据表操作。
另外一种思路是存储层去实现多层的Tier,也就是存储层面去感知数据的冷热,数据库的存储引擎无需感知。但是我们认为存储引擎本身对数据的了解程度远大于存储,在存储看来这些数据都是block 的信息,但是存储引擎有库,表相关信息等等。用户的归档等等操作倾向于将某一个库,或者某一个表转存到OSS,而存储层是不感知这一层信息,因此我们最后选择先在存储引擎层面去实现,可靠性更高。
图. 面向多样存储的冷热分离引擎
如下图所示,用户相关操作如下:
1. 用户通过原有操作创建基于PolarDB-InnoDB引擎的表,此时引擎路径与原有路径一致,用户进行基于块存储(PolarStore)的表操作;
2. 当用户认为表数据为冷数据对延时不敏感,或访问集中可被缓存且尾访问延迟不敏感时可以将块存储表转换为对象存储表,实际DDL语句为:ALTER TABLE table_name SECONDARY_ENGINE = S3;
3. 采用上述DDL操作,内置的OSS引擎将表数据并发拷贝到OSS对象存储上并释放块存储上的表空间(仅保留极少量表数据内容在块存储上,用于meta查询效率),其后用户可进行基于OSS的数据表DML操作,操作保持innodb存储引擎等兼容性;
4. 支持将对象存储数据表切换回块存储模式,实际DDL语句为:ALTER TABLE table_name SECONDARY_ENGINE = NULL。
图. 存储切换模式
3.2 技术实现
元数据/数据字典维护:由于表数据存在两种存储方式(PolarStore或OSS),因此需要增加额外的元数据描述符表征数据的存储位置。首先,OSS二级引擎完全内置于PolarDB-InnoDB引擎中,因此复用原有的数据字典并增加额外的持久化标志,保证重启时能正确的识别表的存储方式。其次,引擎在内存中构建了相应的元信息内存结构,其在重启时通过持久化的元数据构建,在执行时相应DDL语句进行修改。
OSS访问及存储模式:OSS对象存储的访问特性与块存储完全不同,它支持单对象的增删查及尾追加,但不支持部分修改。对于OSS来说,64KB以下对象占用64KB对象传输模式(包括访问费用),这与InnoDB原生页IO模式(通常为16KB)不适应。默认OSS对象大小为1MB(可配置),在进行DDL将数据切换到OSS上时,会先将数据表在BP中的残留页刷脏,然后对目标表分片进行多线程拷贝任务,每个工作线程进行大IO文件读取并写入OSS目标。对OSS上的表数据进行修改时,需要完整写入一个OSS对象大小,因此存在写放大问题,因此需要使用场景匹配(如非IO-bound、业务延时不敏感)。进行读取时OSS支持限定范围读,即比如读取一个1MB对象中的某16KB。
DDL原子性实现:PolarDB在实现OSS引擎能力时结合MySQL8.0的原子DDL特性,Server Layer以及Storage Engine使用同一份数据字典来存储元数据,通过DDL LOG结合Prepare、Perform、Commit、Post DDL四阶段处理实现存储种类切换DDL语句的原子性。
一写多读架构支持:PolarDB采用的是基于share storage的一写多读架构,通过物理复制方案实现RW节点和RO节点的同步。我们同样是通过物理复制实现在一写多读架构上支持存储切换能力。在进行相应DDL操作时,RW节点将相应的OSS元数据维护日志写入share storage,RO节点应用相应日志更新内存元信息。同时,在HA过程中结合DDL LOG及元数据实现HA操作正确性。
3.3 特性分析
因为兼容性问题,OSS引擎也是采用与innodb相同的BTree结构,由于page页写放大、OSS的IO远比块存储的IO慢等原因,引擎基于OSS时的性能注定比不上基于块存储时的。
由于,因此设计IO的运行路径会有极大的不同。对于读场景,对于超出buffer pool的访问数据集,性能会随着访问数据集的增大(cache miss增加)而下降。对于大批量暂态/小批量写入:
(以下为完全不开优化情况下的分析)
对于CPU-bound情况,暂态下主要影响写入性能的是WAL的落盘,如果不存在访问冲突OSS场景和PoalrStore场景一致,不存在cache miss,因此基于OSS和PolarStore时两者性能接近;对于存在访问冲突的,由于刷写page时持有page sx-lock且刷写时间较长,因此对于存在page访问冲突时(多线程小范围更新),写入性能下降;
对于IO-bound,存在cache miss,使用OSS读取数据较慢,因此性能只有使用poalrstore的1/100(不开逻辑预读或预读无效情况下)。
对于持续大批量写入:持续写入情况下,WAL不断快速写入(redo位于块存储上),checkpoint的推进相对缓慢(表文件位于对象存储上),redo不断堆积增加,两端差距不断增加,因为DB限制两者lsn的差距(避免redo文件的大量堆积、过长的recovery过程等问题),此时制约性能的变成了OSS的刷写。目前OSS表不适合于大量写入场景。
后续,为了提升性能会进行进一步性能提升:1)对于同一个OSS对象中不同页(可间隔)的IO进行merge减小写放大;2)对于AP场景提供可开关的预读选项;3)对于OSS的page IO提供shadow page能力缓解IO延时问题;4)调整IO pattern进一步利用OSS带宽能力,等等。