수정 이력(5건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 지식 맵의 「$LogFile은 볼륨 손상을 막는다」는 관계를 「완화한다」로 고쳤습니다. $LogFile 재생으로 복구할 수 있는 것은 메타데이터 작업 중단으로 인한 불일치이며, 배드 섹터나 미디어 고장, $LogFile 자체의 손상으로 인한 손상까지는 복구할 수 없기 때문입니다. 본문의 설명은 바꾸지 않았습니다.
- 기사 서두에 「이 기사의 지식 맵」을 추가했습니다. 본문에서 다루는 개념 사이의 관계를 한 장의 그림으로 조망할 수 있습니다. 관계 전체 목록(근거 URL·확실도·확인일 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 모았고, 기계 가독 데이터를 JSON-LD와 Turtle로 공개합니다.
- 연재 제5회로서 전제가 되는 용어(IRP, 디바이스 스택, 캐시 관리자)를 이전 회 링크와 함께 서두에 정의했습니다. 더불어 resident와 non-resident의 대비 그림, 확인 명령마다 「여기를 봅니다」라는 주석, 두 저널의 대비표, 짧은 이름이 환경에 따라 달라진다는 점을 추가했습니다.
- 최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22175360)
아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.
Go Komura (2026). 「Windows I/O의 심층(제5회) ── NTFS의 내부 구조: MFT로 이해하는 파일 시스템」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/ntfs-internals-mft-structure/
- DOI(등록된 아카이브)
- 10.5281/zenodo.22175360
- DOI(마지막 등록 버전)
- 10.5281/zenodo.22175361
여기까지의 4회에서 I/O 요청이 어떻게 흐르고(제1~3회), 캐시가 어떻게 받아들이는지(제4회)를 살펴봤습니다. 요청은 마지막에 파일 시스템에 도착합니다. 이번에는 그 대표인 NTFS 차례입니다.
시점이 바뀝니다. 지금까지는 「요청의 흐름」이라는 동적인 이야기였습니다. 이번에는 디스크 위에 데이터가 어떻게 놓여 있는지라는 정적인 구조 이야기입니다. 다운로드한 파일에 붙는 보이지 않는 「Zone.Identifier」의 정체. 작은 파일 1만 개의 복사가, 같은 합계 크기의 파일 1개보다 훨씬 느린 이유. 「NTFS는 저널링이니까 안심」이 어디까지 사실인지──전부 이 구조로 설명할 수 있습니다.
연재 「Windows I/O의 심층」의 제5회입니다.
이 회를 읽기 위한 전제: 제1회의 IRP와 디바이스 스택 기본을 알고 있으면 읽기 수월합니다. 다만 단독으로 읽어도 지장이 없도록, 본문에 나오는 이전 회 용어를 먼저 정의해 둡니다.
| 용어 | 한 줄로 말하면 | 자세한 내용 |
|---|---|---|
| IRP(I/O Request Packet) | ReadFile 등의 API 호출이 커널 안에서 변환되는 「I/O 요청 전표」. 드라이버는 이 전표를 받아 처리한다 |
제1회 |
| I/O 관리자와 디바이스 스택 | IRP를 만들고, 목적 디바이스에 이르기까지 쌓인 드라이버(스택)로 차례로 넘겨 가는 커널 구성 요소와, 그 쌓임 | 제1회 |
| 캐시 관리자 | 파일 내용을 메모리에 유지하고, WriteFile 내용을 나중에 모아 디스크에 쓰는 구성 요소. 「쓴 직후에 디스크에 도착해 있다고는 할 수 없다」의 주범 |
제4회 |
표 1: 이 회에서 전제로 하는 이전 회 용어
또 하나, 제1회에서 다룬 cleanup(마지막 핸들이 닫힐 때)과 close(커널 안의 참조가 모두 사라질 때)의 2단계도 4장의 파일 삭제 설명에서 사용합니다.
1. 먼저 결론
- NTFS의 중심은 MFT(마스터 파일 테이블)입니다. 모든 파일이 MFT 안의 레코드로 원장 관리되며, 파일에 관한 모든 정보는 「MFT 엔트리 안」이거나 「엔트리가 가리키는 MFT 밖 영역」에 있습니다(2장).1
- 파일의 실체는 「attribute의 집합」입니다. 작은 파일은 데이터 내용까지 MFT 레코드 안에 들어가고(resident), 큰 파일은 클러스터 열에 대한 참조만 가집니다(non-resident). 작은 파일을 대량 처리할 때 느린 현상은 여기서 설명할 수 있습니다(2장).1
- 데이터는 여러 개를 가질 수 있습니다(다중 데이터 스트림). 평소 데이터는 「이름 없는 스트림」이고,
file.txt:이름으로 추가 스트림을 가질 수 있습니다. Zone.Identifier(Mark of the Web)의 정체입니다(3장).2 - 이름도 attribute입니다. 같은 레코드에 여러 이름을 붙인 것이 하드 링크입니다. 8.3 짧은 이름도 「또 하나의 이름」으로 같이 있습니다(4장).34
- reparse point는 「열면 다른 장소」의 공식 장치입니다. 심볼릭 링크·junction·OneDrive의 Files On-Demand는 모두 이 태그가 붙은 데이터의 응용입니다(5장).56
- 저널은 두 종류입니다.
$LogFile은 메타데이터 정합성 복구용(깨지지 않기 위한 선행 로그), USN 저널은 변경 이력 기록용(무엇이 바뀌었는지의 원장). 역할이 전혀 다릅니다(6장).78 - 「크기」와 「디스크상의 크기」는 별개입니다. 스파스 파일과 압축이 괴리를 만듭니다. 제2회에서 본 「압축 파일은 비동기가 되지 않는다」의 배경도 여기에 있습니다(7장).910
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 35건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. 모든 것은 MFT의 레코드다
2.1. 볼륨의 원장
NTFS 볼륨을 포맷하면 MFT(master file table)와, $로 시작하는 일련의 메타데이터 파일이 만들어집니다. MFT에는 볼륨의 모든 파일에 대해 적어도 하나의 엔트리가 있고, MFT 자신의 엔트리도 포함됩니다. 1
flowchart TB
subgraph VOL["NTFS 볼륨"]
MFT["$MFT ── 마스터 파일 테이블<br/>모든 파일 레코드의 원장(자기 자신도 실려 있다)"]
LOG["$LogFile ── 메타데이터 작업의<br/>트랜잭션 로그(6장)"]
BITMAP["$Bitmap ── 클러스터 사용 상황"]
OTH["$Boot / $Secure / $UpCase 등<br/>그 밖의 메타데이터 파일"]
DATA["사용자 데이터 영역<br/>(non-resident 데이터가 놓이는 곳)"]
end
MFT -->|"레코드가 위치를 가리킨다"| DATA
그림 1: NTFS 볼륨의 구조. 「파일 시스템 자신의 관리 정보도 파일로 가진다」가 NTFS의 설계
파일에 관한 정보──크기, 타임스탬프, 접근 권한, 그리고 데이터 내용까지──는 MFT 엔트리 안에 저장되거나, MFT 엔트리가 위치를 기술하는 MFT 밖 영역에 저장됩니다. 1 파일을 삭제하면 엔트리는 「빈 것」으로 표시되어 재사용되지만, MFT 자체는 줄어들지 않습니다. 또한 MFT를 연속으로 유지하려고 MFT 존이라는 영역이 예약되어 있으며, 볼륨이 차 오면 MFT의 단편화가 시작된다는 수명에 관한 이야기까지 공식 문서에 적혀 있습니다. 1
2.2. 파일 = attribute의 집합, resident와 non-resident
파일 레코드의 내용은 attribute 목록입니다. 표준 정보(타임스탬프 등), 파일 이름, 보안, 그리고 데이터. 여기서 중요한 분기가 있습니다.
flowchart TB
subgraph REC["MFT 파일 레코드(파일 하나의 원장)"]
STD["표준 정보 attribute<br/>타임스탬프·attribute 플래그"]
FN["파일 이름 attribute<br/>(여러 개를 가질 수 있다 ── 4장)"]
DATA["데이터 attribute"]
end
Q{"데이터가 작은가"}
RES["resident<br/>데이터 내용이 레코드 안에 들어간다<br/>읽기는 MFT 접근만으로 끝난다"]
NONRES["non-resident<br/>레코드에는 「클러스터 열에 대한 참조」만<br/>실제 데이터는 사용자 데이터 영역에"]
DATA --> Q
Q -->|"수백 바이트 정도까지"| RES
Q -->|"그 이상"| NONRES
그림 2: 파일 레코드는 attribute의 집합. 데이터가 작으면 레코드 안에 「resident」합니다
같은 레코드의 내용이 resident와 non-resident에서 어떻게 달라지는지를 나란히 보면 이해하기 쉽습니다.
flowchart LR
subgraph RES2["resident ── 작은 파일"]
RA["MFT 파일 레코드(고정 길이)<br/>표준 정보 / 파일 이름 / 보안<br/>─────────────<br/>데이터 attribute = 내용 그 자체<br/>『설정값=1』이 여기에 직접 들어간다"]
RB["디스크상에 다른 저장 위치는 없다<br/>읽기는 MFT 접근만으로 끝난다"]
RA --> RB
end
subgraph NON2["non-resident ── 큰 파일"]
NA["MFT 파일 레코드(고정 길이)<br/>표준 정보 / 파일 이름 / 보안<br/>─────────────<br/>데이터 attribute = 데이터 런 표<br/>『어디서부터 몇 클러스터』의 나열"]
NB["사용자 데이터 영역<br/>런 1: 연속 클러스터"]
NC["사용자 데이터 영역<br/>런 2: 다른 장소의 연속 클러스터"]
NA -->|"위치를 가리킨다"| NB
NA -->|"위치를 가리킨다"| NC
end
그림 3: resident와 non-resident의 대비. non-resident에서 레코드가 갖는 것은 「실제 데이터가 어디에 몇 개 있는지」의 표(데이터 런)뿐입니다
데이터 런 수가 늘수록, 파일 하나를 읽기 위해 흩어진 영역을 건너다니게 됩니다. 이것이 다음에 나오는 단편화의 정체입니다.
이 구조에서 현장에서 만나는 현상을 여러 개 설명할 수 있습니다.
- 작은 파일 1만 개의 복사가 느린 이유. 파일마다 MFT 레코드 작성·이름 등록·보안 설정이라는 메타데이터 작업이 발생합니다. 데이터 전송 자체보다 원장 작업이 지배적이 됩니다(그리고 그 하나하나가 제6회에서 볼 필터의 검사 대상이 되기도 합니다).
- 단편화의 정체. non-resident 데이터는 「클러스터의 연속 구간(런)의 열」로 기록됩니다. 연속 영역을 잡지 못하면 런 수가 늘고, 읽기에 필요한 시크가 늘어납니다──이것이 단편화입니다. 런의 실제 나열은
fsutil file layout으로 들여다볼 수 있습니다. - 「폴더」도 특별하지 않다. 디렉터리는 「파일 이름에서 MFT 레코드 번호로의 색인(index)을 가진 파일」입니다. 원장 위에서는 모두가 같은 구조 위에 올라 있습니다.
3. 데이터는 「스트림」의 하나에 불과하다
3.1. 하나의 파일, 여러 바이트열
NTFS에서는 하나의 파일이 여러 데이터 스트림을 가질 수 있습니다. 평소 ReadFile/WriteFile로 읽고 쓰는 것은 이름 없는 기본 스트림이고, 파일명:스트림명 구문으로 대체 데이터 스트림(ADS)을 만들 수 있습니다. 2
flowchart LR
subgraph F["report.docx라는 파일(하나의 MFT 레코드)"]
D0["기본 스트림(이름 없음)<br/>= 평소 보는 내용"]
D1[":Zone.Identifier<br/>출처 정보(Mark of the Web)"]
D2[":임의의 이름<br/>앱 고유의 부가 정보"]
end
그림 4: 다중 데이터 스트림. 탐색기 크기 표시에 나오는 것은 기본 스트림뿐입니다
가장 익숙한 ADS가 Zone.Identifier입니다. 브라우저로 다운로드한 파일에는 이 스트림에 출처(인터넷에서 왔다 등)가 기록되고, SmartScreen의 「Windows에서 PC를 보호했습니다」나 Office 보호된 보기의 판단 근거가 됩니다. 이 구조의 겉으로 보이는 동작은 「Windows에서 「Windows에서 PC를 보호했습니다」가 나오는 이유」에서 다뤘습니다──이면의 정체는 그저 NTFS 스트림이었던 셈입니다.
3.2. 개발자가 빠지는 함정
- 보이지 않는다. 탐색기의 크기에도
dir목록에도 나오지 않습니다.dir /r이나 Sysinternals의streams로 확인할 수 있습니다. 11 - 옮기지 못한다. ADS는 NTFS 기능이므로, FAT USB 메모리나 클라우드 스토리지를 거친 복사에서는 잃기 쉽습니다. 「다운로드 경고가 복사하니 사라졌다」가 이것입니다.
- 자기 앱에서도 열 수 있다.
CreateFile("data.txt:meta", ...)처럼 경로에 콜론을 넣기만 하면 읽고 쓸 수 있습니다. 2 편리하지만, 앞 항의 「옮기지 못한다」는 성질까지 떠안게 되므로, 업무 데이터의 본문을 넣을 자리는 아닙니다.
4. 이름 또한 attribute다 ── 하드 링크와 8.3 이름
4.1. 하드 링크 ── 같은 레코드에 대한 여러 이름
그림 2에서 「파일 이름 attribute는 여러 개를 가질 수 있다」고 썼습니다. 동일 볼륨 안에서, 여러 경로가 단일 파일을 참조한다──이것이 하드 링크입니다(CreateHardLink / mklink /H). 3
flowchart TB
subgraph DIR1["C:\app\ 의 인덱스"]
E1["config.json → 레코드#1234"]
end
subgraph DIR2["C:\backup\ 의 인덱스"]
E2["config-link.json → 레코드#1234"]
end
REC["MFT 레코드#1234<br/>데이터 내용(또는 런에 대한 참조)<br/>링크 수: 2"]
E1 --> REC
E2 --> REC
그림 5: 하드 링크. 디렉터리의 색인이 같은 MFT 레코드를 가리킬 뿐이고, 어느 쪽도 「진짜」입니다
어느 이름으로 변경해도 같은 파일이므로 내용은 즉시 일치합니다. 3 그리고 「삭제」의 의미가 달라집니다──DeleteFile은 「이름을 하나 제거한다」이며, 마지막 이름이 제거되고, 열린 핸들이 닫히고, 나아가 메모리 맵 섹션 등 커널 안의 참조도 모두 사라진 때에 비로소 실체가 사라집니다. 제1회에서 본 cleanup(마지막 핸들)과 close(마지막 참조)의 2단계가, 삭제 수명에도 그대로 작용하는 것입니다. 또한 attribute 표시에는 버릇이 있어, 어떤 링크를 통해 attribute를 바꿔도 다른 링크의 겉보기 표시가 그대로인 동작이 공식 문서에 적혀 있습니다. 3
4.2. 8.3 이름 ── 또 하나의 숨은 이름
역사적 호환을 위해 NTFS는 긴 파일 이름에 대해 REPORT~1.DOC와 같은 8.3 형식의 짧은 이름을 자동 생성할 수 있습니다. 이것도 「또 하나의 이름」으로 레코드에 같이 있습니다. 파일이 많은 폴더에서는 짧은 이름 생성·충돌 회피가 비용이 되므로, fsutil 8dot3name으로 생성을 끄거나 기존 짧은 이름을 제거할 수 있습니다(레지스트리 경로를 짧은 이름으로 기록한 옛 앱이 있으면 깨지므로, strip 전의 검사 기능이 있는 것도 실무적인 포인트입니다). 4
여기서 주의할 점은 짧은 이름이 있는지는 환경에 따라 다르다는 것입니다. 기본 동작은 레지스트리 값 NtfsDisable8dot3NameCreation으로 정해지며, 0(모든 볼륨에서 생성한다), 1(모든 볼륨에서 생성하지 않는다), 2(볼륨 단위로 설정한다), 3(시스템 볼륨 이외에서는 생성하지 않는다)의 네 가지가 있습니다. 4 2를 고르면 볼륨 단위로 전환할 수 있으므로, 「Windows이면 PROGRA~1 같은 짧은 이름이 반드시 존재한다」고는 할 수 없습니다. 짧은 이름에 의존하는 코드나 절차를 쓰기 전에, fsutil 8dot3name query C:(볼륨을 생략하면 모든 볼륨 공통의 기본 설정)로 현재 상태를 확인하세요.
경로와 이름을 둘러싼 함정(MAX_PATH, 예약 이름, 끝의 점)은 「MAX_PATH와 Windows의 경로·파일 이름 함정」에서 자세히 다룹니다. 제1회의 이름 해석(오브젝트 관리자)과 이 장(파일 시스템 안의 이름)을 합치면, Windows의 「이름」 전체 그림이 됩니다.
5. reparse point ── 「열면 다른 장소」의 장치
파일이나 디렉터리에는 reparse point를 붙일 수 있습니다. 실체는 「태그 + 사용자 정의 데이터」라는 attribute입니다. 파일 시스템이 reparse point가 붙은 파일을 열면, 태그에 따라 처리가 가로채집니다──태그를 이해하는 필터 드라이버가 처리를 맡거나, 이름 재지정 계열 태그이면 링크 대상 경로로 해석이 다시 이뤄집니다. 5
sequenceDiagram
participant App as 앱
participant IOM as I/O 관리자
participant FS as NTFS
App->>IOM: CreateFile("C:\data\link.txt")
IOM->>FS: IRP_MJ_CREATE(제1회의 세계)
Note over FS: 대상에서 reparse point 발견<br/>태그와 데이터를 반환
alt 심볼릭 링크/junction(이름 재지정)
FS-->>IOM: 「진짜 장소는 이쪽」
IOM->>FS: 링크 대상 경로로 해석을 다시 한다
else 필터가 관리하는 태그(클라우드 파일 등)
Note over FS: 태그를 이해하는 필터가<br/>처리를 맡는다(제6회)
end
그림 6: reparse point의 해석. 「연다」는 조작에 끼어드는 공식 훅이 됩니다
이 하나의 장치 위에, 익숙한 기능이 늘어서 있습니다.
- 심볼릭 링크(
mklink)──링크 대상 경로를 유지하는 표지. 다른 볼륨이나 UNC 경로도 가리킬 수 있습니다. 6 - junction/마운트 지점──디렉터리를 다른 로컬 볼륨의 위치로 잇는 오래된 구조. 3
- OneDrive의 Files On-Demand──실제 데이터가 로컬에 없는 파일을 reparse point로 표현하고, 열리는 순간에 필터가 다운로드해 내용을 내놓습니다. 「탐색기에는 보이는데, 열면 통신이 일어난다」의 정체입니다(필터 구조 자체는 제6회에서).
실무 주의는 하나입니다. 「경로가 가리키는 곳이 정말로 로컬의 그 장소라고는 할 수 없다」는 것입니다. 재귀적으로 트리를 따라가는 도구가 junction에서 루프한다, 크기 집계가 이중이 된다, 백업이 클라우드의 실체화를 대량으로 일으킨다──reparse point의 존재를 모르는 코드는 이것을 밟습니다. FindFirstFile 계열의 attribute FILE_ATTRIBUTE_REPARSE_POINT 확인이 대책의 입구입니다. 5
6. 두 종류의 저널 ── $LogFile과 USN
「NTFS는 저널링 파일 시스템」이라고 자주 말하지만, NTFS에는 역할이 다른 두 종류의 저널이 있습니다. 섞으면 보장을 오독합니다.
flowchart TB
subgraph J1["$LogFile ── 선행 로그(깨지지 않기 위해)"]
A1["메타데이터 작업(레코드 갱신·이름 변경 등)을<br/>실행 전에 로그에 기록"]
A2["시스템 장애 후 다음 부팅 때<br/>로그를 재생해 구조의 정합성을 복구"]
A1 --> A2
end
subgraph J2["USN 저널 ── 변경 이력(무엇이 바뀌었는지를 알기 위해)"]
B1["파일/디렉터리 변경마다<br/>변경 내용과 이름을 기록"]
B2["백업·검색 인덱스·동기화 도구가<br/>「지난번부터 무엇이 바뀌었는지」를 전체 스캔 없이 파악"]
B1 --> B2
end
그림 7: 두 종류의 저널. $LogFile은 「깨지지 않게」 하기 위해, USN은 「변경을 알기」 위해
차이를 표로 정리하면 다음과 같습니다.
| 관점 | $LogFile(트랜잭션 로그) |
USN 저널(변경 저널) |
|---|---|---|
| 목적 | 장애 후 파일 시스템의 구조를 정합 상태로 되돌린다7 | 「지난번부터 무엇이 바뀌었는지」를 나중에 안다8 |
| 기록하는 내용 | 메타데이터 작업(레코드 갱신·이름 변경 등)의 선행 로그. 파일 내용은 대상 밖 | 변경마다, 변경 내용과 대상 파일/디렉터리의 이름8 |
| 쓰는 주체 | NTFS 자신. 다음 마운트 때의 자동 복구에 사용 | 백업·검색 인덱스·동기화 도구 등의 앱 |
| 어디까지 거슬러 올라가는가 | 복구에 필요한 범위만. 고정 크기를 돌려 쓰므로, 과거 이력을 따라가는 용도에는 쓰지 못한다 | 목표 최대 크기(MaximumSize)를 넘으면 체크포인트 때 오래된 레코드부터 잘린다. 거슬러 올라갈 수 있는 범위는 크기 설정과 볼륨 갱신량에 따른다12 |
| 참조 방법 | 내용을 읽는 공식 수단은 없다(크기는 chkdsk /L로 확인할 수 있다) |
fsutil usn queryjournal로 상태, fsutil usn readjournal로 내용. 프로그램에서는 FSCTL_QUERY_USN_JOURNAL / FSCTL_READ_USN_JOURNAL12 |
| 멈출 수 있는가 | 멈출 수 없다(NTFS의 일부) | 관리자가 삭제·비활성화할 수 있다. 다만 이용 중인 서비스에 전체 스캔을 강요하므로 영향은 크다12 |
표 2: 두 종류의 저널의 대비
$LogFile(트랜잭션 로그)은 메타데이터 작업의 선행 로그입니다. 시스템 장애가 나도 NTFS는 다음 부팅 때 이 로그와 체크포인트 정보에서 파일 시스템의 정합성을 자동 복구합니다. 7 여기서 지켜지는 것은 구조입니다. 제4회에서 본 대로, 캐시 위의 더티한 데이터 내용은 전원 차단으로 사라질 수 있습니다──「볼륨은 깨지지 않는다. 그러나 마지막 쓰기는 사라질 수 있다」가 올바른 읽기입니다.- USN 저널(변경 저널)은 볼륨 안의 파일이나 디렉터리에 변경이 생길 때마다 변경 내용과 대상 이름을 기록해 가는 원장입니다. 8 백업이나 인덱서가 「지난번부터 바뀐 것만」을 전체 스캔 없이 골라내기 위한 구조이며, 장애 후 인덱스 재구축을 피하는 데에도 쓰입니다. 8 실무에서는
FileSystemWatcher의 놓침(「FileSystemWatcher 실무 가이드」)을 보완하는 대조 원장으로fsutil usn readjournal을 조사에 쓸 수 있다는 점을 기억해 두면 도움이 됩니다.
7. 스파스와 압축 ── 「크기」가 둘인 이야기
NTFS에서는 파일의 논리적인 길이와 실제로 할당된 영역이 따로 관리됩니다. 속성 창의 「크기」와 「디스크상의 크기」입니다. 괴리를 만드는 대표가 둘 있습니다.
스파스 파일은 0이 이어지는 구간에 실제 영역을 할당하지 않고 「구멍」으로 관리합니다. 9 논리 크기 42GB인 가상 디스크 파일이 디스크상에서는 500MB만 쓰고 있다──가 흔히 일어납니다. 구멍을 읽으면 0이 돌아오고, 쓰면 그만큼만 할당됩니다.
flowchart LR
subgraph L["논리적인 파일(크기: 1GB)"]
R1["데이터 10MB"]
H1["구멍(0) 500MB"]
R2["데이터 5MB"]
H2["구멍(0) 나머지"]
end
subgraph P["디스크상의 할당(15MB+관리 정보)"]
A1["런: R1의 실체"]
A2["런: R2의 실체"]
end
R1 --> A1
R2 --> A2
그림 8: 스파스 파일. 「구멍」에는 할당이 없고, 논리 크기와 디스크상의 크기가 벌어진다
NTFS 압축은 데이터를 압축 유닛 단위로 압축해 저장합니다. 10 투명해서 편리하지만, 비용도 투명하지는 않습니다──읽고 쓸 때마다 압축 해제·재압축이 돌고, 단편화도 진행되기 쉽습니다. 그리고 제2회 5장에서 본 대로, 압축 파일에 대한 접근은 비동기가 되지 않습니다(파일 시스템이 동기로 변환합니다). 「비동기 I/O로 바꿨는데 빨라지지 않는 파일이 있다」일 때 의심할 곳 중 하나입니다.
할당 기준의 실제 크기는 GetCompressedFileSize로 가져올 수 있습니다. 「파일 크기 합계」와 「디스크 사용량」이 맞지 않는 조사에서는 스파스·압축·ADS(3장)·클러스터 올림의 네 가지를 차례로 의심하는 것이 정석입니다.
8. 자기 눈으로 확인한다
이번에도 자신의 Windows에서 모두 관찰할 수 있습니다(일부는 관리자 권한이 필요합니다). 실행 결과가 맞는지를 스스로 판정할 수 있도록, 명령마다 어디를 보면 무엇이 보이는지를 붙입니다.
:: 대체 데이터 스트림을 봅니다
dir /r C:\Users\%USERNAME%\Downloads
여기를 봅니다: 보통 파일 행 아래에, 들여 쓴 파일명:Zone.Identifier:$DATA 형태의 행이 길이와 함께 늘어섭니다. 이 행이 있으면 그 파일에 Mark of the Web이 붙은 상태입니다(3장). 브라우저로 다운로드한 파일에는 붙고, 직접 만든 파일에는 붙지 않습니다. 양쪽 위치에서 실행해 비교하면 ADS의 유무가 분명해집니다.
:: 파일의 MFT상 배치(런)나 attribute를 봅니다
fsutil file layout C:\path\to\file.dat
:: 익스텐트만 봅니다(공식 문서에 기재된 하위 명령)
fsutil file queryextents C:\path\to\file.dat
여기를 봅니다: layout은 스트림마다 크기·할당 크기와, non-resident이면 익스텐트(VCN·LCN·클러스터 수의 쌍) 목록을 늘어놓습니다. 익스텐트 행이 나오지 않는 아주 작은 파일은 resident(2.2절), 여러 행으로 나뉘어 있으면 단편화되어 있습니다. 몇 바이트짜리 텍스트 파일과 수백 MB 파일로 실행해 비교하는 것이 resident/non-resident를 가장 짧게 체감하는 방법입니다.
:: 8.3 짧은 이름의 생성 설정과, 기존 짧은 이름
fsutil 8dot3name query C:
dir /x
여기를 봅니다: query는 그 볼륨에서 짧은 이름 생성이 켜져 있는지 꺼져 있는지를 반환합니다(볼륨을 생략하면 모든 볼륨 공통의 기본 설정). 4 dir /x는 긴 이름 옆에 짧은 이름 열을 표시하므로, 열이 비어 있으면 짧은 이름이 만들어지지 않은 것입니다. 4.2절의 「짧은 이름이 있다고는 할 수 없다」를 자기 환경에서 확인할 수 있습니다.
:: USN 저널의 상태
fsutil usn queryjournal C:
여기를 봅니다: 저널 ID, 유효한 USN 범위(First USN / Next USN), 목표 최대 크기(MaximumSize)와 할당 단위(AllocationDelta)가 표시됩니다. 12 파일을 하나 만든 뒤 다시 실행하면 Next USN이 진행되어 있어야 하고, 이것이 「변경이 기록되고 있다」는 확인이 됩니다. MaximumSize는 6장에서 다룬 「어디까지 거슬러 올라가는가」의 가늠입니다. 저널이 꺼진 볼륨에서는 오류가 납니다.
:: reparse point 확인(링크 대상과 태그)
dir /aL C:\Users\%USERNAME%
fsutil reparsepoint query "C:\Users\%USERNAME%\OneDrive"
여기를 봅니다: dir /aL에 나온 것이 reparse point(FILE_ATTRIBUTE_REPARSE_POINT가 설정된 것)입니다. 심볼릭 링크나 junction은 <SYMLINKD> <JUNCTION>처럼 종류가 붙어 표시됩니다. fsutil reparsepoint query는 reparse 태그 값과, 이름 재지정 계열이면 링크 대상 경로를 표시합니다. reparse point가 아닌 대상을 지정하면 오류가 나므로, 오류가 난다는 것 자체가 「여기는 보통 폴더」라는 확인이 됩니다.
Procmon으로 파일 조작을 따라가면, 이번 등장 인물이 실명으로 흘러갑니다($LogFile에 대한 쓰기, 스트림 이름이 붙은 경로, reparse 처리). 사용법은 「Process Monitor(ProcMon) 실천 가이드」를 참고하세요.
9. 정리
- NTFS의 중심은 MFT. 모든 파일이 원장의 레코드이고, 정보는 「레코드 안」이거나 「레코드가 가리키는 외부 영역」에 있습니다. 작은 데이터는 resident, 큰 데이터는 런 참조이며, 작은 파일을 대량 처리할 때의 느림과 단편화는 이 구조의 결과입니다. 1
- 데이터 스트림은 여러 개를 가질 수 있습니다. Zone.Identifier(Mark of the Web)는 그저 ADS이고,
dir /r로 보이며, NTFS 밖으로는 옮겨지지 않습니다. 211 - 이름은 attribute이며, 여러 개를 가질 수 있습니다. 하드 링크는 같은 레코드에 대한 동등한 이름, 8.3 이름은 호환용 또 하나의 이름입니다. 「삭제 = 이름을 제거한다」이며, 실체의 소멸은 마지막 이름·핸들·커널 안 참조(매핑된 섹션 등)가 모두 없어진 때입니다. 34
- reparse point는 「연다」에 대한 공식 훅이며, 심볼릭 링크도 junction도 Files On-Demand도 이 응용입니다. 트리를 따라가는 코드는
FILE_ATTRIBUTE_REPARSE_POINT를 의식해야 합니다. 56 - 저널은 두 종류입니다.
$LogFile은 구조의 정합성 복구(깨지지 않음), USN은 변경 이력(무엇이 바뀌었는지). 「저널링이니까 데이터도 안전하다」가 아닙니다──데이터의 내구성은 제4회의 도구로 구현합니다. 78 - 논리 크기와 할당은 별개. 스파스·압축·ADS·클러스터 올림이 「크기가 맞지 않는다」의 네 가지 요인입니다. 압축 파일이 비동기 I/O가 되지 않는 건까지 포함해, 성능 조사의 단서가 됩니다. 910
이어지는 내용은 최종회, 제6회 「필터 드라이버와 미니필터 ── Procmon과 바이러스 스캔이 I/O에 끼어들 수 있는 이유」입니다. 제1회부터 틈틈이 등장해 온 「사이에 끼는 자들」──바이러스 대책, Procmon, OneDrive, 암호화──가 어떻게 I/O에 끼어드는지. 연재의 마무리로서, 디바이스 스택의 틈에 선 거주자들의 정체를 밝힙니다.
관련 기사
- Windows I/O의 심층(제1회) ── 모든 읽기 쓰기는 IRP가 된다: I/O 시스템의 전체 그림
- Windows I/O의 심층(제2회) ── 동기 I/O와 비동기 I/O: OVERLAPPED의 진짜 의미
- Windows I/O의 심층(제4회) ── 캐시 관리자: 당신의 WriteFile은 언제 디스크에 도착하는가
- Windows에서 「Windows에서 PC를 보호했습니다」가 나오는 이유
- MAX_PATH와 Windows의 경로·파일 이름 함정 ── 260자 제한, 예약 이름, 끝의 점, 대소문자
- FileSystemWatcher 실무 가이드 - 놓침과 중복 대책
- 네트워크 드라이브와 UNC 경로의 함정 ── 업무 앱에서 파일 서버(공유 폴더)를 다루는 실무
- Process Monitor(ProcMon) 실천 가이드 ── 「설정이 읽히지 않는다」「ACCESS DENIED」를 10분에 특정한다
관련 상담 영역
주식회사 고무라소프트에서는 파일 크기나 복사 성능의 원인 모를 동작, 링크·스트림이 얽힌 장애 등, NTFS 구조에 뿌리내린 Windows 업무 앱의 설계·조사를 다룹니다.
참고 링크
-
Microsoft Learn, Master File Table. NTFS 볼륨의 모든 파일에 대해 MFT에 적어도 하나의 엔트리가 있고, MFT 자신의 엔트리도 포함된다는 것, 파일의 크기·타임스탬프·접근 권한·데이터 내용을 포함한 모든 정보가 MFT 엔트리 안이거나 MFT 엔트리가 위치를 기술하는 MFT 밖 영역에 저장된다는 것, 파일 삭제 시 엔트리가 빈 것으로 표시되어 재사용되지만 MFT 크기는 줄어들지 않는다는 것, MFT를 연속으로 유지하기 위한 MFT 존이 예약된다는 것, 할당이 진행되면 MFT의 단편화가 일어난다는 것에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, File Streams. NTFS의 파일 데이터가 하나 이상의 스트림으로 저장된다는 것, 기본(이름 없는) 데이터 스트림과 이름이 있는 대체 데이터 스트림이 있다는 것, 「파일명:스트림명」 형식으로 스트림을 지정해 CreateFile로 열 수 있다는 것에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Hard links and junctions. 하드 링크가 동일 볼륨 안에서 여러 경로가 단일 파일을 참조하는 파일 시스템상의 표현이라는 것, CreateHardLink로 만든다는 것, 어느 링크를 통한 변경도 다른 링크에서 즉시 보인다는 것, attribute 변경은 모든 하드 링크에 전파되는 한편 디렉터리 엔트리상의 표시는 변경을 한 링크에서만 갱신된다는 표시상의 버릇이 있다는 것, 그리고 junction(디렉터리를 다른 로컬 볼륨으로 잇는 구조)에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, fsutil 8dot3name. NTFS가 긴 파일 이름에 대해 8.3 형식의 짧은 이름을 생성할 수 있다는 것, fsutil 8dot3name으로 짧은 이름 생성의 사용/사용 안 함 조회와 설정, 기존 짧은 이름 제거(strip), 제거했을 때 영향을 받는 레지스트리 참조를 검사할 수 있다는 것에 대해. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Reparse points. reparse point가 사용자 정의 데이터와 그 데이터 형식을 고유하게 식별하는 reparse 태그의 모임이라는 것, reparse point가 붙은 파일을 열 때 파일 시스템이 태그에 대응하는 처리(태그를 해석하는 파일 시스템 필터에 의한 처리)를 시도한다는 것, NTFS의 파일 시스템 링크나 원격 스토리지(계층 저장) 구현에 쓰인다는 것, FILE_ATTRIBUTE_REPARSE_POINT attribute로 존재를 확인할 수 있다는 것에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Symbolic links. 심볼릭 링크가 다른 파일이나 디렉터리를 가리키는 파일 시스템 객체이며, 링크 대상으로의 투명한 리디렉션으로 동작한다는 것, 절대·상대 링크가 있고, 볼륨을 넘는 참조나 원격 경로에 대한 참조가 가능하다는 것에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, NTFS overview. NTFS가 로그 파일과 체크포인트 정보를 써서, 시스템 장애가 난 경우 다음 부팅 때 트랜잭션 로그를 재생해 파일 시스템 정합성을 자동으로 복구한다는 것, 배드 섹터의 동적 재매핑이나, 백그라운드에서 경미한 손상을 고치는 self-healing NTFS를 갖춘다는 것에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Change Journals. 볼륨 안의 파일이나 디렉터리에 변경이 가해질 때마다, 그 볼륨의 USN 변경 저널에 변경 내용과 대상 파일/디렉터리 이름이 기록된다는 것, 볼륨마다 저널이 유지된다는 것, 장애 후 파일 시스템 인덱스 복구에 쓰이며 볼륨 전체 재인덱싱을 피할 수 있다는 것에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Sparse Files. 스파스 파일에서 0으로 구성된 큰 구간에 물리적 디스크 영역을 할당하지 않고, 데이터가 있는 부분에만 영역을 할당한다는 것, 할당이 없는 범위를 읽으면 0이 돌아온다는 것에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, File Compression and Decompression. NTFS의 파일 압축이 투명하게 이뤄지고, 압축 단위마다 데이터가 압축·저장된다는 것, GetCompressedFileSize로 압축(실제 할당) 후 크기를 가져올 수 있다는 것, 압축 파일의 읽기 쓰기에 압축 해제·재압축 비용이 따른다는 것에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, Streams - Sysinternals. Sysinternals의 streams 유틸리티가 NTFS 파일의 대체 데이터 스트림을 열거·삭제할 수 있다는 것에 대해. ↩ ↩2
-
Microsoft Learn, Creating, Modifying, and Deleting a Change Journal 및 fsutil usn. 변경 저널의 MaximumSize가 목표값이며, 크기가 MaximumSize와 AllocationDelta의 합을 넘으면 NTFS 체크포인트 때 잘린다는 것, AllocationDelta가 저널 끝에 대한 추가와 앞에서부터의 삭제의 단위라는 것, fsutil usn queryjournal로 저널 상태와 용량을, readjournal로 기록 내용을 참조할 수 있다는 것, 프로그램에서는 FSCTL_CREATE_USN_JOURNAL / FSCTL_QUERY_USN_JOURNAL / FSCTL_READ_USN_JOURNAL / FSCTL_DELETE_USN_JOURNAL을 쓴다는 것, 활성 저널의 삭제·비활성화가 MFT 전체 스캔을 수반하고, 저널을 이용하는 서비스에 볼륨 재스캔을 강요한다는 것에 대해. ↩ ↩2 ↩3 ↩4
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows I/O의 심층(제4회) ── 캐시 관리자: WriteFile은 언제 디스크에 도달하는가
Windows의 캐시 관리자를 그림으로 설명하는 연재 제4회입니다. 파일 매핑으로 구현된 캐시, read-ahead와 lazy write, FlushFileBuffers와 FILE_FLAG_NO_BUFFERING의 용도 구분, 전원 차단으로 데이...
Windows I/O 내부 구조(제1회) ── 모든 읽기·쓰기는 IRP가 된다: I/O 시스템의 전체 그림
Windows I/O 시스템을 밑바닥부터 설명하는 연재의 제1회입니다. Object Manager namespace, driver·device·file 세 가지 object, IRP 수명 주기, CloseHandle 뒤에서 일어나는 일까지를 그림...
Windows I/O의 심층(제2회) ── 동기 I/O와 비동기 I/O: OVERLAPPED의 진짜 의미
Windows의 동기 I/O와 비동기 I/O(overlapped I/O)를 그림으로 설명하는 연재의 제2회입니다. FILE_FLAG_OVERLAPPED의 의미, 완료 통지의 네 가지 방식, 비동기인데도 동기 완료되는 조건, 취소 절차, .NET과...
OneDrive 「파일 온디맨드」와 업무 앱 ── 플레이스홀더가 깨뜨리는 전제와 대책
데스크톱 CSV가 읽히지 않거나 가져오기가 「파일을 찾을 수 없습니다」로 실패한다——원인은 OneDrive의 KFM과 파일 온디맨드일 수 있습니다. 플레이스홀더 구조, 속성 판정, 앱과 정보시스템 양쪽의 대책을 설명합니다.
볼륨 섀도 복사본(VSS)의 원리와 실무 ── 사용 중인 파일은 어떻게 백업되는가
사용 중인 파일은 공유 위반으로 복사하지 못하는데, 백업 소프트웨어는 어떻게 복사할까요. 볼륨 섀도 복사본(VSS)의 requester·writer·provider 역할, copy-on-write 동작, vssadmin 실무와 diff area의...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
장애 조사 & 장기 가동 장애
간헐적 장애, 통신 진단, 장기 가동 크래시, 실패 경로 테스트 기반을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
장애 조사 & 원인 분석
간헐적 장애, 장기 가동 중 크래시, 누수, 통신 중단 등 까다로운 프로덕션 이슈를 조사합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- MFT(마스터 파일 테이블)란 무엇인가요?
- NTFS 볼륨의 중심에 해당하는 데이터 구조로, 볼륨의 모든 파일에 대해 적어도 하나의 엔트리(파일 레코드)를 갖는 원장입니다. MFT 자신의 엔트리도 포함됩니다. 파일의 크기, 타임스탬프, 접근 권한, 그리고 데이터 내용에 이르기까지 파일에 관한 모든 정보는 MFT 엔트리 안이거나, MFT 엔트리가 가리키는 MFT 밖 영역에 저장됩니다. 작은 파일이면 데이터 내용까지 MFT 엔트리 안에 들어가고(resident), 큰 파일은 데이터가 놓인 위치(클러스터 나열)에 대한 참조만 엔트리에 기록됩니다(non-resident). 파일을 삭제하면 엔트리는 빈 것으로 표시되어 재사용되지만, MFT 자체의 크기는 줄어들지 않습니다.
- 파일에 붙어 있는 「Zone.Identifier」라는 보이지 않는 데이터는 무엇인가요?
- NTFS의 다중 데이터 스트림(대체 데이터 스트림) 중 하나입니다. NTFS에서는 하나의 파일이 여러 바이트열(스트림)을 가질 수 있고, 평소 읽고 쓰는 것은 이름 없는 기본 스트림입니다. 「file.txt:Zone.Identifier」처럼 콜론으로 구분하여 지정하는 추가 스트림에, Windows는 파일의 출처(인터넷에서 다운로드했다 등)를 기록합니다. 이것이 이른바 「Mark of the Web」이며, SmartScreen 경고나 Office 보호된 보기 판단 근거가 됩니다. 대체 스트림은 탐색기의 크기 표시에 나타나지 않으며, dir /r 명령이나 Sysinternals의 streams 도구로 확인할 수 있습니다. NTFS가 아닌 파일 시스템(FAT 등)으로 복사하면 유지되지 않는다는 점에도 주의가 필요합니다.
- 하드 링크와 심볼릭 링크는 무엇이 다른가요?
- 하드 링크는 「같은 파일 실체(같은 MFT 레코드)를 가리키는, 동등한 이름이 하나 더 늘어나는」 것입니다. 동일 볼륨 안에서만 만들 수 있고, 어느 이름으로 접근해도 같은 파일이며, 이름 하나를 삭제해도 다른 이름이 남아 있으면 파일은 사라지지 않습니다. 심볼릭 링크는 「다른 경로로 안내하는 표지」이며, reparse point로 구현됩니다. 링크 대상의 경로 문자열만 가지고 있으므로 다른 볼륨이나 원격도 가리킬 수 있지만, 링크 대상이 사라지면 막다른 길이 됩니다. 실무에서는 하드 링크는 실체 공유(삭제의 의미가 달라짐), 심볼릭 링크는 경로 재지정(이전이나 리디렉션)로 나눠 쓰는 것이 기본입니다.
- NTFS는 저널링 파일 시스템이니까, 전원 차단이 있어도 데이터는 사라지지 않나요?
- 지켜지는 범위를 정확히 이해해야 합니다. NTFS의 트랜잭션 로그($LogFile)가 지키는 것은 파일 시스템 구조(메타데이터)의 정합성입니다. 시스템 장애가 나도 다음 부팅 때 로그를 써서 정합성을 자동 복구하고, 「볼륨이 깨져서 읽을 수 없다」는 사태를 막습니다. 그러나 쓰는 중이던 파일의 데이터 내용 자체가 복원되는 것은 아닙니다. 연재 제4회에서 본 대로, 캐시 위의 더티 데이터는 전원 차단으로 사라집니다. 즉 「볼륨은 깨지지 않지만, 마지막 쓰기 내용은 사라질 수 있다」가 올바른 이해이며, 데이터 자체의 내구성이 필요하면 FlushFileBuffers나 WRITE_THROUGH, 혹은 앱 쪽 쓰기 설계(임시 파일+rename 등)로 구현해야 합니다.
- 파일의 「크기」와 「디스크상의 크기」가 다른 이유는 무엇인가요?
- NTFS에서는 파일의 논리적인 길이와, 실제로 할당된 디스크 영역이 따로 관리되기 때문입니다. 보통 파일에서도 클러스터 단위(기본 4KB)로 올림 할당하므로 차이가 나지만, 크게 벌어지는 것은 스파스 파일과 압축 파일입니다. 스파스 파일에서는 0이 이어지는 구간에 실제 영역을 할당하지 않고 「구멍」으로 관리하므로, 논리 크기가 수 GB여도 디스크상은 수 MB인 일이 일어납니다. 압축 파일에서는 압축 후 크기만 할당됩니다. 반대로 「디스크상의 크기」 쪽이 더 크게 보이는 경우는 클러스터 올림이나 대체 데이터 스트림이 원인일 수 있습니다.