メインコンテンツにスキップ ナビゲーションにスキップ フッターにスキップ
期間限定:デザインパートナープログラム — BUSINESSプランを永久利用

断片化の数値が怖く見える。実際のコストを計測してみた。

ストレージヘルスレポートが1ギガバイトあたり約2万エクステントというリポジトリを報告します。その数値は警戒を呼びます。最も悪化した対象で順次読み取りとランダム読み取りのベンチマークを実行したところ、ペナルティはゼロでした。データと、Rediaccがデフラグコマンドを搭載しない理由を報告します。

TL;DR. Rediaccの rdc machine query --storage-health はリポジトリごとに断片化の数値を報告します。ある本番マシンではGitLabが約19,650エクステント/ギガバイトとフラグされています。反射的にデフラグしたくなります。私は代わりに計測しました。

  • 隣のリポジトリより16倍断片化しているリポジトリが順次読み取りで149 MB/s(対143 MB/s)を記録し、ランダム4K読み取りではより高速でした(719対957マイクロ秒)。
  • デバイスはフラッシュストレージです。断片化がスピンドルディスクを傷つけるのはシーク時間を通じてです。SSDではそのメカニズムがほぼ存在しません。
  • このマシンで btrfs filesystem defragment を実行すると、reflinkで共有された約250 GBのフォークとスナップショットが、空き容量4.4 GBのプールに対して非共有化されます。それが本当のリスクであり、ベンチマークはそれに見合うメリットがないことを示しています。

ストレージヘルスレポートは一つの問いに答えるために存在します:ディスクはどこに使われているか。各リポジトリのサイズ、フォークと共有しているデータ量、断片化の数値を表示します。その最後の数値が恐ろしく見えることがあります。私が運用するマシンでは、GitLabのリポジトリイメージが14.6 GB全体で268,771エクステントと報告されます。1ギガバイトあたり約19,650エクステントで、ツールは「high」と分類します。

続く反射は自動的です。断片化が高い、だからデフラグ。私はこの反射をスピンドルディスク用のシェルスクリプトに15年間書き込んできました。RediaccにデフラグボタンをつけるDEの前に、私たちが動かすハードウェアでその数値が実際に何を意味するか知りたかった。そこで実機のライブマシンでベンチマークを実施しました。

数値が実際に何を数えているか

Rediaccのリポジトリは、btrfsプール上に存在する単一のLUKSイメージファイルです。断片化の数値は、そのイメージファイルに対して filefrag を実行することで得られます。暗号化されたコンテナのエクステントを数えるものであり、内部でアプリケーションが読むファイルのものではありません。

これが重要な理由はデータのスタック構造にあります。下から順に:物理SSD、ホストのext4ルートファイルシステム、ループバックされたプールファイル、loop0、btrfsプール、LUKSイメージ、device-mapper cryptデバイス、コンテナが見る内側のext4。btrfsはコピーオンライトです。リポジトリ内でのランダム書き込みはすべてイメージに新しいエクステントを書き込みます。データベースやコンテナオーバーレイレイヤーは一日中ランダムに書き込むため、イメージは設計上エクステントを蓄積します。

ボリューム内部のファイルはまた別の話です。GitLabのリポジトリを確認しました:gitaly バイナリは10エクステント、gitパックファイルは17エクステントでした。内側のファイルシステムは断片化していません。1ギガバイトあたり19,650という数値はコピーオンライトコンテナを表しており、まさにそう見えるべき姿で、読み取りが遅いかどうかについては何も語っていません。

ベンチマーク

断片化スケールの両端にある2つのリポジトリを選び、ページキャッシュをバイパスして物理読み取りを強制するダイレクトIOで読み取りました。

リポジトリ平均エクステントサイズエクステント数/GB順次読み取り
GitLab54 KB約19,650149 MB/s
Stack Overflowデモ880 KB約1,190143 MB/s

断片化の16倍の差がスループットのペナルティを生みませんでした。より断片化したファイルがわずかに高速でした。次に、人々が実際に心配するパターン、データベーストラフィックの形状である小さいランダム読み取り:

リポジトリランダム4KレイテンシIOPS
GitLab(断片化あり)719 us1,390
Stack Overflowデモ(断片化少)957 us1,045

ここでも断片化が多いファイルの方が高速です。小さな差はファイルサイズとバックエンドキャッシュに起因しており、エクステントのレイアウトではありません。フラッシュ上では、ランダム読み取りは周囲のエクステントがどこにあるかに関係なく、1回のルックアップと1回の読み取りです。動かすヘッドがないのです。

絶対値では約145 MB/sと1,000 IOPSと控えめですが、デバイスが共有ホスト上の仮想化ディスクであり、データパスが深いためです。その上限は仮想化とcryptレイヤーによって設定されており、btrfsの上下に存在します。イメージのデフラグではこの上限を引き上げることはできません。

断片化が唯一コストとして現れるもの

誠実さのために反対側も示します。断片化にはここで計測可能なコストがちょうど一つあり、それは読み取り速度ではありません。エクステントマップを列挙する時間です:

  • GitLabでの filefrag(268,771エクステント):3.19秒
  • Stack Overflowデモでの filefrag(152,364エクステント):0.74秒

すべてのエクステントを走査する操作はこのコストを払います。ストレージヘルススキャン自体、バックアップ同期、デルタツールなどが該当します。秒単位であり、エクステント数とほぼ線形にスケールし、アプリケーションではなくバックグラウンドジョブに影響します。エクステント走査時間が実際のボトルネックになるなら、それは限定的な問題であり限定的な解決策があります。ライブデータを書き直す理由にはなりません。

Rediaccがデフラグコマンドを搭載しない理由

btrfs filesystem defragment はカーネル3.9以降、reflinkを保持していません。マニュアルページには明記されています:デフラグはコピーオンライトデータのreflinkを分解し、スペース使用量を大幅に増加させる可能性があります。ファイルを連続して書き直すと、共有されたすべてのエクステントがプライベートなコピーにコピーされます。

このマシンではほぼすべてが共有されています。フォークはreflinkを通じて親のデータを共有し、バックアップタイマーも共有するread-onlyスナップショットを追加します。プールは空き容量4.4 GBで99%満タンです。GitLabは97%共有されているため、デフラグすると約14 GBを4.4 GBにコピーしようとして途中で失敗します。Stack OverflowデモはユニークデータがわずかなGBのうち26 MBで137 GBあり、デフラグすると物理的に存在しない137 GBを実体化しようとします。全リポジトリ合計で約250 GBがreflinkされています。デフラグパスはチューニングではなく、スペース爆弾です。

収まったとしても長続きしません。これらのイメージは同じランダム書き込みワークロードの下で数分以内に再断片化します。フォークを一時的に非共有化することになり、ベンチマークがすでに確認している読み取り速度のために何も得られません。

断片化の代わりに見るべきもの

同じレポートで注目に値するカラムは乖離度(divergence)です。リポジトリのイメージがフォークやスナップショットと共有されるのではなく、そのリポジトリ固有のデータの割合です。新しいフォークはほぼすべてを共有しているため0%近くになります。フォーク後に大量に書き込まれたリポジトリは100%に向かって上昇します。

乖離度は断片化が答えられない問いに答えます:このリポジトリが実際に回収可能なディスクをどれだけ占有しているか。プールが逼迫しているとき、乖離度が低いリポジトリはクリーンアップの標的として適切ではありません。そのバイトは共有されており、削除してもほとんど解放されないためです。バイトが存在するのは乖離度が高い場所です。

まとめ

断片化の数値は実在し、ランダム書き込み下のコピーオンライトイメージでは常に高く見えます。フラッシュ上では情報的なものです。私は16倍の差を計測し、読み取りペナルティがなく、より断片化したファイルの方がランダムプロファイルが速く、バックグラウンドスキャン時間という小さな単一コストを確認しました。その数値を「修正」するツールは、代わりに4分の1テラバイトのフォークを収める余地がないプールへと非共有化します。

だからRediaccは断片化を報告して説明し、それに対して操作するボタンを提供しません。誠実なエンジニアリングの答えは、反射を自動化するのではなく、前提を計測することでした。