近期接到ceph用户报案,说是多进程direct写ceph-fuse的单个文件,性能很低,几乎与单进程direct写文件的性能同样。关乎民生,刻不容缓,笔者当即展开侦查工做~node
笔者经过fio工具展开测试,分别测试了单进程和多进程direct随机写ceph-fuse的单个文件的性能状况。linux
fio -filename=/mnt/fuse/file_1T -direct=1 -rw=randwrite -ioengine=sync -bs=4K -size=1G -numjobs=1 -runtime=180 -group_reporting -name=test fio -filename=/mnt/fuse/file_1T -direct=1 -rw=randwrite -ioengine=sync -bs=4K -size=1G -numjobs=16 -runtime=180 -group_reporting -name=test
结果显示,不管单进程,仍是多进程,并且不管多少进程,iops结果差很少都是50左右(磁盘未开write-cache)。那么,多进程写多个文件呢,性能又是怎样的?bash
这里注意了,将fio的参数filename改为directory后,对于多进程,fio会为每一个进程建立一个测试文件。函数
fio -directory=/mnt/fuse/ -direct=1 -rw=randwrite -ioengine=sync -bs=4K -size=1G -numjobs=16 -runtime=180 -group_reporting -name=test
结果显示,iops大概是800左右,正好是单进程50的16(进程数)倍,怎么这么巧?工具
写同一个文件,单进程和多进程具备相同的iops性能,说明多进程在io路径的某个环节变成了串行,从而致使多进程实际上也变成了单进程。性能
咱们先来看下ceph-fuse的io链条:测试
用户态程序fio -> vfs -> kernel fuse -> libfuse -> ceph-fuse -> [mds] -> ceph clusterui
笔者以前看过链条上一些环节的代码,但因为代码量太大,代码的细节太多,因此认知并不深刻,没法直接定位到凶手。spa
为了帮助分析案情,这里简单描述下这条io链:
用户态程序fio经过多进程进行direct随机写,经过vfs后进入kernel fuse,kernel fuse会将io请求放入到pending队列,而后等待结果。用户态libfuse的多个进程经过/dev/fuse设备读取pending队列中请求,而后调用更底层的ceph-fuse进行处理。ceph-fuse最终将请求发送给mds或者ceph集群进行处理。code
必然是上述io链条中某个环节致使了案情的发生。然而咱们很难熟知于io链条中的每一环,因此咱们须要根据更多已知的线索来缩小io链条的排查范围。
目前已知的线索有:
① fio多进程和单进程写同一文件性能几乎一致
② fio多进程写进程数个文件的性能 = 单进程性能 * 进程数
因为没有更多的线索,笔者开始对io链条进行排查。先从熟悉的ceph-fuse、mds和ceph cluster开始。
嫌疑人 | 分析 |
---|---|
ceph cluster | 对比多进程写单文件和多进程写多文件的性能,说明出现案情时,ceph cluster不是瓶颈 |
mds | 因为fio测试是单客户端的,因此多进程的写也不会竞争fw的caps,另外fio的测试,涉及mds的请求不多,因此mds做案的可能性很小 |
ceph-fuse | 这是笔者怀疑的重点,众所周知,ceph-fuse有把client大锁,经过阅读代码发现,ceph-fuse在发送写请求给osd后就释放了client锁,并无持锁等待结果,这虽然对写性能有必定的影响,但不至于将多进程io串行 |
笔者展开更多测试,发现了一条重要的线索:
③ 16进程direct随机写16个文件时,经过kernel fuse的controlfs(/sys/fs/fuse/connections/39/waiting)看到等待的请求数一直是16,而16进程direct随机写单文件时,等待的请求数一直是1
线索③说明,在libfuse以前,多进程已经被串行化,这样能够基本排除libfuse、ceph-fuse、mds、ceph cluster的嫌疑了,笔者能够把精力放在io链条的前面了。
用户态程序fio -> vfs -> kernel fuse
静下心来,思索下...
多进程写单个文件,io被彻底串行化,凭经验,要么是有文件锁,要么有inode锁。先朝这方向走一下,咱们须要定位下锁在io链条的哪一个环节,逐个分析下每一个环节。
用户态程序fio:
经过查看fio的man page,笔者发现fio有个lockfile的参数,当lockfile设置成exclusive或者readwrite时,多进程的写会被串行化,然而该参数的默认值是none,因此能够排除用户态程序fio的嫌疑。
vfs:
vfs是内核很薄的一层,并且linux上能够经过flock和fcntl对文件进行加锁,在没有用户态程序调用的状况下,vfs显然不会私自对文件和inode加锁,而且,这个案情在kernel cephfs客户端下不存在,因此能够排除vfs的嫌疑。
排除了其余io环节,最后只剩下 kernel fuse ,是笔者熟悉程度相对比较低的模块,看来须要重点招待了。
笔者从新阅读kernel fuse的io流程代码,最终拿到证据,真相是如此简单....
static ssize_t fuse_direct_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){ ... /* Don't allow parallel writes to the same file */ mutex_lock(&inode->i_mutex); res = __fuse_direct_write(&io, &iov, 1, ppos); if (res > 0) fuse_write_update_size(inode, *ppos); mutex_unlock(&inode->i_mutex); ... }
凶手做案手法:
多进程direct写io到kernel fuse的该函数后,拿到inode锁,进行io请求,io请求发送到pending队列后,会持锁等待io请求结束,从而致使多进程的direct写io被串行化。
专一笔者公众号,阅读更多干货文章:)