更新履歴(3件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 知識マップの「$LogFileはボリューム破損を防ぐ」という関係を、「軽減する」に改めました。$LogFileの再生で回復できるのはメタデータ操作の中断による不整合で、不良セクタやメディア故障、$LogFile自体の損傷による破損までは回復できないためです。本文の説明は変えていません。
- 記事の冒頭に「この記事の知識マップ」を追加しました。本文で扱う概念どうしの関係を一枚の図で見渡せます。関係の全一覧(根拠のURL・確度・確認日つき)と主要概念の定義は知識マップ詳細ページにまとめ、機械可読データをJSON-LDとTurtleで公開しています。
- 連載第5回として前提になる用語(IRP、デバイススタック、キャッシュマネージャー)を、前回へのリンク付きで冒頭に定義しました。あわせて常駐と非常駐の対比図、確認コマンドごとの「ここを見る」注釈、2つのジャーナルの対比表、短縮名が環境依存である旨を追加しています。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21739476)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「Windows I/Oの深層(第5回) ── NTFSの内部構造:MFTから理解するファイルシステム」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21739476 https://staging.comcomponent.com/blog/ntfs-internals-mft-structure/
- DOI(最新版)
- 10.5281/zenodo.21739476
- DOI(この版)
- 10.5281/zenodo.21739477
ここまでの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つ、第1回で扱ったcleanup(最後のハンドルが閉じたとき)とclose(カーネル内の参照がすべて消えたとき)の2段階も、4章のファイル削除の説明で使います。
1. まず結論
- NTFSの中心はMFT(マスターファイルテーブル)です。全ファイルがMFT内のレコードとして台帳管理され、ファイルに関するすべての情報は「MFTエントリの中」か「エントリが指すMFT外の領域」にあります(2章)。1
- ファイルの実体は「属性の集合」です。小さいファイルはデータ本体ごとMFTレコード内に収まり(常駐)、大きいファイルはクラスター列への参照だけを持ちます(非常駐)。小ファイル大量処理の遅さはここから説明できます(2章)。1
- データは複数持てます(複数データストリーム)。普段のデータは「名前のないストリーム」で、
file.txt:名前で追加のストリームを持てます。Zone.Identifier(Mark of the Web)の正体です(3章)。2 - 名前も属性です。同じレコードに複数の名前を付けたものがハードリンク。8.3短縮名も「もう一つの名前」として同居しています(4章)。34
- リパースポイントは「開いたら別の場所」の公式な仕掛けです。シンボリックリンク・ジャンクション・OneDrive のファイルオンデマンドは、みなこのタグ付きデータの応用です(5章)。56
- ジャーナルは2つあります。
$LogFileはメタデータ整合性の回復用(壊れないための先行ログ)、USNジャーナルは変更履歴の記録用(何が変わったかの台帳)。役割がまったく違います(6章)。78 - 「サイズ」と「ディスク上のサイズ」は別物です。スパースファイルと圧縮が乖離を生みます。第2回で見た「圧縮ファイルは非同期にならない」の背景もここにあります(7章)。910
この記事の知識マップ
NTFSの中心はMFT(マスターファイルテーブル)で、全ファイルがMFT内のレコードとして台帳管理されます。小さいデータはレコード内に常駐し、大きいデータはクラスター列(データラン)への参照だけを持つ非常駐になり、これが小ファイル大量処理の遅さや断片化の正体です。複数データストリーム・ハードリンク・8.3短縮名はいずれも「属性の集合」という同じ仕組みの応用で、リパースポイントはシンボリックリンクからOneDriveのファイルオンデマンドまでを支える「開く」への公式フックです。$LogFileが守るのは構造の整合性であり、データ内容の耐久性は第4回のキャッシュ制御の道具で別途作り込む必要があります。
flowchart LR
accTitle: NTFS内部構造とMFTの知識マップ
accDescr: NTFS、MFT、ファイルレコード、常駐・非常駐属性、データラン、断片化、複数データストリーム、Zone.Identifier、ハードリンク、8.3短縮名、リパースポイント(シンボリックリンク・ジャンクション・OneDriveファイルオンデマンド)、$LogFileとUSNジャーナル、スパースファイルとNTFS圧縮の関係を示す図
ntfs["NTFS"]
mft["MFT(マスターファイルテーブル)"]
mft_record["MFTファイルレコード"]
mft_zone["MFTゾーン"]
fragmentation["断片化(NTFS)"]
resident_attribute["常駐属性(resident)"]
non_resident_attribute["非常駐属性(non-resident)"]
data_run["データラン"]
fsutil["fsutil"]
alternate_data_stream["代替データストリーム(ADS)"]
zone_identifier["Zone.Identifier(Mark of the Web)"]
streams_tool["streams(Sysinternals)"]
size_disk_usage_mismatch["論理サイズとディスク使用量の乖離"]
hard_link["ハードリンク"]
eight_dot_three_name["8.3短縮名"]
reparse_point["リパースポイント"]
symbolic_link["シンボリックリンク"]
junction["ジャンクション(マウントポイント)"]
onedrive_files_on_demand["OneDriveのファイルオンデマンド"]
ntfs_logfile["$LogFile(NTFSトランザクションログ)"]
volume_corruption["ボリューム破損"]
cache_manager["キャッシュマネージャー"]
usn_journal["USNジャーナル(変更ジャーナル)"]
sparse_file["スパースファイル"]
ntfs_compression["NTFS圧縮"]
asynchronous_io["非同期I/O"]
procmon["Process Monitor(procmon.exe)"]
ntfs -->|"利用する"| mft
mft -->|"利用する"| mft_record
mft -->|"前提とする"| mft_zone
mft_zone -.->|"軽減する"| fragmentation
mft_record -->|"利用する"| resident_attribute
mft_record -->|"利用する"| non_resident_attribute
resident_attribute -->|"両立しない"| non_resident_attribute
non_resident_attribute -->|"利用する"| data_run
data_run -.->|"原因になり得る"| fragmentation
fragmentation -->|"で確認できる"| fsutil
ntfs -->|"利用する"| alternate_data_stream
zone_identifier -->|"に保存される"| alternate_data_stream
zone_identifier -->|"で確認できる"| streams_tool
alternate_data_stream -->|"で確認できる"| streams_tool
alternate_data_stream -.->|"原因になり得る"| size_disk_usage_mismatch
ntfs -->|"利用する"| hard_link
hard_link -->|"前提とする"| mft_record
ntfs -.->|"利用する"| eight_dot_three_name
eight_dot_three_name -->|"で構成できる"| fsutil
ntfs -->|"利用する"| reparse_point
reparse_point -->|"で確認できる"| fsutil
symbolic_link -->|"実装を担う"| reparse_point
junction -->|"実装を担う"| reparse_point
onedrive_files_on_demand -->|"実装を担う"| reparse_point
ntfs -->|"利用する"| ntfs_logfile
ntfs_logfile -->|"軽減する"| volume_corruption
ntfs -->|"利用する"| cache_manager
ntfs -->|"利用する"| usn_journal
usn_journal -->|"で確認できる"| fsutil
ntfs -->|"利用する"| sparse_file
sparse_file -->|"原因になり得る"| size_disk_usage_mismatch
ntfs -->|"利用する"| ntfs_compression
ntfs_compression -->|"原因になり得る"| size_disk_usage_mismatch
ntfs_compression -->|"両立しない"| asynchronous_io
ntfs -->|"で確認できる"| procmon
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全35件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. すべてはMFTのレコードである
2.1. ボリュームの台帳
NTFSボリュームをフォーマットすると、MFT(master file table)と、$ で始まる一連のメタデータファイルが作られます。MFTには、ボリューム上のすべてのファイルについて少なくとも1つのエントリがあり、MFT自身のエントリも含まれます。1
flowchart TB
subgraph VOL["NTFSボリューム"]
MFT["$MFT ── マスターファイルテーブル<br/>全ファイルのレコードの台帳(自分自身も載っている)"]
LOG["$LogFile ── メタデータ操作の<br/>トランザクションログ(6章)"]
BITMAP["$Bitmap ── クラスターの使用状況"]
OTH["$Boot / $Secure / $UpCase など<br/>その他のメタデータファイル"]
DATA["ユーザーデータ領域<br/>(非常駐データの置き場所)"]
end
MFT -->|"レコードが位置を指す"| DATA
図1: NTFSボリュームの構造。「ファイルシステム自身の管理情報もファイルとして持つ」のがNTFSの設計
ファイルに関する情報──サイズ、タイムスタンプ、アクセス許可、そしてデータの内容まで──は、MFTエントリの中に格納されるか、MFTエントリが位置を記述するMFT外の領域に格納されます。1 ファイルを削除するとエントリは「空き」としてマークされ再利用されますが、MFT自体は縮みません。また、MFTを連続的に保つためにMFTゾーンという領域が予約されており、ボリュームが埋まってくるとMFTの断片化が始まる──という寿命の話まで、公式ドキュメントに書かれています。1
2.2. ファイル=属性の集合、常駐と非常駐
ファイルレコードの中身は属性のリストです。標準情報(タイムスタンプ等)、ファイル名、セキュリティ、そしてデータ。ここで大事な分岐があります。
flowchart TB
subgraph REC["MFTファイルレコード(1ファイルぶんの台帳)"]
STD["標準情報属性<br/>タイムスタンプ・属性フラグ"]
FN["ファイル名属性<br/>(複数持てる ── 4章)"]
DATA["データ属性"]
end
Q{"データは小さいか"}
RES["常駐(resident)<br/>データ本体がレコード内に収まる<br/>読み出しはMFTアクセスだけで完結"]
NONRES["非常駐(non-resident)<br/>レコードには「クラスター列への参照」だけ<br/>実データはユーザーデータ領域に"]
DATA --> Q
Q -->|"数百バイト程度まで"| RES
Q -->|"それ以上"| NONRES
図2: ファイルレコードは属性の集合。データが小さければレコード内に「常駐」する
同じレコードの中身が、常駐と非常駐でどう変わるかを並べて見ると分かりやすくなります。
flowchart LR
subgraph RES2["常駐(resident) ── 小さいファイル"]
RA["MFTファイルレコード(固定長)<br/>標準情報 / ファイル名 / セキュリティ<br/>─────────────<br/>データ属性 = 中身そのもの<br/>『設定値=1』がここに直接入る"]
RB["ディスク上に別の置き場所は無い<br/>読み出しはMFTへのアクセスだけで完結"]
RA --> RB
end
subgraph NON2["非常駐(non-resident) ── 大きいファイル"]
NA["MFTファイルレコード(固定長)<br/>標準情報 / ファイル名 / セキュリティ<br/>─────────────<br/>データ属性 = データランの表<br/>『どこから何クラスター』の並び"]
NB["ユーザーデータ領域<br/>ラン1: 連続クラスター"]
NC["ユーザーデータ領域<br/>ラン2: 別の場所の連続クラスター"]
NA -->|"位置を指す"| NB
NA -->|"位置を指す"| NC
end
図3: 常駐と非常駐の対比。非常駐では、レコードが持つのは「実データがどこにいくつあるか」の表(データラン)だけになる
データランの数が増えるほど、1つのファイルを読むために散らばった領域を渡り歩くことになります。これが次に挙げる断片化の正体です。
この構造から、現場で出会う現象がいくつも説明できます。
- 小ファイル1万個のコピーが遅い理由。1ファイルごとにMFTレコードの作成・名前の登録・セキュリティ設定というメタデータ操作が発生します。データ転送そのものより台帳仕事が支配的になるのです(そしてその1つ1つが、第6回で見るフィルターの検査対象にもなります)。
- 断片化の正体。非常駐データは「クラスターの連続区間(ラン)の列」として記録されます。連続領域が取れないとランの数が増え、読み出しに必要なシークが増える──これが断片化です。ランの実際の並びは
fsutil file layoutで覗けます。 - 「フォルダー」も特別ではない。ディレクトリは「ファイル名からMFTレコード番号への索引(インデックス)を持つファイル」です。台帳の上では、すべてが同じ仕組みの上に乗っています。
3. データは「ストリーム」の1つにすぎない
3.1. 1つのファイル、複数のバイト列
NTFSでは、1つのファイルが複数のデータストリームを持てます。普段 ReadFile/WriteFile で読み書きしているのは名前のない既定ストリームで、ファイル名:ストリーム名 という構文で代替データストリーム(ADS)を作れます。2
flowchart LR
subgraph F["report.docx というファイル(1つの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. 名前もまた属性である ── ハードリンクと8.3名
4.1. ハードリンク ── 同じレコードへの複数の名前
図2で「ファイル名属性は複数持てる」と書きました。同一ボリューム内で、複数のパスが単一のファイルを参照する──これがハードリンクです(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つ外す」であり、最後の名前が外れ、開いているハンドルが閉じ、さらにメモリマップのセクションなどカーネル内の参照もすべて消えたときに初めて実体が消えます。第1回で見たcleanup(最後のハンドル)とclose(最後の参照)の2段階が、削除の寿命にもそのまま効いているわけです。なお属性の表示には癖があり、あるリンク経由で属性を変えても他のリンクの見かけ上の表示が古いまま、といった挙動が公式に注記されています。3
4.2. 8.3名 ── もう一つの隠れた名前
歴史的互換性のため、NTFSは長いファイル名に対して REPORT~1.DOC のような8.3形式の短縮名を自動生成できます。これも「もう一つの名前」としてレコードに同居します。大量のファイルがあるフォルダーでは短縮名の生成・衝突回避がコストになるため、fsutil 8dot3name で生成の無効化や既存短縮名の除去ができます(レジストリパスを短縮名で記録している古いアプリがあると壊れるので、strip前の検査機能があるのも実務的なポイントです)。4
ここで注意したいのは、短縮名があるかどうかは環境依存だということです。既定の挙動はレジストリ値 NtfsDisable8dot3NameCreation で決まり、0(全ボリュームで生成する)、1(全ボリュームで生成しない)、2(ボリューム単位で設定する)、3(システムボリューム以外では生成しない)の4通りがあります。4 2 を選べばボリューム単位で切り替えられるので、「Windowsなら PROGRA~1 のような短縮名が必ず存在する」とは限りません。短縮名に依存するコードや手順を書く前に、fsutil 8dot3name query C:(ボリュームを省略すると全ボリューム共通の既定設定)で現在の状態を確認してください。
パスと名前をめぐる落とし穴(MAX_PATH、予約名、末尾ドット)は「MAX_PATHとWindowsのパス・ファイル名の落とし穴」で詳しく扱っています。第1回の名前解決(オブジェクトマネージャー)とこの章(ファイルシステム内の名前)を合わせると、Windowsの「名前」の全体像になります。
5. リパースポイント ── 「開いたら別の場所」の仕掛け
ファイルやディレクトリにはリパースポイントを付けられます。実体は「タグ+ユーザー定義データ」という属性です。ファイルシステムがリパースポイント付きのファイルを開くと、タグに応じて処理が乗っ取られます──タグを理解するフィルタードライバーが処理を引き受けるか、名前の付け替え系のタグならリンク先のパスで解決がやり直されます。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: 対象にリパースポイント発見<br/>タグとデータを返す
alt シンボリックリンク/ジャンクション(名前の付け替え)
FS-->>IOM: 「本当の場所はこちら」
IOM->>FS: リンク先のパスで解決をやり直す
else フィルター管理のタグ(クラウドファイル等)
Note over FS: タグを理解するフィルターが<br/>処理を引き受ける(第6回)
end
図6: リパースポイントの解決。「開く」という操作に割り込む公式のフックになっている
この1つの仕掛けの上に、見慣れた機能が並んでいます。
- シンボリックリンク(
mklink)──リンク先パスを保持する標識。別ボリュームやUNCパスも指せます。6 - ジャンクション/マウントポイント──ディレクトリを別のローカルボリュームの位置へつなぐ古参の仕組み。3
- OneDriveのファイルオンデマンド──実データが手元にないファイルをリパースポイントで表現し、開かれた瞬間にフィルターがダウンロードして中身を差し出します。「エクスプローラーには見えるのに、開くと通信が走る」の正体です(フィルターの仕組み自体は第6回で)。
実務の注意はひとつ。「パスの先が本当にローカルのその場所とは限らない」ことです。再帰的にツリーを辿るツールがジャンクションでループする、サイズ集計が二重になる、バックアップがクラウドの実体化を大量に引き起こす──リパースポイントの存在を知らないコードは、これらを踏みます。FindFirstFile 系の属性 FILE_ATTRIBUTE_REPARSE_POINT の確認が対策の入口です。5
6. 2つのジャーナル ── $LogFileとUSN
「NTFSはジャーナリングファイルシステム」とよく言われますが、NTFSには役割の違う2つのジャーナルがあります。混同すると保証を読み違えます。
flowchart TB
subgraph J1["$LogFile ── 先行ログ(壊れないため)"]
A1["メタデータ操作(レコード更新・名前変更等)を<br/>実行前にログへ記録"]
A2["システム障害後の次回起動時に<br/>ログを再生して構造の整合性を回復"]
A1 --> A2
end
subgraph J2["USNジャーナル ── 変更履歴(何が変わったかを知るため)"]
B1["ファイル/ディレクトリへの変更のたびに<br/>変更内容と名前を記録"]
B2["バックアップ・検索インデックス・同期ツールが<br/>「前回から何が変わったか」を全走査なしで把握"]
B1 --> B2
end
図7: 2つのジャーナル。$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: 2つのジャーナルの対比
$LogFile(トランザクションログ)は、メタデータ操作の先行ログです。システム障害が起きても、NTFSは次回起動時にこのログとチェックポイント情報からファイルシステムの整合性を自動回復します。7 ここで守られるのは構造です。第4回で見たとおり、キャッシュ上のダーティなデータ内容は電源断で失われうる──「ボリュームは壊れない。しかし最後の書き込みは消えうる」が正しい読み方です。- USNジャーナル(変更ジャーナル)は、ボリューム内のファイルやディレクトリに変更が起きるたびに、変更の内容と対象の名前を記録していく台帳です。8 バックアップやインデクサーが「前回から変わったものだけ」を全走査なしで拾うための仕組みで、障害後のインデックス再構築の回避にも使われます。8 実務では、
FileSystemWatcherの取りこぼし(「FileSystemWatcher実務ガイド」)を補う突き合わせ台帳としてfsutil usn readjournalを調査に使えることを覚えておくと役立ちます。
7. スパースと圧縮 ── 「サイズ」が二つある話
NTFSでは、ファイルの論理的な長さと実際に割り当てられた領域が別々に管理されます。プロパティ画面の「サイズ」と「ディスク上のサイズ」です。乖離を生む代表が2つあります。
スパースファイルは、ゼロが続く範囲に実領域を割り当てず「穴」として管理します。9 論理サイズ42GBの仮想ディスクファイルが、ディスク上では500MBしか使っていない──が普通に起こります。穴を読めばゼロが返り、書けばそのぶんだけ割り当てられます。
flowchart LR
subgraph L["論理的なファイル(サイズ: 1GB)"]
R1["データ 10MB"]
H1["穴(ゼロ) 500MB"]
R2["データ 5MB"]
H2["穴(ゼロ) 残り"]
end
subgraph P["ディスク上の割り当て(15MB+管理情報)"]
A1["ラン: R1の実体"]
A2["ラン: R2の実体"]
end
R1 --> A1
R2 --> A2
図8: スパースファイル。「穴」には割り当てが無く、論理サイズとディスク上のサイズが乖離する
NTFS圧縮は、データを圧縮ユニット単位で圧縮して格納します。10 透過的で便利ですが、コストも透過的ではありません──読み書きのたびに展開・再圧縮が走り、断片化も進みやすくなります。そして第2回5章で見たとおり、圧縮ファイルへのアクセスは非同期になりません(ファイルシステムが同期に変換します)。「非同期I/Oにしたのに速くならないファイルがある」ときに疑う場所の一つです。
割り当てベースの実サイズは GetCompressedFileSize で取得できます。「ファイルサイズの合計」と「ディスク使用量」が合わない調査では、スパース・圧縮・ADS(3章)・クラスター切り上げの4つを順に疑うのが定石です。
8. 自分の目で確かめる
今回も、手元のWindowsですべて観察できます(一部は管理者権限が必要です)。実行結果が正しいかを自分で判定できるよう、コマンドごとにどこを見れば何が分かるかを添えます。
:: 代替データストリームを見る
dir /r C:\Users\%USERNAME%\Downloads
ここを見る: 通常のファイル行の下に、字下げされた ファイル名:Zone.Identifier:$DATA の形の行が長さ付きで並びます。この行があれば、そのファイルにMark of the Webが付いている状態です(3章)。ブラウザーでダウンロードしたファイルには付き、自分で作ったファイルには付きません。両方の場所で実行して見比べると、ADSの有無がはっきりします。
:: ファイルのMFT上の配置(ラン)や属性を見る
fsutil file layout C:\path\to\file.dat
:: エクステントだけを見る(公式ドキュメントに記載のあるサブコマンド)
fsutil file queryextents C:\path\to\file.dat
ここを見る: layout はストリームごとに、サイズ・割り当てサイズと、非常駐であればエクステント(VCN・LCN・クラスター数の組)の一覧を並べます。エクステントの行が出ないごく小さいファイルは常駐(2.2節)、複数行に分かれていれば断片化しています。数バイトのテキストファイルと数百MBのファイルで実行して見比べるのが、常駐/非常駐を最短で体感する方法です。
:: 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章で触れた「どこまで遡れるか」の目安です。ジャーナルが無効なボリュームではエラーになります。
:: リパースポイントの確認(リンク先とタグ)
dir /aL C:\Users\%USERNAME%
fsutil reparsepoint query "C:\Users\%USERNAME%\OneDrive"
ここを見る: dir /aL に出てきたものがリパースポイント(FILE_ATTRIBUTE_REPARSE_POINT が立っているもの)です。シンボリックリンクやジャンクションは <SYMLINKD> <JUNCTION> のような種別付きで表示されます。fsutil reparsepoint query はリパースタグの値と、名前の付け替え系ならリンク先のパスを表示します。リパースポイントでない対象を指定するとエラーになるので、エラーになること自体が「ここは普通のフォルダー」の確認になります。
Procmonでファイル操作を追うと、今回の登場人物が実名で流れていきます($LogFile への書き込み、ストリーム名付きのパス、リパース処理)。使い方は「Process Monitor(ProcMon)実践ガイド」を参照してください。
9. まとめ
- NTFSの中心はMFT。全ファイルが台帳のレコードで、情報は「レコード内」か「レコードが指す外部領域」にあります。小さいデータは常駐、大きいデータはラン参照で、小ファイル大量処理の遅さや断片化はこの構造の帰結です。1
- データストリームは複数持てます。Zone.Identifier(Mark of the Web)はただのADSで、
dir /rで見え、NTFSの外へは運ばれません。211 - 名前は属性で、複数持てます。ハードリンクは同じレコードへの同格の名前、8.3名は互換用のもう一つの名前。「削除=名前を外す」であり、実体の消滅は最後の名前・ハンドル・カーネル内参照(マップ済みセクション等)がすべて無くなったときです。34
- リパースポイントは「開く」への公式フックで、シンボリックリンクもジャンクションもファイルオンデマンドもこの応用です。ツリーを辿るコードは
FILE_ATTRIBUTE_REPARSE_POINTを意識する必要があります。56 - ジャーナルは2つ。
$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に少なくとも1つのエントリがあり、MFT自身のエントリも含まれること、ファイルのサイズ・タイムスタンプ・アクセス許可・データ内容を含むすべての情報がMFTエントリ内かMFTエントリが位置を記述するMFT外の領域に格納されること、ファイル削除時にエントリが空きとしてマークされ再利用されるがMFTのサイズは縮まないこと、MFTを連続に保つためのMFTゾーンが予約されること、割り当ての進行によりMFTの断片化が起こることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, File Streams. NTFSのファイルデータが1つ以上のストリームとして格納されること、既定の(名前のない)データストリームと名前付きの代替データストリームがあること、「ファイル名:ストリーム名」の形式でストリームを指定してCreateFileで開けることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Hard links and junctions. ハードリンクが同一ボリューム内で複数のパスが単一のファイルを参照するファイルシステム上の表現であること、CreateHardLinkで作成すること、どのリンク経由の変更も他のリンクから即座に見えること、属性の変更がすべてのハードリンクに伝播する一方で、ディレクトリエントリ上の表示は変更を行ったリンクでのみ更新されるという表示上の癖があること、およびジャンクション(ディレクトリを別のローカルボリュームへつなぐ仕組み)について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, fsutil 8dot3name. NTFSが長いファイル名に対して8.3形式の短縮名を生成できること、fsutil 8dot3nameで短縮名生成の有効/無効の照会と設定、既存の短縮名の除去(strip)、除去した場合に影響を受けるレジストリ参照の走査ができることについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Reparse points. リパースポイントがユーザー定義データとそのデータ形式を一意に識別するリパースタグの集まりであること、リパースポイント付きファイルのオープン時にファイルシステムがタグに対応する処理(タグを解釈するファイルシステムフィルターによる処理)を試みること、NTFSのファイルシステムリンクやリモートストレージ(階層記憶)の実装に使われていること、FILE_ATTRIBUTE_REPARSE_POINT属性で存在を確認できることについて。 ↩ ↩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. スパースファイルにおいて、ゼロで構成される大きな範囲に物理的なディスク領域を割り当てず、データを含む部分にのみ領域を割り当てること、割り当てのない範囲を読むとゼロが返ることについて。 ↩ ↩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回です。ファイルマッピングとして実装されたキャッシュ、先読みと遅延書き込み、FlushFileBuffersやFILE_FLAG_NO_BUFFERINGの使い分け、電源断でデータが消える条件までを整理します。
Windows I/Oの深層(第1回) ── すべての読み書きはIRPになる:I/Oシステムの全体像
WindowsのI/Oシステムを底から解説する連載の第1回です。オブジェクトマネージャーの名前空間、ドライバー・デバイス・ファイルの3つのオブジェクト、IRPのライフサイクル、CloseHandleの裏側までを図解で整理します。
Windows I/Oの深層(第6回・最終回) ── フィルタードライバーとミニフィルター:ProcmonとウイルススキャンがI/Oに割り込める理由
Windowsのフィルタードライバーとミニフィルターを図解で解説する連載の最終回です。フィルターマネージャーとアルティチュード、pre/postコールバック、Procmonやウイルス対策が全I/Oを検査できる仕組み、「あの環境だけ遅い」の調査手順まで整理します。
Windows I/Oの深層(第2回) ── 同期I/Oと非同期I/O:OVERLAPPEDの本当の意味
Windowsの同期I/Oと非同期I/O(オーバーラップI/O)を図解で解説する連載の第2回です。FILE_FLAG_OVERLAPPEDの意味、完了通知の4方式、非同期のはずが同期完了する条件、キャンセルの作法、.NETとの対応までを整理します。
マルチスレッドの実務ベストプラクティス .NET編 ── スレッドを増やす前に決めておくこと
「スレッドを立てたら、たまに落ちる・固まる」を防ぐ設計の定石を.NET/C#向けに整理。スレッドを自分で作らずTaskに乗る、共有可変状態を減らす、ロックの規律、CancellationTokenでの停止設計、UIスレッドの扱いまで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
不具合調査 / 長期稼働テーマ
再現しにくい不具合、通信停止、長期稼働障害、失敗パス検証を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
不具合調査・原因解析
再現しにくい障害、長期稼働後の不具合、通信停止などの調査を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- MFT(マスターファイルテーブル)とは何ですか?
- NTFSボリュームの心臓部にあたるデータ構造で、ボリューム上のすべてのファイルについて少なくとも1つのエントリ(ファイルレコード)を持つ台帳です。MFT自身のエントリも含まれます。ファイルのサイズ、タイムスタンプ、アクセス許可、そしてデータ本体に至るまで、ファイルに関するすべての情報は、MFTエントリの中か、MFTエントリが指し示すMFT外の領域に格納されます。小さなファイルならデータ本体ごとMFTエントリ内に収まり(常駐)、大きなファイルはデータの置き場所(クラスターの並び)への参照だけがエントリに記録されます(非常駐)。ファイルを削除するとエントリは空きとしてマークされ再利用されますが、MFT自体のサイズは縮みません。
- ファイルに付いている「Zone.Identifier」という見えないデータは何ですか?
- NTFSの複数データストリーム(代替データストリーム)の一つです。NTFSでは1つのファイルが複数のバイト列(ストリーム)を持て、普段読み書きしているのは名前のない既定ストリームです。「file.txt:Zone.Identifier」のようにコロン区切りで指定する追加ストリームに、Windowsはファイルの出所(インターネットからダウンロードした等)を記録します。これがいわゆる「Mark of the Web」で、SmartScreenの警告やOfficeの保護ビューの判定材料になります。代替ストリームはエクスプローラーのサイズ表示に現れず、dir /rコマンドやSysinternalsのstreamsツールで確認できます。NTFS以外のファイルシステム(FATなど)へコピーすると保持されない点にも注意が必要です。
- ハードリンクとシンボリックリンクは何が違いますか?
- ハードリンクは「同じファイル実体(同じMFTレコード)を指す、同格の名前がもう一つ増える」ことです。同一ボリューム内でしか作れず、どの名前からアクセスしても同じファイルで、1つの名前を削除しても他の名前が残っていればファイルは消えません。シンボリックリンクは「別のパスへ案内する標識」で、リパースポイントとして実装されます。リンク先のパス文字列を保持しているだけなので、別ボリュームやリモートも指せますが、リンク先が消えれば行き止まりになります。実務では、ハードリンクは実体の共有(削除の意味が変わる)、シンボリックリンクはパスの付け替え(移設やリダイレクト)と使い分けるのが基本です。
- NTFSはジャーナリングファイルシステムだから、電源断でもデータは消えないのですか?
- 守られる範囲を正確に理解する必要があります。NTFSのトランザクションログ($LogFile)が守るのは、ファイルシステムの構造(メタデータ)の整合性です。システム障害が起きても、次回起動時にログを使って整合性を自動回復し、「ボリュームが壊れて読めない」事態を防ぎます。しかし、書き込み途中だったファイルのデータ内容そのものが復元されるわけではありません。連載第4回で見たとおり、キャッシュ上のダーティなデータは電源断で失われます。つまり「ボリュームは壊れないが、最後の書き込み内容は消えうる」が正しい理解で、データ自体の耐久性が必要ならFlushFileBuffersやWRITE_THROUGH、あるいはアプリ側の書き込み設計(一時ファイル+リネーム等)で作り込む必要があります。
- ファイルの「サイズ」と「ディスク上のサイズ」が違うのはなぜですか?
- NTFSでは、ファイルの論理的な長さと、実際に割り当てられたディスク領域が別々に管理されているからです。通常のファイルでもクラスター単位(既定4KB)で切り上げて割り当てるため差が出ますが、大きく乖離するのはスパースファイルと圧縮ファイルです。スパースファイルでは、ゼロが続く範囲に実領域を割り当てず「穴」として管理するため、論理サイズ数GBでもディスク上は数MBということが起こります。圧縮ファイルでは圧縮後のサイズだけが割り当てられます。逆に「ディスク上のサイズ」の方が大きく見える場合は、クラスター切り上げや代替データストリームが原因のことがあります。