bluefs的操作大量增加时,日志也会增加,从而增加占用空间,由于文件系统的元数据在内存中都有记录,且内存中的元数据都是非重复的,因此可以通过遍历元数据,将元数据重新写到日志文件当中,即可实现日志的压缩,具体是_compact_log_async函数来实现的,其实现如下:
old_log_jump_to = log_file->fnode.get_allocated(); //获取已经分配的空间,这些空间最后要释放
_allocate(log_file->fnode.prefer_bdev, cct->_conf->bluefs_max_log_runway, &log_file->fnode); //重新在快速设备中开辟一段新的空间
log_t.op_file_update(log_file->fnode);
log_t.op_jump(log_seq, old_log_jump_to);
_flush_and_sync_log(l, 0, old_log_jump_to);
_compact_log_dump_metadata(&t); //压缩日志
new_log_jump_to = round_up_to(t.op_bl.length() + super.block_size * 2, cct->_conf->bluefs_alloc_size)
t.op_jump(log_seq, new_log_jump_to);
_allocate(BlueFS::BDEV_DB, new_log_jump_to, &new_log->fnode);
new_log_writer = _create_writer(new_log);
new_log_writer->append(bl); //在新的空间里写
_flush(new_log_writer, true);
_flush_bdev_safely(new_log_writer); //此时,原来的日志已经写在新空间里了
while (discarded < old_log_jump_to)
bluefs_extent_t& e = log_file->fnode.extents.front();
bluefs_extent_t temp = e;
if (discarded + e.length <= old_log_jump_to) //这个pextent可以完全释放
discarded += e.length;
log_file->fnode.pop_front_extent(); //将这个pextent出栈
else //该pextent只能释放前一部分
uint64_t drop = old_log_jump_to - discarded;
temp.length = drop; //将长度变小,就相当于截取pextent的前一部分了
e.offset += drop;
e.length -= drop; //e代表原来的pextent,这里更新原来的pextent的元数据
old_extents.push_back(temp);
//经过上述的while循环后,old_extents中保存了旧日志文件中old_log_jump_to位置之前的日志,旧日志文件中保存了old_log_jump_to位置之后的pextent
auto from = log_file->fnode.extents.begin();
auto to = log_file->fnode.extents.end();
while (from != to)
new_log->fnode.append_extent(*from); //将原先log文件的pextent元数据加入new_log
++from;
log_file->fnode.clear_extents();
new_log->fnode.swap_extents(log_file->fnode); //交换新老日志文件的pextent,
log_writer->pos = log_writer->file->fnode.size = log_writer->pos - old_log_jump_to + new_log_jump_to; //更新日志文件的写偏移,即日志的最后一项
super.log_fnode = log_file->fnode;
++super.version;
_write_super(); //将super写的super block中
encode(super, bl);
encode(crc, bl);
bdev[BDEV_DB]->write(get_super_offset(), bl, false);
for (auto& r : old_extents) //释放老日志文件中,old_log_jump_to位置之前的空间
pending_release[r.bdev].insert(r.offset, r.length);
(1)计算log_file目前已经分配磁盘空间大小,利用old_log_jump_to标记,这部分空间在压缩后可以释放。同时在块设备block.wal中申请新的空间用于以后的日志存储。
(2)调用_compact_log_dump_metadata函数来压缩日志,并设置下一个日志项的地址new_log_jump_to,注意new_log_jump_to是根据bluefs_alloc_size(1M)对齐的,因此很多时候新的日志文件中有一些空间是为了对齐没有使用的,向设备block.db申请new_log_jump_to大小的空间存放这些压缩后的日志。
(3)调用_flush和_flush_bdev_safely将压缩后的日志刷新到磁盘设备中。
(4)经过一系列的swap等操作后,log_file就只保存了压缩后的日志和op_jump(log_seq, new_log_jump_to)日志,然后把log_file的fnode写入到super中。
(5)最后将log_file中原来的处于old_extents的老日志所占用的空间释放。
关于磁盘压缩过程中磁盘空间的变化如下图所示
原来日志是存放在block.wal设备中,将压缩后的日志放在block.db设备中,并且最后一条日志为op_jump,所跳到位置就是new_log_jump_to,而在op_jump和new_log_jump_to之间是为了对齐而多出的空间,因此日志回放过程中遇到op_jump就会跳到new_log_jump_to,从new_log_jump_to开始读取block.wal设备中新增加的日志。因此压缩的日志是存放在block.db设备中的,未压缩的日志是存放在block.wal设备中的。
最后关于日志的压缩函数_compact_log_dump_metadata,其实现如下:
t->op_init();
for (unsigned bdev = 0; bdev < MAX_BDEV; ++bdev)
interval_set<uint64_t>& p = block_all[bdev];
for (interval_set<uint64_t>::iterator q = p.begin(); q != p.end(); ++q)
t->op_alloc_add(bdev, q.get_start(), q.get_len());
for (auto& p : file_map)
if (p.first == 1)
continue;
t->op_file_update(p.second->fnode);
for (auto& p : dir_map)
t->op_dir_create(p.first);
for (auto& q : p.second->file_map)
t->op_dir_link(p.first, q.first, q.second->fnode.ino);
可以看到日志压缩就是主要是记录bluefs的总空间、目录、文件、目录和文件的联系,在日志回放的时候就可以仅根据这些信息来还原出bluefs文件组织结构,这样可以把不需要的重复日志删除。