읽기와 쓰기는 생각보다 비대칭적이다
원제 Reading and writing are less symmetric than you (probably) think
102 포인트댓글 24
Key Point
데이터 손실 방지가 중요한 애플리케이션을 개발할 때, fwrite 후 fflush/close 반환 코드와 fsync만으로는 실제 저장 보장이 부족함을 알아야 한다.
핵심 요약
- POSIX의 read(2)와 write(2) 시스템 콜은 대칭적으로 보이지만, 실제로는 중요한 동작 차이가 있다.
- 파일 I/O는 커널의 버퍼 캐시와 사용자 공간의 버퍼 등 여러 계층을 거친다. 블록 디바이스는 전체 블록(512바이트~4KB)만 읽고 쓸 수 있어서, 바이트 단위 I/O 요청을 정렬된 블록 연산으로 변환해야 한다.
- 읽기 작업은 버퍼링이 투명하게 작동한다. 데이터를 채우는 과정에서 문제가 생기면 fread 호출이 반환되기 전에 감지되어 즉시 에러 코드로 보고된다.
- 쓰기 작업은 버퍼링 때문에 에러 보고가 지연된다. fwrite는 데이터를 쓰기 버퍼로만 복사하고 즉시 성공을 반환하며, 실제 write 시스템 콜은 버퍼가 가득 차거나 fflush·fclose를 호출할 때까지 실행되지 않는다.
- write 시스템 콜이 성공해도 데이터는 아직 커널의 버퍼 캐시에만 있고, 실제 저장 장치에 도달하지 않았을 수 있다. 장치 오류나 시스템 크래시 발생 시 손실될 수 있다.
- NFS 같은 네트워크 파일 시스템에서는 close 호출 시점에만 쓰기 에러가 보고될 수 있으므로, close 반환 코드를 확인해야 한다.
- 데이터가 저장 장치에 도달하도록 보장하려면 fsync(2)를 명시적으로 호출해야 한다. 단순히 write 성공 코드와 close 반환 코드를 확인하는 것만으로는 부족하다.
- fsync 호출도 디렉토리 메타데이터가 아닌 파일 데이터만 보장한다. 파일에 접근하는 디렉토리 엔트리도 디스크에 기록되도록 하려면 디렉토리 파일 디스크립터에 fsync를 별도로 호출해야 한다.
- RAID 컨트롤러와 저장 장치 자체도 캐시를 가지고 있으며, 전원 손실 시 안전하게 쓰기할 충분한 커패시터가 있다고 가정해야 한다.
- 결론: 읽기 버퍼링은 투명하게 작동하지만, 쓰기 버퍼링은 에러 보고 지연과 여러 계층의 캐시로 인해 대부분의 프로그래머가 인식하지 못하는 복잡한 문제를 야기한다.