http://www.cnblogs.com/hustcat/p/3283955.html
http://www.cnblogs.com/zengkefu/p/5639200.html
http://www.cnblogs.com/zengkefu/p/4943836.html
http://www.cnblogs.com/zengkefu/p/5639200.html
http://blog.sina.com.cn/s/blog_8308bc810102uxhz.html
深入理解Fsync
1 介绍
数据库系统从诞生那天开始,就面对一个很棘手的问题,fsync的性能问题。组提交(group commit)就是为了解决fsync的问题。最近,遇到一个业务反映MySQL创建分区表很慢,仔细分析了一下,发现InnoDB在创建表的时候有很多fsync——每个文件会有4个fsync的调用。当然,并不每个fsync的开销都很大。
这里引出几个问题:
(1)问题1:为什么fsync开销相对都比较大?它到底做了什么?
(2)问题2:细心的人可以发现,第一次open数据文件后,第二次fsync的时间远远小于第1次调用fsync的时间,为什么?
(3)问题3:能否优化fsync?
来着这些疑问,一起来了解一下fsync。
2 原因分析
我们先通过一个测试程序来学习一下fsync在块层的基本流程。
2.1 测试程序1
Write page 0 Sleep 5 Fsync |
用blktrace跟踪结果如下:
上半部红色框内为pwrite在块层的流程,下半部黄色框内为fsync在块层流程,中间刚好相差5秒。
4722712为测试文件的第1个block对应的扇区号,590339(block号) * 8=4722712(扇区号)。
无论是pwrite,还是fsync,主要的开销都发生IO请求提交给驱动和IO完成之间,也就是说开自设备驱动。差不多占了整个系统调用的1/2的开销。
另外,可以看到调用fsync时,发生了3次块层IO,起始扇区分别是19240、19248和19256,物理上3个连续的块。实际上这3个块为内核线程kjournald写的日志,分别描述块(2405)、数据块(2406)和提交块(2407)。为了验证,不妨看一下这三个块的实际数据。
19240/8=2405
19248/8=2406
19256/8=2407
块2405:
#define JFS_MAGIC_NUMBER 0xc03b3998U #define JFS_DESCRIPTOR_BLOCK 1 #define JFS_COMMIT_BLOCK 2 |
开始的4个字节为JFS_MAGIC_NUMBER,然后是block type:JFS_DESCRIPTOR_BLOCK。
块2407:
的确是提交块。
2.2 fsync的实现
既然fsync的开销很大,就来看看代码吧。
函数ext3_sync_file:
函数log_start_commit负责唤醒kjounald内核线程,log_wait_commit等待jbd事务提交完成。
从代码来看,fsync的主要开销在于调用log_wait_commit后的等待。也就是说fsync要等待kjournald把事务提交完成,才会返回。
到这里,我们已经知道了fsync开销的主要来源:(1)硬件驱动层的开销;(2)ext3写日志。
另外,当log_start_commit返回0时,fsync就不会等待事务提交完成。到这里已经基本可以确认第2次fsync的开销为什么那么小了——没有wait事务提交。
下面验证这一想法。为了方便调试,打开了内核jbd debug日志。
2.3 测试程序2
Write page 0 Fsync Write page 0 Fsync Write page 1 Fsync Write page 2 Fsync |
从第2个红框的日志来看,第2次fsync时,的确是没有wait的,所以开销这么小,而其它3次fsync都调用了log_wait_commit函数。
问题4:第2次fsync为什么不会调用log_wait_commit?
因为挂载文件系统的时候,data=writeback,即写数据本身不会写jbd日志。第2次pwrite没有引起文件扩展,只会修改ext3 inode的i_mtime,而i_mtime只精确到second,也就是说第2次pwrite不会引起inode信息改变,所以,不会生成jbd日志,也就不需要等待事务提交完成。
下面验证一下该想法。
2.4 测试程序3
Write page 0 Fsync Sleep 1 second Write page 0 Fsync Write page 1 Fsync Write page 2 Fsync |
在第2次pwrite之前,sleep 1秒钟,保证ext3 inode的i_mtime修改。
想法被证实了,第2次fsync的时间回到正常水平。
可以看到,第2次fsync调用提交了新的事务,并调用了log_wait_commit等待事务完成。
3 优化
如何优化fsync?是个难题。
(1)系统减少对fsync的调用。
(2)ext3日志放在更快的存储介质,参考http://insights.oetiker.ch/linux/external-journal-on-ssd/
作者:YY哥
出处:http://www.cnblogs.com/hustcat/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
[root@localhost ~]# debugfs -R "stat ./test" /dev/sda2 debugfs 1.39 (29-May-2006) Inode: 3604481 Type: directory Mode: 0755 Flags: 0x0 Generation: 46195286 User: 502 Group: 503 Size: 4096 File ACL: 0 Directory ACL: 0 Links: 3 Blockcount: 8 Fragment: Address: 0 Number: 0 Size: 0 ctime: 0x5768c427 -- Mon Jun 20 21:35:51 2016 atime: 0x57725a43 -- Tue Jun 28 04:06:43 2016 mtime: 0x5768c427 -- Mon Jun 20 21:35:51 2016 BLOCKS: (0):3631328 TOTAL: 1
[root@localhost fs]# find / -name "*.c" | xargs grep "void file_update_time" -rn /usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/fs/inode.c:1225:void file_update_time(struct file *file) /usr/src/kernels/linux-2.6.32/fs/inode.c:1460:void file_update_time(struct file *file)
void file_update_time(struct file *file) { struct inode *inode = file->f_path.dentry->d_inode; struct timespec now; enum { S_MTIME = 1, S_CTIME = 2, S_VERSION = 4 } sync_it = 0; /* First try to exhaust all avenues to not sync */ if (IS_NOCMTIME(inode)) return; now = current_fs_time(inode->i_sb); if (!timespec_equal(&inode->i_mtime, &now)) sync_it = S_MTIME; if (!timespec_equal(&inode->i_ctime, &now)) sync_it |= S_CTIME; if (IS_I_VERSION(inode)) sync_it |= S_VERSION; if (!sync_it) return; /* Finally allowed to write? Takes lock. */ if (mnt_want_write_file(file)) return; /* Only change inode inside the lock region */ if (sync_it & S_VERSION) inode_inc_iversion(inode); if (sync_it & S_CTIME) inode->i_ctime = now; if (sync_it & S_MTIME) inode->i_mtime = now; mark_inode_dirty_sync(inode); mnt_drop_write(file->f_path.mnt); } EXPORT_SYMBOL(file_update_time);
[root@localhost jbd]# find / -name "*.c" | xargs grep "int __log_start_commit" -rn /usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/fs/jbd/journal.c:427:int __log_start_commit(journal_t *journal, tid_t target) /usr/src/kernels/linux-2.6.32/fs/jbd/journal.c:435:int __log_start_commit(journal_t *journal, tid_t target)
int __log_start_commit(journal_t *journal, tid_t target) { /* * Are we already doing a recent enough commit? */ if (!tid_geq(journal->j_commit_request, target)) { /* * We want a new commit: OK, mark the request and wakup the * commit thread. We do _not_ do the commit ourselves. */ journal->j_commit_request = target; jbd_debug(1, "JBD: requesting commit %d/%d ", journal->j_commit_request, journal->j_commit_sequence); wake_up(&journal->j_wait_commit); return 1; } return 0; }
[root@localhost ~]# strace -f -F -T -r -p 5109 -e trace=write,open,read,fsync Process 5160 attached with 22 threads - interrupt to quit [pid 5160] 0.000000 open("./test/h.frm", O_RDONLY) = 18 <0.000049> [pid 5160] 0.000492 read(18, "3761 f3 201 000 20 5 210 10 "..., 64) = 64 <0.000062> [pid 5160] 0.000312 read(18, "// ", 7) = 7 <0.000019> [pid 5160] 0.000120 read(18, "j1 20 "..., 288) = 288 <0.000019> [pid 5160] 0.000154 read(18, "