2013年9月4日水曜日

OpenNVMってなんぞや

久々にブログでも書こうかなっと。

今回は、最近出てきた OpenNVM というものを紹介してみたいと思います。



OpenNVM :: Welcome to the OpenNVM Project

http://opennvm.github.io/



  • フラッシュメディアに新しいアクセス手段が求められる理由
  • OpenNVM
    • アトミックライト
    • バッチ インターフェイス
    • スパースアドレス空間とブロックの割り当て状況の取得
      • ファイルシステムでのスパース活用例
      • データキャッシュでのスパース活用例
  • NVMKV
■ フラッシュメディアに新しいアクセス手段が求められる理由

HDDよりも高速として知られるSSDは、フラッシュメモリを従来どおりの「ブロックデバイス」としてアクセスできるようにして後方互換性を維持していますが、フラッシュメモリのポテンシャルは現状オペレーティングシステムが持つ、レガシなAPIだけでは発揮できません。

これは音楽メディアの変化に例えるとわかりやすいかもしれません。はるか昔は蓄音機で音を記録することができました。その後にレコードが出てきたとき、人々は針をレコード盤の上において選曲をしていました。カセットテープになると再生、巻戻り、早送り、そしてカセットのイジェクトという操作方法に変わりました。今ではデジタルオーディオプレイヤーがあり、曲を自由に選曲できるだけではなく、アルバムのサムネイルや歌詞情報も保持できます。これらは記憶メディアの特性はレコード盤から磁気テープ、そして今ではハードディスクやフラッシュメモリといったランダムアクセス可能なメディアに変わってきたためです。たとえフラッシュメモリやハードディスクを使っていても、音楽プレイヤーが「再生」「巻戻し」「早送り」ボタンしか持っていなければ、記憶媒体が進化しても音楽の楽しみ方に変化は起きなかったでしょう。その時々に、そのメディアに適したアクセス方法とインターフェイスが提供されているからこそ、今のような充実した音楽プレイヤーが存在していると言えます。


そこで出てきた OpenNVM は、コンピュータプログラムに対してフラッシュメモリへのアクセスをより自由に実現するためのインターフェイスを定義するプロジェクトです。


■ OpenNVM

ハードディスクの時代では回転型の磁気メディア上にヘッドをあてて読み書きするという特性から「シーク」「読み込み」「書き込み」という3つの操作があり、またそれは(ヘッドがメディアあたり一つしかないので)事実上直列化されるという前提がありました。またシークの所要時間が大きくなりがちだったので、一度シークさせたらいかにまとめて効率的にデータを読み書きするかがスループット最大化のポイントです。

これに対して、フラッシュデバイスの場合、記憶メディアはメモリ素子なので「シーク」に相当する操作のコストが非常に低く、事実上無視できるレベルです。また、例えばメモリ素子からデータを転送したり、システムバスを介してデバイスとプロセッサ/メモリ間のデータ転送時間コストのほうが大きくなるため、データをまとめて読むより本当に必要なデータのみを転送するアプローチのほうが向いており、ミドルウエアやアプリケーションのチューニングポイントになったりしています。

そこで OpenNVM では、さらに一歩踏み込んだ新しいアクセスインターフェイスを決めたり、それに関連するライブラリを用意したりしています。

アトミックライト

アトミックライトとは、複数の書き込み操作をグループ化して行う仕組みです。アトミックライトによりグループ化された書き込み操作は、まとめて反映されることが保証されます。

ハードディスクの設計では、論理ブロック番号(LBA)で特定される「セクタ」の位置が、記憶メディアであるディスク上で決まっていました。しかし、フラッシュデバイスの場合は内部でデータベースのログ構造をような仕組みが動いていて、デバイス内部ではLBAと実際のメディアの書き込み位置に関係がなく、常に追記書き込みで片付けていたりします。だったら、複数の書き込みを1トランザクションとして処理してあげればいいじゃん、というのが基本的な発想です。



アトミックライトは SCSI を制定する T10 Technical Committee ですでに標準化されているので、今後様々なフラッシュデバイス、またストレージコントローラがこういったトランザクショナルな書き込みに対応してくるのではないかと思われます。

バッチ インターフェイス

ハードディスクの時代はシーク操作のコストが非常に重かったので、スループットを最大化しようとするといかに連続の読み書きをし続けるかという点がポイントでした。このためI/Oはディスクあたり数百回/秒程度しか発生しませんでした。しかし現在では、 SSD をはじめとするフラッシュデバイスは、数千回/秒どころか数十万回/秒の読み書きを行います。そうすると今オペレーティングシステムにある、読み書きを1回だけ実行する read()/write() のようなシステムコールでは、「ほしいデータを様々なところにピンポイントに書き込む」ような利用では効率がわるくなります(システムコールによるCPUのステート切り替えでサイクルを無駄に消費してしまいます)。この問題を解決するため OpenNVM では、複数の書き込みやTRIM操作をまとめて発行できるバッチ インターフェイスが備わっています。

スパースアドレス空間とブロックの割り当て状況の取得

ハードディスクでは記憶メディア上にあらかじめセクタが割り当てられているため、「セクタが存在しない」という状態は存在しません。ただ、フラッシュデバイスでは、論理ブロック番号(LBA)に対して動的に物理的なフラッシュメモリがあてがわれる仕組みが一般的です。だったら、ハードディスクのようにLBAは0からnまでという制約をつけなくてもいいじゃん、というのがフラッシュデバイスレベルのスパース空間の考え方です。

もし100GBのフラッシュデバイスがあるとします。ハードデイスクやSSDの場合はLBAとして0から順番に100GB分が割り当てられますが、スパースの考え方では0~100GB分の領域以外のところにもデータを書き込めます。たとえば0~50GB分のアドレス空間と1000GB~1050GB分のアドレス空間に各50GB、合計100GBのデータを置くといったことを許してあげましょう、ということです。フラッシュデバイスの中ではどっちみちLBAと物理アドレスの関係は動的にマッピングしているのですから、スペック容量にあわせてLBA範囲を制限しているのをやめるだけで、別に難しいことではありません。

また、フラッシュデバイスの場合、「LBAにメモリが未割り当ての状態」「LBAにメモリが割り当てされている状態」という状態が存在し得ます。
特にスパースの考え方で仮想化されたLBAを使うのなら、どのLBAにデータがマップ(割り当て)されているかという情報は役に立ちます。

ファイルシステムでのスパース活用例

スパースの考え方をデバイスレベルで提供すると二次記憶のしくみが激変します。現在、いわゆるファイルシステムというのは、ディスク上の限られたアドレス空間にどうやってデータを詰め込むかという観点で非常に複雑な作りになっています。これに対して、デバイスがスパースを許すと


  • 最初の1GBの仮想アドレス空間にはメタデータを置きます

  • 次の1GBの仮想アドレス空間にはファイルの一覧を置きます

  • 頭から1TBの仮想アドレス空間からファイル実体を置きます

  • ファイルにはファイル番号(inode)をつけて、各ファイルの位置は(inode+1)*1TBの位置に置きます

といった感じにルールを決めれば、割と簡単にファイルシステムを作れてしまいます。これはコンピュータやオペレーティングシステムが実現している仮想メモリの仕組みを記憶媒体にも適用しただけなのですが、SSDではハードディスクとの互換性を重視してLBA 0〜容量分の範囲しか使わせてくれないので、こういったことができていません。
 


データキャッシュでのスパース活用例

たとえば、動画サイトでフラッシュのスパース空間にデータをキャッシュする仕組みを作るとします。

従来の考え方だと、恐らくハッシュ的なものやリスト的なものでどの動画がどこにキャッシュされているかとかを頑張って管理することを考えるでしょう。もしスパースとマップ状況の確認APIがある前提で、動画には必ずユニークなID番号がついていて、かつ最大サイズは1GB以内だと決めて、さらに

動画のキャッシュ位置 = ID * 1GB

というルールを決めたとしましょう。すると、スパース空間およびブロック割り当て状況の取得ができると、ファイルシステムなしでも以下のようなコードの書き方ができます。

// 動画ファイルの ID 番号

int id;

off_t cache_offset = (off_t) id * 1024 * 1024 * 1024 * 1024;



// 計算したオフセット位置にデータがあるか確認

if (cache_offset にデータがあるかデバイスに確認) {

    // ToDo: キャッシュ上のデータを読み込む

} else {

    // ToDo: キャッシュ上にデータがないのでバックエンドサーバから取得する

    if (デバイスの容量がいっぱいいっぱい) {

   
 // ToDo: 捨てるデータを決めて TRIM を発行

    }

    // ToDo: キャッシュ上にデータを書き込む

}


 
スパースって便利ですね。

■ NVMKV (Key-Value Store)

NVMKV は、上記のアトミックライトとスパースの仕組みを組み合わせて作られているキーバリューストアのAPIです。以前、個人的にNVMKVになる前の限定公開バージョンを使ってみたことがあります。NVMKVはCのインターフェイスがありますが、Cでごりごり書くのがめんどくさかったので、上記のテストを行うときは Perl 用のXS を書いて試していました。こんなかんじです。

 ...
        $start_time = Time::HiRes::time;
        my @l = ();
        foreach (1..10) {
                my $from = $next;
                $next += $keys_per_thread;
                my $to = $next - 1;
                push @l, threads->new(\&load_data, $from, $to);
        }

        # wait for workers
        foreach (@l) { $_->join; }
        $end_time = Time::HiRes::time;
        $t = $end_time - $start_time;
        printf("%d keys loaded in %0.3f seconds, %d keys/s\n", $next, $t, ($next) / $t);

 ...

sub load_data
{
        my ($from, $to) = @_;

        my $DMY = 'x' x 10;
        print "load_thread: $from $to START\n";
        my $err = 0;
        foreach $d ($from..$to) {
                my $r = KVS::Put($kv_id, $pool_id, "$d", "$DMY");
                $err += 1 if ($r < 0);
        }
        print("load_thread: $from $to FINISHED, errors=$err\n");
}


これをスレッドでいくつか平行で回したらこうなった(10億キー書き込んだけど、出力時にロードしたキー数からうっかり1引いてしまった:D)

999999999 keys loaded in 3546.419 seconds, 281974 keys/s

フラッシュデバイスへの同期PUT操作が毎秒28万回できていました。現在世の中で使われているKVSのミドルウエアはDRAMをキャッシュとして使うのでGET操作はもっと高速にできるでしょうが、毎秒30万回のPUT操作(永続化込みで)ができるKVSというのはかなり衝撃的です。言い換えれば、KVSの永続化層をこれに置き換えることなどができれば、KVSミドルウェアの性能が劇的にアップするでしょう。

■ まとめ

こんなかんじで、 OpenNVM って一体どんなものなの?ってのを紹介してみました。フラッシュメディアは従来のハードディスクと違う特性をもっているので、それを活用してあげるとより高い性能が得られます。 OpenNVM には、フラッシュの機能を活用するために必要なインターフェイス、そのインターフェイス上で利用できるライブラリなどが含まれています。