Skip to content
난이도 문제은행 3

파일 시스템 2 ​

파일 시스템에서 본 구조 위에서 링크와 장애 대비가 어떻게 동작하는지 이어서 다룬다. 유닉스 계열 파일 시스템(file system)에서 inode(index node, 색인 노드)는 파일의 크기·권한·데이터 블록(block) 위치와, 자신을 가리키는 이름의 개수인 링크 수를 담는 구조다. 파일 이름은 inode가 아니라 디렉터리(directory) 항목에 "이름 → inode 번호"로만 적혀 있다. 이 구조에서 나오는 두 가지 링크를 먼저 보고, 여러 곳을 고치다 전원이 나가도 구조가 깨지지 않게 하는 방법과 write()한 데이터가 실제로 디스크에 닿는 시점을 차례로 본다.

하드 링크와 심볼릭 링크 ​

다이어그램 로딩…
  • 하드 링크(hard link)는 같은 inode를 가리키는 디렉터리 항목을 하나 더 만드는 것이다. 두 이름은 완전히 동등하고, 어느 쪽을 지워도 다른 이름으로 계속 읽힌다. 이름을 지우면 링크 수가 줄고, 링크 수가 0이 되고 그 파일을 연 프로세스(process)도 없어야 실제 데이터가 지워진다. inode 번호는 파일 시스템 안에서만 의미가 있어서 다른 파일 시스템에는 하드 링크를 만들 수 없다. 디렉터리에도 보통 만들 수 없다. 디렉터리에 하드 링크를 허용하면 디렉터리가 자기 조상을 가리키는 고리가 생길 수 있고, 그러면 디렉터리 계층을 따라 내려가는 탐색이나 삭제가 끝나지 않을 수 있기 때문이다.
  • 심볼릭 링크(symbolic link)는 대상의 경로 문자열을 내용으로 담은 별도의 작은 파일이다. 경로만 적어 두므로 다른 파일 시스템이나 디렉터리도 가리킬 수 있다. 대신 원본이 지워지거나 옮겨지면 가리킬 곳이 없는 끊어진 링크가 된다.

저널링: 중간에 전원이 나가도 ​

파일 하나에 내용을 덧붙이는 작업도 디스크 여러 곳을 고친다. 새 데이터 블록을 쓰고, 그 블록을 "사용 중"으로 표시하고, inode의 크기와 블록 목록을 고쳐야 한다. 이 중 일부만 쓰인 채 전원이 나가면 파일 시스템이 서로 맞지 않는 상태로 남는다. 예전에는 부팅할 때 fsck(파일 시스템 검사 도구)로 디스크 전체를 훑어 고쳤는데, 디스크가 클수록 몇 시간씩 걸렸다.

저널링(journaling)은 실제 위치를 고치기 전에, 무엇을 고칠지를 디스크의 저널(journal) 영역에 먼저 적고 "여기까지 한 묶음"이라는 커밋 표시를 남긴다. 그다음에 실제 위치에 반영한다. 장애 뒤에는 저널만 보면 된다. 커밋 표시까지 적힌 묶음은 다시 반영하고, 커밋 표시가 없는 묶음은 버린다.

다이어그램 로딩…

그래서 복구가 디스크 크기와 상관없이 빨라지고, 한 묶음의 변경이 전부 반영되거나 전혀 반영되지 않는다.

모든 데이터까지 저널에 두 번 쓰면 느리므로, 흔히 메타데이터(metadata)만 저널에 남긴다. 리눅스의 대표 파일 시스템인 ext4의 기본 모드(data=ordered)는 메타데이터만 저널에 쓰되, 관련 데이터 블록을 먼저 제자리에 쓴 뒤 메타데이터를 커밋한다. 이 순서를 지키면 장애가 나도 inode가 아직 쓰이지 않은 블록, 곧 쓰레기 값이 든 블록을 가리키는 일이 생기지 않는다.

마운트 ​

마운트(mount)는 어떤 저장 장치의 파일 시스템을 이미 있는 디렉터리 하나에 붙여, 하나의 계층 구조 안에서 보이게 하는 일이다. 예를 들어 mount /dev/sdb1 /mnt/data를 하면 그 뒤로 /mnt/data 아래를 읽는 것은 두 번째 디스크를 읽는 것이 된다. 윈도우가 장치마다 C:·D:처럼 드라이브 문자를 붙이는 것과 달리, 유닉스 계열은 모든 장치를 / 하나의 계층 구조에 이어 붙인다.

페이지 캐시와 fsync ​

파일에 데이터를 쓰는 시스템 콜(system call)인 write()는 보통 디스크에 바로 쓰지 않는다. 커널(kernel)은 파일 내용을 메모리에 담아 두는 페이지 캐시(page cache)에 데이터를 복사하고 곧바로 돌아온다. 실제 디스크 쓰기는 나중에 커널이 모아서 한다. 덕분에 쓰기가 빠르고, 같은 파일을 다시 읽을 때도 디스크를 거치지 않는다. 대신 디스크에 쓰이기 전에 전원이 나가면 "쓰기에 성공한" 데이터가 사라질 수 있다.

fsync(fd)는 파일 디스크립터(file descriptor) fd가 가리키는 파일의 바뀐 데이터와 메타데이터를 저장 장치까지 내려보내고, 장치가 완료를 알릴 때까지 기다린다. 파일 이름은 inode가 아니라 디렉터리 항목에 적혀 있으므로, 새로 만든 파일이라면 그 디렉터리도 따로 fsync()해야 장애 뒤에도 이름으로 파일을 찾을 수 있다. 데이터베이스가 커밋 때마다 로그를 fsync()하는 이유가 이것이다. 커밋에 성공했다고 응답한 변경은 장애가 나도 남아 있어야 하기 때문이다(버퍼 풀과 로그).

면접 포인트 ​

  • "하드 링크와 심볼릭 링크의 차이는?" → 같은 inode를 가리키는 이름 추가 vs 경로를 담은 별도 파일. 원본 삭제 시 하드 링크는 살아 있고 심볼릭 링크는 끊어진다. 하드 링크는 파일 시스템을 넘지 못한다.
  • "로그 파일을 지웠는데 디스크 공간이 안 줄어드는 이유는?" → 링크 수가 0이 돼도 그 파일을 연 프로세스가 남아 있으면 데이터가 지워지지 않는다.
  • "저널링은 왜 필요한가요?" → 여러 곳을 고치는 도중의 장애로 생기는 불일치를 저널 재반영으로 빠르게 복구.
  • "write()가 성공하면 디스크에 저장된 건가요?" → 아니다. 페이지 캐시에 있을 뿐이고, 내구성(durability)이 필요하면 fsync()한다. 새 파일이면 디렉터리도 fsync().

관련 문서