분산 파일 시스템은 여러 대의 머신이 하나의 파일 시스템을 함께 쓰는 것을 목표로 한다. 한 머신에서 보던 파일과 디렉터리를 다른 머신에서도 같은 방식으로 접근하게 만들어 “어디서 접속하든 같은 파일을 쓰는 환경”을 제공한다.

이 방식의 가장 큰 가치는 공유에 있다. 파일이 특정 클라이언트의 로컬 디스크에 갇히지 않고 서버 쪽에 놓이므로, 여러 클라이언트가 같은 데이터를 기준으로 작업하게 된다. 결과적으로 팀 단위 작업이나 여러 머신을 오가며 일하는 환경에서 데이터가 흩어지지 않는다.
운영 측면에서도 이점이 생긴다. 데이터가 서버에 모이므로 관리가 중앙집중화된다. 백업 같은 작업을 모든 클라이언트에서 각각 처리하지 않고, 소수의 서버를 대상으로 수행하게 된다.
보안 측면의 동기도 존재한다. 데이터가 저장된 서버를 물리적으로 통제된 장소에 두면, 물리적 접근을 통한 위험을 줄이는 방향의 운영이 가능해진다.
1. 기본적인 분산 파일 시스템
분산 파일 시스템은 여러 클라이언트와 하나(또는 소수)의 서버로 구성되며 서버가 디스크에 데이터를 저장하고 클라이언트가 잘 정의된 프로토콜 메시지로 파일과 디렉터리에 접근한다.
이 구조를 쓰는 가장 큰 이유는 클라이언트들 사이에서 데이터 공유를 쉽게 하기 위해서이며 한 머신에서 보던 파일 시스템의 “같은 관점”을 다른 머신에서도 그대로 보게 한다.
추가로 서버 쪽에 데이터를 모아 두면 백업 같은 관리 작업을 중앙에서 수행하기 쉬워지고 서버를 물리적으로 보호하면 보안상 이점도 생긴다.
단순한 클라이언트/서버 분산 파일 시스템에서 클라이언트 애플리케이션은 클라이언트 측 파일 시스템을 통해 open(), read(), write(), close(), mkdir() 같은 시스템콜로 서버에 있는 파일을 접근한다.
이때 애플리케이션 입장에서는 성능 차이를 제외하면 로컬 디스크 파일 시스템과 다르게 보이지 않게 “투명한 접근”을 제공하는 것이 목표가 된다.

클라이언트 측 파일 시스템의 역할은 시스템콜을 처리하기 위해 필요한 동작을 수행하는 것이며 예를 들어 read() 요청이 오면 서버(파일 서버)로 “특정 블록을 읽어 달라”는 메시지를 보낼 수 있다.
파일 서버는 디스크(또는 서버의 메모리 캐시)에서 데이터를 읽어 클라이언트에 응답하고 클라이언트 측 파일 시스템은 그 데이터를 사용자 버퍼에 복사해 read()를 완료한다. 같은 블록을 다시 읽는 경우 클라이언트 메모리나 클라이언트 디스크에 캐시해 두면 네트워크 트래픽 없이 처리될 수도 있다.
결국 분산 파일 시스템의 동작은 “클라이언트 측 파일 시스템”과 “파일 서버”라는 두 소프트웨어 구성요소의 설계와 상호작용이 좌우한다.
서버는 왜 크래시 되는가?
서버가 크래시하는 이유로는 전원 장애처럼 물리적인 원인도 있고 거대한 코드베이스가 갖는 버그 때문에 언젠가 크래시를 유발할 수도 있다. 또한 작은 메모리 누수도 결국 메모리 고갈로 이어져 시스템 크래시를 만들 수 있다. 분산 환경에서는 네트워크가 분할되는 등 이상 동작을 하면 실제로는 원격 서버가 살아 있어도 통신이 안 되어 “크래시처럼 보이는” 상황이 생긴다고 본다.
2. NFS에 대하여
초기의 성공적인 분산 시스템 중 하나로 Sun Microsystems가 만든 Sun Network File System(NFS)이 있으며 NFS를 정의할 때 Sun은 폐쇄적이고 독점적인 시스템 대신 “오픈 프로토콜”을 만드는 방식을 택한다.
이 프로토콜은 클라이언트와 서버가 통신할 때 쓰는 메시지 형식을 정확히 규정하며 서로 다른 집단이 각자 NFS 서버를 구현해도 상호운용성을 유지한 채 시장에서 경쟁할 수 있게 한다.
이 “오픈 마켓” 접근은 실제로 성공했고 Oracle/Sun, NetApp, EMC, IBM 등 많은 회사가 NFS 서버를 판매하게 되었으며 NFS의 확산은 이러한 접근 덕분이다.
3. 핵심 단순하고 빠른 서버 크래시 복구
NFSv2는 서버가 크래시했을 때 빠르게 복구하는 것을 최우선 목표로 삼는다
여러 클라이언트가 한 서버를 공유하는 환경에서는 서버가 멈춘 시간이 곧 모든 사용자의 생산성 손실로 이어지기 때문에, “서버가 빨리 다시 살아나는가”가 시스템 전체의 품질을 좌우한다
4. 빠른 크래시 복구의 열쇠 : 상태를 유지하지 않음
이 목표를 위해 NFSv2는 stateless(무상태) 프로토콜로 설계한다
서버는 “어떤 클라이언트가 어떤 블록을 캐시하는지”, “어떤 파일이 열려 있는지”, “파일 포인터가 어디인지” 같은 정보를 추적하지 않는다
대신 각 요청 메시지 자체에 작업을 완료하는 데 필요한 정보를 모두 담도록 설계한다
왜 open() 같은 stateful 방식이 문제가 되는지도 예로 든다
만약 서버가 open()을 받아서 파일 디스크립터를 만들고 클라이언트에 돌려주는 방식이라면, 그 디스크립터는 클라이언트와 서버가 공유하는 분산 상태(distributed state)가 된다 이때 서버가 크래시하면 “그 fd가 어떤 파일을 가리키는지” 같은 정보가 메모리에서 사라져 복구가 복잡해진다 또한 클라이언트가 open() 후 크래시하면 close()가 오지 않으므로 서버가 “언제 파일을 닫아도 되는지” 판단하려면 결국 클라이언트 생존을 추적해야 하는 문제가 생긴다
그래서 NFS는 각 요청이 완결적으로 동작하도록 만들고, 서버는 재시작 후에도 그냥 요청을 다시 처리하면 되게 만든다
5. NFSv2 Protocol
stateless하게 만들려면 “서버가 상태를 들고 있어야만 가능한 open()” 같은 호출을 프로토콜에서 직접 제공하기 어렵다
그 대신 NFS는 file handle을 핵심 식별자로 사용한다.
file handle은 보통 (volume id, inode number, generation number)로 구성되며, 서버가 “어느 파일시스템의 어느 inode를 대상으로 하는 요청인지”를 즉시 알 수 있게 한다
프로토콜은 대략 다음처럼 “핸들 기반 연산”들로 구성한다
- LOOKUP: (디렉터리 핸들, 이름) → (대상 파일/디렉터리 핸들)
- READ/WRITE: (파일 핸들, offset, count, 데이터) 중심으로 동작한다
- GETATTR/SETATTR: 파일 속성(메타데이터) 조회/설정에 사용한다
LOOKUP은 경로명을 단계적으로 해석해 file handle을 얻는 데 쓰인다
예를 들어 클라이언트가 루트(/) 디렉터리의 핸들을 이미 갖고 있다면, /foo.txt를 열기 위해 루트 핸들과 “foo.txt”를 넘겨 LOOKUP을 하고, 성공 시 foo.txt의 핸들과 속성을 받는다
(루트 핸들은 실제로는 NFS mount 프로토콜로 얻지만 여기서는 생략한다)
READ/WRITE는 반드시 offset과 count를 포함한다
이 덕분에 서버는 “현재 파일 포인터가 어디인지” 같은 상태를 기억하지 않아도, 요청만 보고 정확히 어느 바이트를 읽고/써야 하는지 처리할 수 있다.
6. 프로토콜에서 분산 파일 시스템으로
NFS는 “프로토콜 호출”만으로 끝나지 않고, 클라이언트 쪽에서 POSIX API(open/read/write/close)를 자연스럽게 보이도록 클라이언트 측 파일시스템이 번역 계층 역할을 한다.
클라이언트 측 파일시스템은 열린 파일 목록을 추적하고, 응용프로그램의 시스템콜을 적절한 NFS 요청들로 변환한다.
여기서 중요한 포인트는 “상태는 서버가 아니라 클라이언트가 가진다”는 점이다.
예를 들어 read(fd, …)는 시스템콜 자체로는 offset을 넘기지 않지만, 클라이언트는 fd → file handle 매핑과 현재 파일 포인터(current file pointer)를 로컬에서 들고 있으므로, 이를 바탕으로 (핸들, offset, count) 형태의 READ 요청을 만들어 서버에 보낸다.
경로가 길면 LOOKUP도 여러 번 나가며(/home/remzi/foo.txt면 home, remzi, foo.txt 순서), 각 요청은 서버가 처리하는 데 필요한 정보를 모두 담는다.
7. 서버의 고장을 멱등연산으로 처리하기
여기서부터는 “서버가 크래시하거나 네트워크가 이상해져도 시스템이 계속 굴러가게 만드는 법”을 다룬다. 핵심은 타임아웃 후 재전송을 기본 동작으로 두면, 서버 입장에서는 같은 요청이 중복 도착할 수 있다는 점을 받아들여야 한다는 점이다 따라서 NFS는 가능한 연산을 멱등적(idempotent)으로 만들거나, 멱등적으로 다루기 쉬운 방식(예: 명시적 offset 기반 WRITE)을 선호한다.
8. 성능 개선하기: 클라이언트 측 캐싱
네트워크 왕복이 많아지면 성능이 급격히 나빠지므로, NFS는 성능 개선을 위해 클라이언트 캐싱을 사용한다.
하지만 서버가 무상태이므로 “누가 무엇을 캐시 중인지”를 서버가 모르며, 이는 곧 캐시 일관성 문제로 이어진다.
9. 캐시 일관성 문제
캐싱이 들어오면 “여러 클라이언트가 동시에 접근할 때 같은 내용을 보장하는가”가 문제가 된다.
- Update Visibility 문제: 한 클라이언트가 파일을 수정했을 때, 다른 클라이언트가 “언제부터” 그 변경을 볼 수 있는지 불명확해지는 문제를 말한다
- Stale Cache 문제: 다른 클라이언트가 이미 변경한 뒤에도, 내가 들고 있던 캐시를 계속 쓰면 오래된 데이터를 읽게 되는 문제를 말한다
10. NFS의 캐시 일관성 기법에 대한 평가
NFS는 원래 “접근 전에 검사(validate-before-access)” 같은 방식으로 일관성을 맞추려 하면 GETATTR 요청이 과도하게 늘어나는 문제가 생기며, 이를 완화하려고 attribute cache를 도입했다. 이 attribute cache는 timeout을 두며, 또한 flush-on-close 같은 동작이 일관성 의미에 영향을 준다.
그 결과 NFS가 제공하는 가장 대표적인 보장은 close-to-open consistency로 알려져 있다 즉 한 클라이언트가 파일을 닫을 때(close) 변경 내용을 밀어 넣고, 다른 클라이언트가 파일을 열 때(open) 최신을 보게 되는 수준의 일관성을 기대하게 만든다.
11. 서버 측 쓰기 버퍼링의 의미
클라이언트가 타임아웃 후 재시도하는 모델에서는, 서버가 WRITE에 대해 “성공”을 응답한 뒤 크래시하면 데이터 유실이 생길 수 있다. 그래서 서버는 WRITE 성공 응답을 하기 전에 안정 저장장치에 반영(stable storage에 커밋)해야 하는 압박을 받으며, 이 때문에 서버 측 write buffering(메모리에만 쌓아두는 최적화)을 마음대로 하기 어렵다.
'Deep Dive > OS' 카테고리의 다른 글
| [OSTEP] 스터디 21주차 Security (1) | 2026.01.20 |
|---|---|
| [OSTEP] 스터디 20주차 Andrew File System (AFS) (0) | 2026.01.13 |
| [OSTEP] 스터디 19주차 Part.1 : 분산 시스템 (0) | 2026.01.06 |
| [OSTEP] 스터디 18주차 Flash 기반 SSD (0) | 2025.12.29 |
| [OSTEP] 스터디 17주차 Part.2 Data Integrity and Protection (0) | 2025.12.23 |
