Skip to content

Storage Block Size and NVM Block Size

Takeru Ohta edited this page Oct 21, 2018 · 9 revisions

CannyLS内には互いに関係はしているが、役割が異なる二種類のブロックサイズというものが存在する:

  • NVMが要求するブロックのサイズ(ダイレクトI/O起因の制約)
  • ストレージが使用するブロックのサイズ(I/O効率向上とメモリ節約目的)

NVMが要求するブロックのサイズ

CannyLSでは、原則としてダイレクトI/Oが使用されているため、ファイルに読み書きする際には(特にLinuxの場合には)以下の値として、OSが要求する物理ブロックの境界にアライメントされているものを渡す必要がある(さもなければエラーとなる):

  • ファイルの読み書き開始位置および終了位置
  • 読み書きに使用されるメモリ範囲の開始アドレスおよび終了アドレス

Linuxの場合には、この物理ブロックサイズは、大抵のケースで512となるが、具体的なサイズはNonVolatileMemoryの実装によって変わる可能性があるためNonVolatileMemory::block_sizeというメソッドで、NVM側が自分で、I/O時に要求されるブロックサイズを利用側(主にStorage)に伝えられるようになっている。

なお、NVMのブロックサイズを意識した読み書き自体は、crate内部に閉じた話であるため、通常はCannyLSの利用者が気を払う必要はない(ただし、後述するように現状の実装では、NVMのブロックサイズがストレージのブロックサイズの下限となっているので、もし利用者が後者の値を明示的に指定したくなった場合には、前者の値を意識する必要が出てくる)。

参考: Man page of OPEN#O_DIRECT

ストレージが使用するブロックのサイズ

NVMが利用側に課す制約としての"ブロックサイズ"とは別に、ストレージも(こちらは主に性能向上目的で)そのインスタンス毎に固有なサイズのブロック単位で、各種データ管理や処理を行っている(各ストレージインスタンスが採用しているブロックのサイズはストレージのヘッダ部分に格納されている)。

明示的に指定されなかった場合には、ストレージのブロックサイズはNVMのそれと等しくなる。

ストレージでのブロックの利用箇所

(a) 各領域のアライメント

ストレージフォーマットで定義されている各領域の開始位置および終了位置は、そのストレージのブロック境界にアライメントされている。 ヘッダ系に関しては、本質的にはブロック境界に揃える強い理由はないのだが、実装を簡単になるため、一律でアライメントするようにしてしまっている。

(b) データ領域内の各lumpデータのアライメント

データ領域全体の開始位置と終端位置だけではなく、その中に格納される各lumpデータに関しても開始と終了位置は、ストレージのブロック境界にアライメントされるようになっている(その理由には後続部分で触れられる)。

これにより、ストレージのブロックサイズ(典型的には512バイト)にサイズが一致しないlumpデータに関しては、データ領域割当時にサイズの切り上げ処理が適用されることになるので注意が必要(その分だけ余計にディスクを消費することになる)。 特に、lumpのデータサイズがごく小さい(e.g., 数十バイト未満)場合には、データ領域に格納すると無駄が多くなってしまうので、代わりに埋め込みlumpを使用して、ジャーナル領域にデータを格納することが推奨されている。

(c) データ領域アドレスの単位としてのブロック

「各lumpがデータ領域内のどこに配置されているか」の情報を保持しているlumpインデックス、および、PUTされたlumpに対してデータ領域の保存先部分領域の割当を行うデータ領域アロケータは、どちらも(バイト単位ではなく)ブロック単位でデータ領域内のアドレスを管理している。

アドレスをブロック単位で扱っている理由は、アドレス表現に必要となるビット数を節約するためである。具体的には、この方式を採用したことで、最大32MBのlumpデータが16bitで、最大512TBのデータ領域が40bitで表現可能となっている(関連: Memory UsageIn Memory Lump IndexData Region Allocator)。 CannyLSではディスクアクセス回数を最小化するために、インデックスおよびアロケータの状態を全てメモリに載せるようにしているため、ここで必要となるビット数を削減することは、全体のメモリ使用量削減に大きく寄与することとなる。 ブロックサイズの下限が512に設定されている理由の一つもここに由来する(これ以上小さくすると、表現可能なlumpデータサイズの範囲が狭くなり過ぎてしまう)。

ストレージのブロックサイズの制約

現状では「ストレージのブロックサイズは、それが使用するNVMのブロックサイズの倍数ではなければならない」という制約がある。

この制約は、無駄なディスクI/Oを極力削減するために設定されている。
以下のような例を考えてみる:

  1. ストレージのブロックサイズが512、NVMのブロックサイズが1024、とする (上記制約が満たされていない)
  2. データサイズが100バイトのlumpのPUT要求を受け取った
  3. データ領域アロケータが、該当lumpを格納するための領域割当を行う:
    • データ領域アロケータは、ストレージのブロック単位で割当を管理している
    • そのため、この場合にはデータ領域内の512バイト分の部分領域が、新規lump用に割り当てられることになる
  4. 割り当てられた部分領域に、lumpデータが書き込まれる
    • ただし、NVMは1024ブロックを使用しているため、一回の書き込みの最小単位は1024バイトとなる
    • 仮に「サイズが1024バイトのゼロ埋めバッファを用意して、その先頭100バイトをlumpデータで埋めて書き込みを実施」したとする
      • これだと割当領域を超過先の512バイト分の領域を、ゼロで上書きしてしまうことになる
    • それを防ぐためには「最初にバッファを該当領域から読み込んだ既存データで埋めた上で、その先頭100バイトをlumpデータで更新して書き込みを実施」といった処理が必要となってしまう
      • 本質的には無駄な、読み込み用のディスクI/Oが追加で必要となる
      • この無駄を発生させなくするのが、上述の制約の目的

なお、ストレージインスタンスの作成後に、そのブロックサイズを変更することはできない(NVMのブロックサイズは、上述のストレージとの間の制約を崩さない範囲であれば変更可能)。

ストレージのブロックサイズはどういった時に変更すべきか

基本的には、デフォルト値(つまりNVMのブロックサイズ)を使用していれば問題ないが、以下のようなケースでは、ストレージのブロックサイズを明示的に増やしたくなるかもしれない:

  • データ領域アロケータが管理するフリーリストのフラグメンテーションを少しでも抑えたい
    • ブロックサイズは、アロケータの割当単位と等しく、それが大きいほどフラグメンテーションの数は減少する傾向がある
    • ただし、その分、割当てられたが実際には使用されないデータ領域内のスペースは増えることになる
  • lumpデータの最大サイズを大きくしたい
    • 理屈上は、ブロックサイズを倍にすれば、lumpデータの最大サイズも倍になることになる
    • ただし、実際に実施するためには実装の変更も必要
  • データ領域の最大容量を増やしたい
    • 理屈上は、ブロックサイズを倍にすれば、データ領域の最大容量も倍になることになる
    • ただし、実際に実施するためには実装の変更も必要
Clone this wiki locally