很多的用户在提到 Ceph 性能的时候都会提到“写放大”这点,实际上就是 FileJournal 在起作用。只要使用默认的 FileStore,所有数据包括 metadata 都会在 FileJournal 上预写一份。那么本文就会介绍 FileJournal 在 FileStore 存储引擎上提供的作用。
作用
FileJournal 就是数据库中常见的 WAL(Write Ahead Log) 实现,主要提供了事务的一致性和原子性。Ceph 数据访问所提供的写操作在落到 ObjectStore 时实际上会产生多个写操作,为了保证用户层面的写操作的原子性,避免 FileStore 在执行多个操作时发生意外造成中间状态而无法追溯或者回滚,我们需要引入 Journal 来作为日志。使得 OSD 进程在非正常退出后再启动可以从 Journal 中恢复之前正在执行的操作。
除此之外,FileJournal 提供了更短的写操作耗时,因为用户 IO 操作到达 FileStore 以后,只要经过 FileJournal 存储后都可以立即回复给用户,无需等待操作正常落盘。对于大量随机小写来说,这实际上能大大提高单个 OSD 的处理能力。
工作流程
如上图所示,所有 PG 层提交事务都会在 FileStore 经过一层 Throttle 直接进入 FileJournal 队列。然后有后面的不同类型线程都是单一线程,以 Pipeline 的形式最大化 FileJournal 的吞吐量。IO 从 Journal Queue 被提取后由 Write Thread 进行处理,图上只标出了 AIO,实际上如果 OS 不支持 AIO+DIO 的方式,那么就会采用 write+flush 的组合。在这里,Write Thread 会尽可能获取多的队列事务进行批量提交,每个事务都会以页对齐的方式补零后者重新编排,最后提交 IO。Write Finish Thread 实际上只在 AIO+DIO 中存在,主要是为了收割正在进行的 IO,收购后提交给 Finisher Thread。
可能的改进
日志的实现实际上并不是一个简单的话题,它的逻辑非常简单但在 IO 程序中起到非常重要的作用,也是用户写操作最重要的延时消耗者。因此如何最大化日志来提高 IO 吞吐量和延迟的讨论早存在于学术和工业届。Pipeline、减小临界区和批量提交是主要的手段,FileJournal 采用多个线程协同的方式而不是多个独立工作线程单独工作的形式,两者各有优劣。目前 FileJournal 在设计上没有太大的问题,在实现上需要更加 SMART,解决不同大小 IO size 的相互影响。除此之外,非常大或者对象存储的工作场景下,跳过 FileJournal 直接落盘是用户期待的方式,但是目前 FileJournal 与 FileStore 耦合严重,因此社区会重起一个新的 Backend 来解决这些问题。