본문 바로가기
1. 개발/1.8. ActiveMQ

'fsync'와 'datasync' 명령이 디스크 컨트롤러에 전달되는 방식은?

by 엉짱 2026. 3. 23.
반응형

'fsync'와 'datasync' 명령이 디스크 컨트롤러에 전달되는 방식은?

엔터프라이즈 메시징 시스템이나 데이터베이스 아키텍처를 설계할 때, "데이터가 디스크에 안전하게 저장되었다"고 확신할 수 있는 시점은 언제일까요? 애플리케이션 코드에서 파일 쓰기 함수인 write()가 성공적으로 반환(Return)된 시점이라고 생각하기 쉽지만, 이는 거대한 착각입니다.

현대의 운영체제(OS)는 극도의 디스크 I/O 병목을 피하기 위해 애플리케이션이 넘긴 데이터를 즉시 물리적 하드웨어에 쓰지 않고, 주 메모리(RAM)의 '페이지 캐시(Page Cache)' 영역에 임시로 보관합니다. 만약 이 상태에서 서버의 전원이 차단되면, 애플리케이션은 쓰기가 완료되었다고 믿었음에도 불구하고 데이터는 허공으로 증발해 버립니다.

이러한 재플을 막고 휘발성 메모리에 머무는 데이터를 비휘발성 물리적 디스크로 강제 밀어 넣기 위해 시스템 프로그래밍 레벨에서 호출하는 핵심 명령어가 바로 fsync()fdatasync()입니다.

본 가이드에서는 이 두 명령어가 OS 커널을 통과하여 하드웨어인 디스크 컨트롤러에 도달하고, 최종적으로 데이터를 영속화하는 파이프라인과 그 차이점을 상세히 해부합니다.


1. OS 커널과 하드웨어 사이의 데이터 플러시 파이프라인

fsyncfdatasync 명령이 호출되면, 데이터는 단순히 "디스크로 이동"하는 것이 아니라 여러 계층의 복잡한 커널 서브시스템을 통과해야 합니다.

  1. VFS (Virtual File System) 계층: 애플리케이션의 동기화 요청을 수신하고, 대상 파일이 속한 구체적인 파일 시스템(ext4, xfs 등)의 동기화 함수로 작업을 위임합니다.
  2. 파일 시스템 (File System) 및 블록 계층 (Block Layer): 페이지 캐시에 있는 '더티 페이지(Dirty Page, 아직 디스크에 쓰이지 않은 변경된 메모리 블록)'들을 찾아 물리적인 디스크 블록 주소로 매핑합니다. 이후 엘리베이터 알고리즘(I/O Scheduler)을 통해 디스크 헤더의 이동을 최소화할 수 있도록 요청을 정렬하고 병합합니다.
  3. 디바이스 드라이버 (Device Driver): 정렬된 I/O 요청을 디스크 하드웨어가 이해할 수 있는 전기적 신호와 전용 프로토콜 명령어(SCSI, SATA, NVMe 규격)로 변환하여 케이블을 통해 전송합니다.

이 과정을 거쳐 명령은 최종적으로 하드웨어의 두뇌인 디스크 컨트롤러(Disk Controller)에 도달하게 됩니다.


2. 'fsync'의 동작 원리: 데이터와 메타데이터의 완벽한 동기화

fsync(fd) 시스템 콜은 파일의 식별자(File Descriptor)를 입력받아, 해당 파일과 관련된 모든 것을 물리적 디스크에 동기화하라고 OS와 디스크 컨트롤러에 명령합니다.

  • 동기화 범위: 파일의 본문인 '데이터(Payload)' 블록뿐만 아니라, 파일의 수정 시간(mtime), 접근 시간(atime), 권한, 소유자, 파일 크기 등을 담고 있는 '메타데이터(Inode)' 블록까지 모두 디스크에 내려씁니다.
  • 성능적 한계 (Double I/O Penalty): 파일에 단 1바이트의 데이터를 추가하더라도, 데이터가 기록되는 디스크 위치(Data Block)와 파일의 메타데이터가 기록되는 디스크 위치(Inode Table)는 물리적으로 멀리 떨어져 있습니다. 따라서 fsync가 호출되면 디스크 헤더는 데이터를 쓰고, 다시 메타데이터를 업데이트하기 위해 크게 이동하는 두 번의 무거운 쓰기 작업(Double I/O)을 강제당하게 되며, 이는 전체 시스템의 처리량(Throughput)을 급격히 저하시키는 원인이 됩니다.

3. 'fdatasync'의 최적화: 필수 메타데이터만 타겟팅

성능 저하를 극복하기 위해 등장한 명령어가 바로 fdatasync(fd) (종종 datasync로 축약 통칭)입니다. 이 명령어는 완벽함보다는 실용성과 I/O 속도에 초점을 맞춥니다.

  • 동기화 범위: 파일의 본문 '데이터(Payload)'를 물리적 디스크에 쓰는 것은 fsync와 동일합니다. 하지만 메타데이터 동기화에서 결정적인 차이를 보입니다.
  • 핵심 최적화 매커니즘: fdatasync는 파일을 나중에 다시 읽는 데 '반드시 필요한 메타데이터(예: 파일 크기 증가)'가 변경되었을 때만 Inode 블록을 디스크에 동기화합니다. 단순히 파일이 수정되었다는 '수정 시간(mtime)'이나 '접근 시간(atime)'과 같이 비핵심적인 메타데이터의 변경은 무시하고 메모리에만 남겨둡니다.
  • 성능적 이점: 파일의 내용만 덮어쓰는(Overwrite) 작업이라면 파일 크기에 변화가 없으므로, 메타데이터 업데이트를 위한 추가적인 디스크 헤더 이동이 생략됩니다. 단 한 번의 디스크 쓰기만으로 동기화가 완료되므로, 초당 수만 건의 트랜잭션을 처리하는 시스템에서 디스크 I/O 병목을 획기적으로 줄여줍니다.

4. 하드웨어의 반격: 디스크 컨트롤러 캐시와 Flush 명령어

여기서 아키텍트들이 놓치기 쉬운 가장 깊은 함정이 있습니다. OS 커널이 디스크로 데이터를 완벽하게 내려보냈더라도, 디스크 하드웨어 자체적으로 내장된 '디스크 쓰기 캐시(Disk Write Cache)'라는 또 다른 휘발성 메모리(SRAM/DRAM)가 존재한다는 사실입니다.

디스크 컨트롤러는 벤치마크 성능을 높이기 위해 OS가 보낸 데이터를 즉시 낸드플래시(NAND)나 마그네틱 플래터에 기록하지 않고 자신의 캐시에 담은 뒤 OS에게 "기록 완료" 응답을 보낼 수 있습니다. 이 상태에서 정전이 발생하면 역시나 데이터는 증발합니다.

이를 원천 차단하기 위해 fsyncfdatasync 시스템 콜은 데이터 전송 후 반드시 디스크 컨트롤러에 하드웨어 레벨의 캐시 비우기(Cache Flush) 명령어를 추가로 발송합니다.

  • SATA 디스크의 경우: FLUSH CACHE EXT 명령어를 컨트롤러에 전송.
  • NVMe SSD의 경우: Flush 명령어를 제출 큐(Submission Queue)에 전송.

디스크 컨트롤러는 이 명령을 수신하는 즉시 자신의 휘발성 캐시에 있는 모든 데이터를 비휘발성 저장 매체에 강제로 기록(Commit)합니다. 이 하드웨어 작업이 완전히 끝난 후에야 비로소 애플리케이션의 fsync 호출이 최종적으로 성공(Return)하며, 우리는 진정한 의미의 '데이터 영속성(Durability)'을 얻게 됩니다.


5. 메시지 브로커 및 데이터베이스 아키텍처에의 적용

ActiveMQ Artemis나 Kafka 같은 메시지 브로커, 혹은 PostgreSQL 같은 관계형 데이터베이스의 스토리지 엔진(WAL - Write Ahead Logging)은 데이터 유실을 0으로 만들기 위해 매 트랜잭션마다 이 동기화 함수들을 호출해야 합니다.

  • 저널링과 fdatasync의 궁합: 브로커가 저널 파일에 데이터를 밀어 넣을 때, 매번 fsync를 호출하면 메타데이터 갱신 오버헤드에 짓눌려 초당 처리량(TPS)이 바닥을 칩니다. 따라서 성능이 중요한 고부하 환경에서는 파일 크기만 갱신하는 fdatasync를 적극적으로 활용하여 I/O 비용을 최소화하는 최적화 기법을 기본적으로 채택하고 있습니다.
반응형