데이터 일관성과 내구성(Data consistency and durability)

데이터 일관성과 내구성(Data consistency and durability)

파일 I/O를 할 때 데이터가 내구성(durable) 있게 저장되는지, 다른 프로세스가 일관성(consistent) 있게 바라볼 수 있는지에 대한 이야기를 다루는 문서예요. 기본 동작이 무엇인지, 더 보장을 얻으려면 어떻게 해야 하는지를 설명해줘요.

출처: Data consistency and durability (The GNU Fortran Compiler)

본문

이 절은 I/O를 할 때 데이터와 메타데이터의 일관성, 그리고 내구성 문제에 대한 간략한 개요를 다뤄요.

내구성(durability) 측면에서, GNU Fortran은 데이터가 안정적인 저장 장치에 커밋되도록 하는 노력을 하지 않아요. 이것이 필요하다면, GNU Fortran 프로그래머는 내장 함수 FNUM 을 써서 열려 있는 Fortran 유닛에 대응하는 저수준 파일 기술자(file descriptor)를 얻을 수 있어요. 그런 다음 예를 들어 ISO_C_BINDING 기능을 이용해, 더럽혀진(dirty) 데이터를 안정적인 저장 장치로 플러시하는 시스템 호출을 직접 부를 수 있어요. POSIX에서는 fsync, MinGW에서는 _commit, macOS에서는 fcntl(fd, F_FULLSYNC, 0)이죠. fsync를 호출하는 예시는 다음과 같아요.

  ! POSIX fsync 함수의 인터페이스를 선언해요
  interface
    function fsync (fd) bind(c,name="fsync")
    use iso_c_binding, only: c_int
      integer(c_int), value :: fd
      integer(c_int) :: fsync
    end function fsync
  end interface
  ! 변수 선언
  integer :: ret
  ! 유닛 10 을 연다
  open (10,file="foo")
  ! ...
  ! 유닛 10 에 대해 I/O 를 수행
  ! ...
  ! 플러시하고 동기화한다
  flush(10)
  ret = fsync(fnum(10))
  ! 가능한 오류를 처리
  if (ret /= 0) stop "Error calling FSYNC"

일관성(consistency) 측면에서, 일반 파일에 대해 GNU Fortran은 성능을 위해 **버퍼링된 I/O(buffered I/O)**를 사용해요. 이 버퍼는 가득 차면 자동으로 플러시되고, 유닛을 닫을 때 같은 일부 상황에서도 플러시돼요. FLUSH 문으로 명시적으로 플러시할 수도 있어요. 또 GFORTRAN_UNBUFFERED_ALLGFORTRAN_UNBUFFERED_PRECONNECTED 환경 변수로 버퍼링을 끌 수도 있어요. 터미널이나 파이프 같은 특수 파일은 항상 버퍼링 없이(unbuffered) 처리돼요. 그런데 때로는 GNU Fortran이 쓴 데이터를 다른 프로세스가 볼 수 있도록 하려면 추가로 조치가 필요할 때가 있어요.

Windows 플랫폼은 느슨한 메타데이터 일관성 모델을 지원해요. 파일 메타데이터가 디렉터리에 느리게(lazily) 기록되거든요. 그래서 예를 들어 dir 명령이 파일의 지난 크기를 보여줄 수 있어요. 유닛을 닫거나, 파일 기술자에 _commit을 호출하면 디렉터리 메타데이터 갱신을 강제할 수 있어요. 다만 _commit은 모든 더럽혀진 데이터를 안정적인 저장 장치로 강제해서, 흔히 아주 느린 연산이에요.

**네트워크 파일 시스템(NFS)**은 open-to-close consistency라고 부르는 느슨한 일관성 모델을 구현해요. 파일을 닫으면 더럽혀진 데이터와 메타데이터가 서버로 플러시되고, 파일을 열면 클라이언트가 서버에 접촉해서 캐시된 데이터를 재검증해요. fsync도 더럽혀진 데이터와 메타데이터를 서버로 플러시해요. 열기/닫기와 비슷하게, 서버가 지원한다면 fcntl 파일 잠금을 얻고 해제하는 것 역시 캐시 검증과 더럽혀진 데이터·메타데이터 플러시를 강제해요.

더 알아보기

  • FNUM 내장 함수는 열려 있는 유닛의 저수준 파일 기술자를 돌려줘요. 시스템 호출과 연결할 때 첫 단계예요.
  • FLUSH 문은 유닛의 버퍼를 사용자 코드에서 명시적으로 비울 때 써요. 버퍼링된 I/O의 타이밍을 제어하는 기본 수단이에요.
  • GFORTRAN_UNBUFFERED_ALL, GFORTRAN_UNBUFFERED_PRECONNECTED 환경 변수로 버퍼링을 비활성화하면, 작은 쓰기가 잦을 때 동작은 바뀌지만 성능은 나빠질 수 있어요.
  • 크래시 내성이 꼭 필요한 파일(예: 로그, 트랜잭션 레코드)은 flush + fsync 조합을 쓰지 않으면 손실될 수 있어요. 언제 저장이 보장되는지의 의미론을 정확히 알고 써야 해요.