数据库操作系统

今天在推上看到一篇论文推荐,觉得内容很有意思,于是马上下来看。

    

首先文章的作者来自于顶级高校和企业,另外数据库大牛图灵奖获得者Stonebraker(这个名字也太像石破天了)也名列其中。大牛当年怒喷MapReduce引发的论战还记忆犹新。如果说MapReduce是操作系统界入侵数据库界,那这篇文章则可以看成数据库界抄操作系统的后路。

文章链接:https://vldb.org/pvldb/vol15/p21-skiadopoulos.pdf

万物皆表

UNIX时代发明的“Everything is a file”的抽象哲学(并得到Linux的发扬光大)仍被奉为圭臬,但本文则直接挑战这一思想,认为它已经落后于时代。本文一开始列举了诸多计算的趋势——大规模、云、并行计算、异构硬件、新编程模型(例如Serverless)和数据监管等等。针对上面这些问题,本文认为虽然有不少修修补补的方案,但是时候把思路转向更高的层次——本质上是大数据问题,应该用大数据的思路来寻找答案。作者们认为“Everything is a table”就是正确的方向。更具体的说,则是”Everything is a distributed table”,从底层支持分布式和数据库。

顺着万物皆表的思路,很多复杂的功能可以很容易通过数据库实现。比如调度器,可以用两张表分别表示任务队列和工作节点。剩下的工作就是通过存储过程原子地操作这两张表(数据库事务保证了一致性),实现各种调度方式,例如负载均衡、本地优先等等。

第二个例子是IPC。通过消息信箱实现分布式IPC早就不是什么新鲜事,比如Actor模式的编程就是通过给信箱的收发进行通信并解耦消息的收发双方。而数据库可以轻松地实现分布式消息队列。

第三个例子是文件系统。目录项和Inode都分别建表,文件还能分为集中存放(一个shard,并发度高)和全局散布(所有shard以求吞吐量最大化)两种模式。类似对象存储,目录树成为逻辑概念而非物理概念(存放的都是全路径)。当中还举了一个统计目录下文件大小的例子,数据库可以通过SQL的聚合轻松完成,而现代文件系统因为没有这个标准操作所以只能通过一个98行代码的程序完成并且性能差一大截。虽然这个比较并不公平,但数据库强大的功能也可见一斑。我不由得想起了当年Longhorn项目里的WinFS(虽然概念基本不相关),怪不得微软也尝试了多次想把数据库引入用户文件系统。

实现路径

这个研究项目也有实打实的路线图,并且看上去还比较靠谱。毕竟现代操作系统如此复杂,几十年的积累,在底层引入这么大的变化,工程量绝非一般人可以想象。而分布式数据库又是一个大坑。分层替换自然是可行的选择。

首先新的操作系统(称为DBOS)如下图划分为4个层次。

  • 第4层即用户层类似现代操作系统的用户态,在这个层次DBOS仅支持那些分布式应用——这也是本文划定的范围,DBOS并未打算取代现有操作系统的所有领域,比如桌面、移动,而是聚焦在分布式服务器范畴。当然现有的程序肯定也不能直接跑,而是要经过一定的改写,把程序变成由小型无状态任务组成的DAG(有向无环图)。类似Spark这种程序比较符合这种范式。
  • 第3层类似系统调用层。所有的这些服务需要通过分布式数据库实现,而上一节讲了通过数据库怎么实现文件系统、调度器、IPC这些重要的接口或者服务。
  • 第2层即DBOS的核心——分布式数据库。
  • 第1层设计为微服务形式,处理底层硬件的中断事件、节点间通信等等。注意这一层计划放弃某些复杂的管理机制,比如虚拟内存管理(其实现在有一些跑Spark或者k8s应用的服务器已经主动关闭了swap)

类似房子的演化,整个路线图分为3阶段:

  • 草:在第2层使用现有的分布式数据库(VoltDB)基础上,对第3层实现进行概念验证,并编写的第4层应用示例进行性能测试。这个阶段工作已经接近完成并在下一部分给出了一些结果。
  • 木:在Linux集群上(第1层)运行真正的由执行图组成的用户程序,并将部分第3层系统功能替换成系统功能。这一阶段的目的是保证操作系统可以稳定运行在SQL之上。这也是这个项目的目前阶段。
  • 砖:这一阶段尚不是很明朗,亟待木阶段的结果和可以获得的资源来确定具体方案。总体上来说,需要替换掉第1层的实现,并且第3层基本由分布式数据库服务。第2层的分布式数据库如果重写工作量太大。

(不知道以后会不会有钢筋混凝土)

性能为王

学过操作系统课的都知道当年的微内核和宏内核之争。即便微内核的设计多么优雅,计划多么雄心勃勃(正如这个DBOS),性能达不到需求,便只能停留在研究上无法走入工业界。这也是DBOS面对的最大质疑。DBOS-草阶段也主要是为了尽量平息这个争议。

调度器

调度器的测试展示了在分布式环境下单纯任务调度的延迟可以在1M吞吐量下达到200us以内。对于CPU级别的任务调度当然这算不上什么,但是如果考虑调度的目标是平均延迟在100ms的serverless function,这个成绩也足够了。

IPC

IPC的测试对比了单纯的TCP/IP和gRPC。后者是目前应用最广泛的RPC协议。相比gRPC,DBOS的ping-pong测试(网络版的echo)性能较差,但还在同一数量级上。

而在小消息大批次的ping-pong测试(一次性发送20组消息)以及多播测试(一个发送者多个接受者)中,DBOS略优于gRPC。

由于目前VoltDB尚不支持触发器,因此在收件端只能采用性能较低的忙等待轮询。

文件系统

文件系统方面做了两组测试,分别是在本地对比ext4,和集群环境里对比LustreFS。不出意外,单个文件的读取ext4绝对占优,多个文件的读取由于ext4需要多个系统调用因此差距缩小了。写入则是DBOS的长处,因为没有全局锁。文件创建/删除方面,DBOS的优势在于使用了全路径。

和集群文件系统的对比DBOS则在读取上获得得了明显的优势。

小结

DBOS构建的是原生支持分布式和并行计算的操作系统,运行在计算中心里,并无意取代大家所熟悉的Linux/Windows/Mac。可以把DBOS想象成为运行serverless应用、Spark任务的计算平台。在这个平台上编写分布式应用程序将大大简化,并且所有操作都可以溯源(想象一下所有读写都有操作记录并可以回放)。即便如此,这个项目构想的前景依旧非常宏大,即使取得一小部分成功也会对业界产生重大影响。也让我们拭目以待期待项目后续发展。

(最新的进度报告也发表在CIDR2022上:http://cidrdb.org/cidr2022/papers/p26-li.pdf

另外知乎上还有一篇论文解读

Fixing “error checking mount status” when using mkfs.btrfs to a new file

(Update: Fix is also available at http://permalink.gmane.org/gmane.comp.file-systems.btrfs/15906)

Symptom

When you're following instructions to debug btrfs with gdb and UML (User Mode Linux), you might come across this error when making btrfs on a file:

error checking <file name> mount status

And here're the steps before:
1. Download latest btrfs-prog(git clone git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-progs.git)
2. Build the tools
3. Run mkfs.btrfs <image file>

Root cause

If you're sure that you just created the file with dd and not touching it, then it is quite likely you hit an issue in the mkfs.btrfs code which works well on later linux kernel.

mkfs.btrfs will check if the target device/file is mounted anywhere or it is formatted in btrfs before it do any write. One last thing is to check that the file is not backing a loop device. In utils.c, it resolves the loop device by parsing /sys/block/<loop name>/loop/backing_file, which doens't exist until 2.6.37 (I'm running on 2.6.29). Failure in opening the file results in failure of the whole operation.

Fix

Unmount any loop device

I'm not going to file any bug request or provide any fix myself, due to the fact that this issue won't happen in the real environment (should be running a later linux kernel). So just put it down with the workaround for your reference.

MBR,GPT与Boot Camp

MBR

作为BIOS时代的分区管理格式,MBR (Master Boot Record)已经走过了31个年头。1982年,MBR被引入IBM PC DOS 2.0,用来支持10MB大小的硬盘。

MBR的结构非常简单。前面446字节是引导代码,接着4条16字节的主分区信息,最后是两个字节的签名(魔数):

Start Size Description
0 446 Boot code
446 16 Primary partition entry 1
462 16 Primary partition entry 2
478 16 Primary partition entry 3
494 16 Primary partition entry 3
510 2 Signature (0xaa55)

这么看每个磁盘的分区数被限死在了4个,不过后来又引入了扩展分区技术(EBR)解决(或者说是绕过)了这个问题。简单来说,EBR就是把最后一个主分区特别对待,这个分区(又称扩展分区)的第一个扇区看成是一个MBR,又叫EBR,不过不同的是EBR只用了前两个分区项(同样从446字节处开始),第一项指明了新的普通分区,而第二项除了可以指新的普通分区外,还可能用来指向新的扩展分区。这样通过链表的形式把非主分区(又称逻辑分区)给串起来。下面这两张张图(来自MSDN)基本说明了是怎么回事:

 

上图前三个主分区项都指向了普通分区,最后一项指向了扩展分区。下图则演示了扩展分区的细节:

 

EBR

扩展分区里只用了两项分区项,第一项指向普通分区,第二项指向下一个扩展分区。前一级扩展分区总是涵盖了后面的所有分区,形成了嵌套的链接关系。

这样的做法缺点很明显,链表当中要是断了,后面的可就都丢掉了。另外要让一些主流操作系统从分主分区启动也要花一些功夫。

下面简单说说分区项的具体内容:

Start Size Descripton
0 1 Status (0x80 = boot flag)
1 3 CHS start (deprecated, use 0xfeffff)
4 1 Partition type
5 3 CHS end (deprecated, 0xfeffff)
8 4 LBA start (in sectors)
12 4 LBA size (in sectors)

可以看到一个明显的限制是磁盘的大小。LBA的宽度只有4个字节,这样只能用来描述最大2TB的磁盘,再大就不够了(有别的hack,不过至少肯定不能用来当启动盘)。另外分区类型最多256种,不够描述当今所有的文件系统类型(包括操作系统)。MBR格式(以及其背后的BIOS)过于古老,严重阻碍了新技术的推广。但是PC市场这么大,想要改也没那么容易,涉及到硬件软件以及大量第三方公司和整个产业链。

EFI

Intel和HP在90年代一起研发Itanium的时候,就打算趁引入新架构的机会也把老掉牙的BIOS踢到一边去。98年搞了一个项目叫Intel Boot Initiative,后来更名为EFI,2005年又更名为现在的名字UEFI。Itanium算是没做起来,UEFI这些年的势头开始起来了,除了Intel架构的Mac以外,Windows 8的加入无疑将大大加速UEFI的推广(虽然Secure Boot备受争议)。

EFI的一个组成部分是规定了新的磁盘分区格式GPT(GUID Partition Table)。首先GPT支持最高达8ZB(4PB*2M)的磁盘容量(在可见的未来足够了),以及最多128项磁盘分区。另外出于保护数据的目的,分区表在磁盘上存放了两份,分别放在磁盘的开头和结尾,以防以外丢失。

保护MBR(Protective MBR)

由于市场上大量非EFI机器的存在,GPT通过保护MBR来达成与MBR的兼容,或者说是保护GPT分区不被非EFI机器破坏。方法很简单,结合上面MBR的描述就可以明白:

  1. 占领MBR的第一项分区项;
  2. 分区类别设定为特殊类型;
  3. 起始地址为扇区1;
  4. 分区大小为磁盘大小-1。

这样非EFI机器读MBR的时候,就会发现整个磁盘已经被别的分区全部占满了,而用户也不会轻易的重新分区。这就是所谓的“保护性”。

GPT头和分区项

GPT头描述了磁盘和分区表的基本信息,如磁盘的GUID和分区表的大小。分区项最多有128项,一般4项为一个单位。分区项每项占128字节:

Start Size Descrption
0 16 Partition type GUID
16 16 Unique Partion GUID
32 8 Start LBA
40 8 Last LBA
48 8 Flags
56 72 Partition name

(令人意外的是,分区类型虽然是GUID,不过已经有冲突了)

GPT和MBR这两种磁盘分区格式可以说是天差地别,融合起来可不是很容易。不过好在GPT给MBR让出了扇区0作为保护MBR,在一个磁盘上支持两种分区格式也成为可能。典型的做法是Boot Camp的实现。

Boot Camp

苹果在2005年宣布使用Intel作为Mac平台以后,由于控制了硬件软件平台,使得它应用EFI没有什么问题。不过大部分Windows并不支持GPT而只能从MBR磁盘启动,苹果也提供了Boot Camp方便使用者在Mac上安装双系统。

Boot Camp的原理是混合MBR (Hybrid MBR)。利用MBR里的空余主分区项,使得一个分区在两个分区表里都有份。

前面说过了保护性MBR的做法是把整个磁盘写成一个大分区,这时候这个大分区被Mac OSX占据了。如果我们把Mac OSX分区缩小(比如一半),那后面一半分区就可以腾出来给Windows用了。要做到这一步很简单,只需要把保护性MBR里那条特殊的分区项的分区大小改小就可以了,另外需要往MBR里再加一条分区项(并设置成启动),描述后面半个磁盘上的Windows分区。

操作系统按道理可以安装启动了,不过下一步我们还需要在两个操作系统里分别都能看到对方操作系统的分区,至少方便拷贝数据(假定都有相应文件系统的驱动)。对于Windows来说,由于MBR里已经有两条分区项充分描述了这两个分区,所以Windows里能够看到两个分区;但是对于Mac OSX来说,它只认识GPT的分区,所以还看不到Windows的分区。当然这也很简单,只要往GPT分区表里加一条描述Windows分区的分区项就可以了。

具体来说Boot Camp做了三件事:

  • 把Mac OSX分区缩小(Shrink HFS+)
  • 改动GPT和MBR的分区表;
  • 在启动菜单里加上其他操作系统的选项,另外给其他操作系统提供驱动。

下面是我启用了Boot Camp的MBR导出结果:

00fe ffff eefe ffff 0100 0000 2740 0600   <<<< EFI partition (200M ESP)
00fe ffff affe ffff 2840 0600 0047 0740   <<<< Mac main partition, size 0x40074700 (512G)
00fe ffff abfe ffff 2887 0d40 205f 1300   <<<< Mac recovery partition
80fe ffff 07fe ffff 00e8 2040 0078 3317   <<<< Windows partition, size 0x1733008 (185G), boot bit             
                                               (first bit) is set

或者是用fdisk导出结果

wum@dev$ sudo fdisk -d /dev/rdisk0

1,409639,0xEE,-,1023,254,63,1023,254,63
409640,1074218752,0xAF,-,1023,254,63,1023,254,63
1074628392,1269536,0xAB,-,1023,254,63,1023,254,63
1075898368,389249024,0×07,*,1023,254,63,1023,254,63

参考文献

Hybrid MBRs

What's a GPT?

Apple support of GPT