書いて覚えるSwift入門

第32回APFSの研究

最低限文化的なマカーのためのAPFS入門

iOSでは10.3以降、問答無用でHFS+を置き換えたAPFSが、macOS High SierraでいよいよMacにもやって来ました。利用にあたってファイルシステムを意識する必要が事実上皆無で、外付けストレージをマウントすることもほとんどないiPhoneやiPadやApple WatchやApple TVと異なり、外付けストレージもNASもごく当然に利用し、Boot CampなどでOSすらmacOS以外のものをサポートしているMacを利用する以上、ファイルシステムというものをまったく意識しないということは不可能です。今回はSwiftを意識しつつも、Swiftの開発において事実上必須である、macOSの新メインファイルシステムたるAPFSをあらためて見ていきます。

なぜ、

  • APFSは事実上問答無用で現在サポートされているApple製品すべてで採用されたのか
  • Fusion DriveやHDDを内蔵するMacはその例外となったのか
  • 開発者たる我々はどのような点に気を付けたら良いのか

本記事を読めば、これらの点にも納得がいくはずです。

Copy-on-Write=SwiftとAPFSの共通点

APFSの最大の特長は、それがCopy-on-Write(CoW)なファイルシステムであるということです。公式ページにも真っ先にそれが示されています。

CoWとはいったい何なのでしょうか? 実はSwiftもCoWを採用しています。そのことを確認してみましょう。

extension String {
    func peek() {
        let type = (UInt, UInt, UInt).self
        let (u0, u1, u2) = unsafeBitCast(self, to:type)
        debugPrint(u0, u1, u2)
    }
}

let orig = "Hello, playground"
var copy = orig
orig.peek()
copy.peek()
copy += " string"
orig.peek()
copy.peek()

SwiftのStringは抽象化されていて、その構造を直接見ることは本来できないのですが、ここでは無理やりunsafeBitCastを使って覗いています。最初にpeek()したときにはorigcopyも同一だったのが、copy"string"を追記したあとは内容が変わっていることが確認できます。

SwiftのString構造体は常に24bit=ポインタ3つ分。この値が固定であることからも、文字列の内容は構造体そのものではなく構造体の中のポインタが示すアドレスに格納されていることが察せられます。それが最初にcopy = origした時点では変わらず、copyに変更が加えられたあとに変わったというのはどういうことか。内容に変更がなされるときに初めてデータが複製され、その複製されたデータに対して変更が行われたのです。Copy-on-Writeすなわち「複製は書き込み時」というわけです。

別の見方をすると、CoWにおいてはデータの上書きというのは存在せず、必ずコピーされたデータを変更するということになります。たとえ1バイトでも変更されたら、元のデータとは違う領域に変更後のデータが保存されるということです。

これが何を意味するのか。元のデータへのポインタさえ残っていれば、変更前のデータが必ず取り出せるということです。APFSを含め、CoWなファイルシステムにおけるスナップショット(snapshot)とは、このもとのデータへのポインタのことなのです。先ほどのSwiftのコードのorigに相当します。

スナップショットがあると何がうれしいか。まず、Undoが超簡単になります。Swiftの例であれば、もう一度copy = origするだけです。それがファイルシステム全体に対して行えるのです。

実際にやってみましょう。macOSにはv10.5以来Time Machineというバックアップ用インターフェースが用意されています。賢明なる読者のみなさんはすでに使っていらっしゃるはずですが、High Sierraよりこれはスナップショットへのインターフェースともなっています。

まずは念のためにTime Machineを有効にしておきましょう図1⁠。バックアップ先は用意しなくてもOKです。システム設定の自動バックアップにチェックを入れてもOKですし、Terminalからsudo tmutil enableを実行してもOKです。

図1 Time Machineを有効にしておく
図1 Time Machineを有効にしておく

これだけで、1時間おきに自動でスナップショットが作られるのですが、tmutil snap shotで手動でスナップショットを取ることもできます。完了は一瞬です。CoWではデータの複製は書き込み時なのですから、⁠ここがスナップショット」以上の情報は書き込まないのですから当然です。

$ tmutil localsnapshot
Created local snapshot with date: 2017-10-17-165216

スナップショットのリストは、tmutil listlocalsnapshotsのあとにマウントポイントを指定して見ることができます。

% tmutil listlocalsnapshots /
com.apple.TimeMachine.2017-10-18-150407
com.apple.TimeMachine.2017-10-18-160303
com.apple.TimeMachine.2017-10-18-170437
com.apple.TimeMachine.2017-10-18-180253
com.apple.TimeMachine.2017-10-18-190345
com.apple.TimeMachine.2017-10-18-200341
com.apple.TimeMachine.2017-10-18-210231
com.apple.TimeMachine.2017-10-18-220244
com.apple.TimeMachine.2017-10-18-230916
com.apple.TimeMachine.2017-10-19-000459
com.apple.TimeMachine.2017-10-19-010440
com.apple.TimeMachine.2017-10-19-021104
com.apple.TimeMachine.2017-10-19-030708
com.apple.TimeMachine.2017-10-19-040243
com.apple.TimeMachine.2017-10-19-050420
com.apple.TimeMachine.2017-10-19-070312
com.apple.TimeMachine.2017-10-19-080249
com.apple.TimeMachine.2017-10-19-090235
com.apple.TimeMachine.2017-10-19-111504
com.apple.TimeMachine.2017-10-19-122825
com.apple.TimeMachine.2017-10-19-133025
com.apple.TimeMachine.2017-10-19-142826

スナップショットが作成されていることを確認したら、Macをリカバリーモードで再起動してみましょう。起動時に[Command][R]です図2、図3、図4、図5⁠。

図2 macOS UtilitiesでTime Machineからリストア
図2 macOS UtilitiesでTime Machineからリストア
図3 リストア開始
図3 リストア開始
図4 リストア元の選択
図4 リストア元の選択
図5 スナップショットを選択
図5 スナップショットを選択

あとはここからTime Machineを選択し、バックアップの代わりにスナップショットされたディスクを選択すれば、スナップショットのリストが表示されます。リストアはやはり一瞬で完了します。

ファイルシステム全体をスナップショットでundoする場合はリカバリーモードでの再起動が必要ですが、特定のファイルだけを元に戻したい場合は普通にTime Machineに入れば、バックアップストレージが接続されていなくてもスナップショット上のファイルを元に戻せます図6⁠。

図6 バックアップがなくてもローカルのスナップショットでリストア可能
図6 バックアップがなくてもローカルのスナップショットでリストア可能

なんだか良いことづくめのように思えるCoWですが、重大な欠点が1つあります。ファイルに上書きをしないということは、その分ストレージを食うということにもなります。実際、APFS上のゴミ箱を空にしても空き容量はすぐには減りません。ゴミ箱の中のファイルがスナップショットにあったら当然そうなります。また、Finderが申告する空き容量とdfコマンドの出力結果は食い違います。前者は「削除可能(purgeable)なスナップショットを削除した後の空き容量」を、後者は本当にどこにも使われていない空きブロックの容量をそれぞれ表示します。

macOSの実装は、なるべくスナップショットは残しておき、空き容量が本当に足りなくなって初めて古いスナップショットを消すという仕様になっているようです。常時バックアップ用のSSDが接続されている筆者のiMacでも、ほぼ24時間分のスナップショットが残っています。

ちなみに、これらのスナップショットはtmutilコマンドで削除することもできます。tmutil deletelocalsnapshot YYYY-MM-DDhhmmssで日付を指定して1つずつ消すこともできますし、tmutil thinlocalsnapshots「十分」な空き容量が確保されるまで古いスナップショットを消すこともできます。このあたりを手動でできるところも、ほかのApple製品とMacの違いですね。

なお大事なことなのであらためて申し上げると、スナップショットというのはバックアップを置き換えるものではありません。ハードウェアが壊れてしまえばスナップショットも一蓮托生。というわけでバックアップも相変わらず必要なのですが、そのバックアップを取る手法もスナップショットのおかげで変わります。

CoWではないHFS+では、ファイルシステムへの変更を常時監視して、書き込みがあったファイルをマークしてそれをバックアップするという方法をとっていました。しかしAPFSのバックアップでは、スナップショットどうしを比較するという手法になりました。常時監視しなくてもいい分、ずっとシンプルになったのです。

残念ながら、Time Machineのバックアップディスクは現時点ではHFS+なので、ZFSのsend/recvのようにスナップショットの差分そのものをまとめてブロック転送という手法は使えないのですが、近い将来には必ずそうなるのではないかと筆者は考えています。

さよならパーティション

APFSのもう1つの特長は、物理的なパーティションを不要にしたこと。物理的にストレージをパーティションした場合、それぞれのパーティションの空き容量は物理的に固定されてしまいますが、APFSではまず「コンテナ」⁠container)があり、そこに論理的なヴォリューム(volume)を作成することで、コンテナの空き容量をすべて活用できます。ZFSをご存じの方は前者がpool、後者がdatasetというと理解しやすいでしょう。

図7図8は起動ヴォリュームと同じコンテナにヴォリュームを追加した例。どちらも総容量が一緒であることが確認できます。

図7 起動ボリューム
図7 起動ボリューム
図8 ボリュームを追加
図8 ボリュームを追加

APFSではヴォリュームが論理的な存在です。実はこのことが、HDDやFusion DriveへのAPFS導入が見送られた理由でもあります。APFSというのはファイルシステムというよりメモリ管理システムのようだと感じた読者は鋭い。空き容量を増やすにはスナップショットを削除する必要があるというのはガベージコレクションのようですし、ヴォリュームがコンテナを共有するというのは実メモリをプロセスに仮想アドレスとして割り当てるような感じ。実はそうなのです。SSDというのはRAMに似て、どのブロックにもアクセスする速度は変わりません。だったらより利便性の高いメモリ管理に似せようとするのはソフトウェアエンジニアの願望といってもいいでしょう。ZFSでは実メモリをどっさり使ってRAMに似ていないHDDでそれを実現しました。SSDを前提とすればそれも不要です。そして今日日のApple製品は、廉価なデスクトップを除けばその前提条件がそろっています。実にAppleらしい割り切りっぷりではありませんか。

「APFS? 何それ知らない」と昔のハードウェアは言った

新開発のファイルシステムの導入はそれ自体がたいへんなものですが、それを標準とするにあたってはもう1つ問題があります。OSをそこからブートするにはどうすれば良いのでしょう?

ここで、新旧2つの起動ストレージを見比べてみましょう。どちらもdiskutil listの表示結果です図9、図10⁠。

図9 Sierraの場合
/dev/disk0 (external, physical):
   #:                       TYPE NAME                SIZE        IDENTIFIER
   0:      GUID_partition_scheme                    *68.7 GB     disk0
   1:                        EFI EFI                 209.7 MB    disk0s1
   2:                  Apple_HFS Macintosh HD        67.9 GB     disk0s2
   3:                 Apple_Boot Recovery HD         650.1 MB    disk0s3
図10 High Sierraの場合
/dev/disk0 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *68.7 GB    disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:                 Apple_APFS Container disk1         68.5 GB    disk0s2

/dev/disk1 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +68.5 GB    disk1
                                 Physical Store disk0s2
   1:                APFS Volume root                    15.2 GB    disk1s1
   2:                APFS Volume Preboot                 19.9 MB    disk1s2
   3:                APFS Volume Recovery                519.9 MB   disk1s3
   4:                APFS Volume VM                      20.5 KB    disk1s4

High SierraではSierraのころにあったRecovery Partitionが消え、APFSコンテナの中に収納されたことが見てとれます。それはよしとして、Prebootとはいったい何でしょう?

実は、現代のOSというのはmacOSを含め、最初にロードされるプログラムではありません。まずブートローダーという、OSをロードするプログラムが読み込まれ、そのブートローダーがOSをロードするという2段ロケットのようなしくみになっています。BIOS時代、パソコンはそもそもファイルシステムなんて知らず、起動ディスクの最初のセクター(これがMBR[Master BootRecord⁠⁠)を決め打ちで読み込んで、それがブートローダーを読み込んで、最後にやっとOSがロードされるというしくみでした。EFI時代になって、パソコンはFAT32でフォーマットされたEFIパーティションをサポートするようになりましたが、それにしてもハードウェアが知っているのはFAT32だけです。Macのファームウェアはそれに加えてHFS+もサポートしているので、たとえEFIパーティションが空でもブートできたのですが、それにしてもAPFSなんて知らないことは変わりません。

Prebootの役割は、FAT32とHFS+しか知らない現在のMacにAPFSを教えてあげるために存在するのです。

さらに今日においては、⁠コンピュータ」自体物理的な存在とは限りません。仮想マシンの利用が進んだ結果、物理的なコンピュータのことをわざわざベアメタルと呼ぶようになったぐらいです。Macもこの例外ではなく、仮想MacがあるおかげでTravis CIでSwiftコードもテストできるのです。

その仮想マシンにおけるAPFS Bootサポートですが、本原稿執筆現在、VMware Fusion 8.5.8以降がOK、VirtualBox 5.1.XがNGという状態でした。前者の場合も、Sierraの仮想マシンをAPFSにアップグレードしようとした場合はPreboot Volumeの作成に失敗するため、アップグレード経由ではHFS+のままです。インストール用DVDイメージを作成したうえで新規インストールする必要があります。おかげで本記事が執筆できたのですが、そこに至るまでずいぶん手間暇がかかりました。同じ苦労を読者に味あわせるのは忍びないので、DVDイメージ作成スクリプトをGistに置いておきます。App StoreからHigh Sierraのインストーラを(再)ダウンロードした状態でお使いください。"use at your own risk"で念のため。

次回予告

どんなにすばらしい建築物も、土台が沈めば建物ごと沈んでしまいます。よって今回はXcode の土台であるmacOSの新しい土台であるAPFSを検証しました。実際に体験してみて、強引に将来を見据えた、Appleという名を冠にするにふさわしい一品でした。次回はふたたび物を作るための建物であるXcodeとSwiftに焦点をあてます。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」⁠使いたい」⁠発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧