最近一位NAS用户在微信上报道了NAS的性能测试报告,报告中测试数据显示buffered io读性能比direct io读要差。这显然和直观的认识不符,在内存充足的情况下,buffered io读的数据一般都在page cache中,每次读都是内存操作,其性能应该远远高于direct io,但测试数据却得到了相反的结果,这说明某些地方拖慢了buffered io读性能。
首先回顾一下用户的测试场景:保证内存完全够用的情况下,使用fio工具,基于libaio对比测试buffered io和direct io的读性能。结合测试场景和NAS后端逻辑可得到如下信息:
1) 内存完全够用,文件的数据能够全部缓存到内存中;
2) fio使用libaio库发起异步读请求(这通过阅读fio代码确认);
3) NAS后端在处理读请求时,不关心到底是buffered io还是direct io;
4) 挂载参数:vers=3,nolock,actimeo=0,proto=tcp,sec=sys,port=2049
基于上述信息,直观上推断可能是NFS客户端或者fio的实现导致测试数据异常,从该疑点出发,跟踪了客户端读操作的调用过程:
io_submit(系统调用) ---> io_submit_one(fs/aio.c) ---> aio_run_iocb(fs/aio.c) ---> aio_rw_vect_retry(fs/aio.c) ---> nfs_file_read(fs/nfs/file.c)
nfs_file_read的代码实现比较简单:
1) 针对O_DIRECT情况:
nfs_file_direct_read会在调用nfs_sync_mapping(因为是只读场景该函数不会触发sync操作)后调用nfs_direct_read_schedule_iovec,该函数会调用nfs_direct_read_schedule_segment对每个需要读取的数据片段发起异步的rpc请求操作,然后返回,不会有阻塞的情况发生;
2) 针对buffered io的情况:
首先会调用nfs_revalidate_mapping函数,当actimeo=0情况下会发起同步的getattr操作去更新inode信息,这会导致io_submit系统调用阻塞,进而导致fio的压力线程阻塞住,对外看来就是只有一个并发在运行,其代码如下:
在nfsv3协议中getattr函数调用的是nfs3_proc_getattr,在nfsv4调用的是nfs4_proc_getattr,以v3为例:
综合上面的代码分析可知,buffered io读性能差的原因是在actimo=0的情况下,内核每次读文件时都去server端获取inode最新的属性信息,以确保本地page cache数据的有效性,若inode信息表明文件内容没有改动,则直接使用page cache中缓存的数据给调用者;否则会在nfsi->cache_validity设置NFS_INO_INVALID_DATA标记,nfs_revalidate_mapping函数会调用nfs_invalid_mapping函数将page cache清空掉。当前nfs client判断page cache有效性主要通过ctime、mtime、change time等属性是否发生变化判断。
为验证上述推断的正确性,进行了如下的对比测试:
1) mount参数不变,增加fio的worker的数量(通过numjobs选项指定),发现在一定的范围内,性能随着numjobs的变大而基本呈现线性增加的趋势;
2)修改mount参数actimeo不为0,发现刚开始一段时间内qps没有什么太大变化依旧很慢,然后运行一段时间后突然飙升,直接上升到300K左右的QPS,后续继续测试同一文件QPS保持很高,基本不再变化,本地文件系统文件测试也存在同样的现象。
针对对比测试2)中的场景,跟踪了generic_file_aio_read的实现逻辑,其对每个需要读取的数据段调用do_generic_file_read,该函数的主要逻辑是从文件的page cache (文件的address space对象)中查找对应数据所在的page是否存在,若存在且page的数据是有效的(uptodate),则将数据拷贝到用户指定的buffer中;若不存在或者page中不是有效数据,则等待page的数据变为有效(必要时会发起预读操作)。从上述的分析可知,此时的buffered io读仍旧是同步的过程,即fio发起压力仍旧为同步的,所以测试用例刚开始运行时,性能依旧不高,随着page cache不断填充,当page cache接近完全命中时,QPS会有一个暴涨的过程。下面是,page cache命中率和QPS之间的关系图,其中page cache命中读延时为0.0033ms,不命中延时为1.5ms (采用NFSServerMock测试的数据):
从图中可知,随着page cache命中率的增高,QPS逐渐增加,但只有在命中率90%以上时,才有QPS的暴增过程,这是和实验结果相符的。
调查问题的过程中,查阅了一些linux内核aio的相关资料和实现,内核本身对direct io提供较好的支持,这也是大多数应用使用libaio的基本场景;但对于buffered io的支持是有限的,下面的文章对此进行了一些讨论:
http://www.wzxue.com/linux-kernel-aio%E8%BF%99%E4%B8%AA%E5%A5%87%E8%91%A9/#6_AIO
针对该用户性能测试遇到问题,解决的方法可在如下两个方面入手:
1) 若应用容忍短时间内多个NFS客户端存在不一致,则可将actimeo参数设定为非零值,避免每次操作都要去server端获取最新的inode等信息,提高性能;
2) 若应用不能容忍不一致的情况即actimeo=0,则最好使用direct io的方式,避免buffered io可能引发的阻塞操作。
最后附上用户的测试报告地址: http://mp.weixin.qq.com/s?__biz=MzAxNTA2NjkyNQ==&mid=2650380557&idx=1&sn=b7a3276c866b097a918ee1c91385b13b&scene=23&srcid=0502xGTLpNRsq5pIKxH2c5TY#rd