EROFS: A Compression-friendly Readonly File System for Resource-scarce Devices
#作者
Gao Xiang @ 华为,MIngkai Dong @ IPADS @ SJTU
USENIX ATC 2019
#背景
安卓系统本身对设备存储的占用越来越大。
这些系统数据大部分都是Read-only的。Read-only为压缩存储提供了巨大的优化空间。
#Ext4
Ext4允许只读挂载文件系统,但是可以通过重新挂载绕过这种限制,不够安全。
#Btrfs
支持压缩,文件会分成128KB的块
是general-purposed的文件系统,不能只为了压缩只读做很多优化
#SquashFs
Squashfs 广泛使用,支持多种压缩算法,可选的块大小4KB~1MB。
元数据可以被压缩,inode和目录紧密存储,文件内容逐个块压缩,在inode中用一个list保存每个块压缩后的大小。
#SquashFs 的问题
在低端设备上使用SquashFs会导致数秒的巨大延迟(使用FIO测试,文件enwik9)。
#IO放大和计算浪费:
SquashFs 按输入固定大小分块压缩,压缩后的数据是变长的。然后变长的数据被compact存储。
Squashfs固定要对原始数据先按fixed-size分块,分别压缩,再拼接到一起,即使只改了一个字节也要重复整个过程;
读数据需要先把整个block读进来,整个解压缩后才能定位到需要读的数据;
工程实现问题:压缩过的块和磁盘不强制对齐,最坏情况需要多读和解压两个块;
可以把块大小减小到4KB,然而会降低压缩率
#内存放大和数据拷贝
SquashFs 在解压数据的时候需要大量的临时内存。
读写数据的时候需要多次分配内存用于page cache/保存解压缩后的数据等;
在小内存机器上会额外加剧swapping开销;
#EROFS:增强的压缩文件系统
#Fixed-sized output compression
为了缓解读放大的问题,EROFS对压缩后的数据(而非压缩前)分块,这样的好处有:
- 压缩比更大,因为一次压缩输入的数据可能更多;
- 解压的时候只需要取包含了需要数据的块,没有额外的块读取和解压开销;
- 允许进行就地解压,内存开销能够稍微小一些;
EROFS采用了滑动窗口来压缩数据,在 输入大小到达 1MB
或者 输出大小到达 4k
时输出一个新的block。
#缓存层优化
对于压缩型文件系统,为了保存解压后的数据,需要额外分配内存,这部分对内存的需求很大。
EROFS对于是否要读完整的块,采用了两种不同策略,以优化读性能:
- Cached I/O:正常使用内核的Page Cache;
- In-place I/O:绕过Page Cache机制;
对于只需要读一部分数据(部分解压)的块,使用 Cached I/O。EROFS使用了一个特别的inode
的page cache保存 物理块号->压缩块
之间的映射关系,在读取时可以直接定位到需要的块。
对于需要读全部数据(完全解压)的块,使用 In-place I/O。每次读文件会主动从Page Cache层分配页去保存文件数据,然后就地解压数据;空闲下来的页会被复用,以减小频繁内存分配。
这里的想法是一个已经解压过的块不太可能被再次使用(因为已经有了解压后的原始数据),占用的page cache就可以回收利用;如果仍然cache的话就没有必要,而且会增加内存抖动。
#解压缩优化
EROFS对解压做了很多细致的优化。
#vmap解压
在EROFS中,每次解压一个块可能会得到多块数据,如果只需要读其中一部分,那么解压出来的其他数据块就没必要保存。为此,EROFS利用了虚拟内存映射机制,将不需要的块映射到临时页。
实现上,每个压缩的硬盘块中会保存原始数据的元数据(如长度),在解压时会先准备好足够的 Buffer,然后使用 vmap
手动映射成连续的虚拟地址空间, 以调用解压缩算法。
在解压时,对于需要的部分,会vmap
映射一段page cache空间,对于不需要的部分,只会映射到临时页。
#Per-CPU解压
上述的方式还有两个问题:
- 还是需要动态分配内存页,不好;
- 每次解压都要调用
vmap
和vunmap
,不好;
为此,EROFS静态分配了若干个页专门用于解压,具体来说,每个CPU会持有个页。当解压出来的数据不超过页的时候,就用这些页装解压后的数据,然后再memcpy到目标page cache位置。
论文中采用的为4。
#滚动解压
由于LZ4算法需要当前位置之前最多64KB的数据才能解压。EROFS会给每个CPU预先分配若干个物理页,作为vmap解压时存储数据的基本buffer。这些物理页会循环使用。
#实现
EROFS已经合并进内核。使用内核的4KB块大小,LZ4 (v1.8.3) 压缩算法,文件元数据没有被压缩。
atc19-erofs 是合并进5.3-rc1的分支。
atc19-mkfs是论文版本的utils,最新版本在erofs-utils。
kmod-erofs (WIP)是out-of-tree版本的erofs模块,还没完成。
#EROFS镜像布局
和其他文件系统一样,EROFS镜像的开头也是Super block
;然后是混合存储的元数据和数据区。
EROFS 允许元数据(inode,目录)和数据混合存储,即inline data,目的是提高局部性,减少I/O操作。
TODO
#解压策略
商业版:少于4页时采用per-CPU解压,否则采用vmap解压;
最新版:增加了in-place解压的优化。
#优化
#索引内存优化
TODO
#调度优化
EROFS没有单独的解压线程,而是由读数据的线程解压数据。
一旦硬盘块(硬件)准备好就会立即开始解压。
#协同解压
对同一个数据的多个请求会去重,避免重复解压相同的数据。
#镜像补丁
在镜像末尾放置更新数据,解压之后用新数据覆盖。