写回把缓冲可见性与存储完成分开
缓冲文件 I/O 可以在不同时间经过语言缓冲区、内核页缓存和存储栈。被修改的缓存页需要写回,使数据进入后端文件系统与设备。分开这些阶段可以合并工作、提高吞吐,但也意味着另一读取者看到新字节,本身不能证明已经完成可经历崩溃的存储边界。理解性能和持久性时,需要先确定观察的是哪一层完成。 坏掉的样子 缓冲写入不保证瞬间返回。分配、内存压力、脏页限制或文件系统活动,都可能让写者等待。如果生产速度持续超过存储处理能力,脏数据不能无限增长,压力最终会反映到工作负载。把每次 write 都描述为单纯内存复制、所有 I/O 都在之后发生,会错误解释持续负载下的延迟尖峰。 语言层 flush 与文件系统同步含义不同。flush 把应用缓冲数据提交到下一层,fsync 则请求文件同步边界。普通关闭文件,或者从另一个描述符读到相同字节,不能替代这一显式契约。因此只测成功写入的基准,可能测到的是缓存接受速度,而不是稳定、持久的存储吞吐,需要与实际工作要求对应。 写回设置也不是逐文件持久性截止时间。后台阈值促使刷新线程工作,更高的脏页阈值影响生成写入的进程,周期参数则控制唤醒或较老脏数据何时符合处理条件。设备进展、文件系统行为和竞争负载仍会影响结果。不能仅设一个时间间隔,就证明每次已确认更新在这么多秒后必定安全。 快速验证 把脚本保存为 writeback.py,在装有 Python 和 strace 的 Linux 环境中运行 python3 writeback.py,并让临时目录位于 ext4。实测使用 Linux 6.12.76、Python 3.11.2 和 strace 6.1。样本写入约四 MiB、读取记账信息,不改变全局脏内存参数或缓存控制: from pathlib import Path from tempfile import TemporaryDirectory import subprocess,sys program=r''' from pathlib import Path import json,os,subprocess,sys root=Path(sys.argv[1]);path=root/'data' def io():return {key:int(value) for key,value in (line.split(':',1) for line in Path('/proc/self/io').read_text().splitlines())} def dirty():return int(next(line.split()[1] for line in Path('/proc/meminfo').read_text().splitlines() if line.startswith('Dirty:'))) with path.open('wb',buffering=65536) as handle: handle.write(b'buffered') assert path.stat().st_size==0 and path.read_bytes()==b'' before=io();handle.flush();after=io() assert after['wchar']-before['wchar']>=8 reader='from pathlib import Path;import sys;assert Path(sys.argv[1]).read_bytes()==b"buffered"' subprocess.run([sys.executable,'-c',reader,str(path)],check=True) print('visible before explicit fsync',flush=True) before=io();chunk=b'x'*(4*1024*1024);handle.write(chunk);handle.flush();after=io() assert after['wchar']-before['wchar']>=len(chunk) assert after['write_bytes']-before['write_bytes']>=len(chunk) dirty_before=dirty();os.fsync(handle.fileno());dirty_after=dirty() assert path.stat().st_size==len(chunk)+8 print(json.dumps({'wchar_delta':after['wchar']-before['wchar'],'write_bytes_delta':after['write_bytes']-before['write_bytes'],'system_dirty_kib_before':dirty_before,'system_dirty_kib_after':dirty_after})) ''' with TemporaryDirectory(prefix='xbcatm-writeback-') as directory: root=Path(directory);trace=root/'trace' result=subprocess.run(['strace','-f','-yy','-s','200','-e','trace=write,fsync,fdatasync','-o',str(trace),'--',sys.executable,'-c',program,str(root)],capture_output=True,text=True,timeout=10) assert result.returncode==0,result.stderr lines=trace.read_text().splitlines() visible=next(i for i,line in enumerate(lines) if 'visible before explicit fsync' in line) syncs=[i for i,line in enumerate(lines) if 'fsync(' in line and str(root/'data') in line] assert len(syncs)==1 and visible<syncs[0] and '= 0' in lines[syncs[0]] print(result.stdout,end='');print(lines[syncs[0]]) print('another process read flushed bytes before the only explicit file synchronization; global Dirty is observational, not an isolated-device counter') 子进程用六万五千五百三十六字节的 Python 缓冲打开文件,并写入八字节。flush 前,路径报告长度零,独立打开也读不到内容,因为数据仍在应用缓冲里。这个对照直接建立最早的边界,没有假定每次语言级 write 都已经发出内核写入。应用接口返回与系统调用发生,需要分别确认。 ...