LoginSignup
70
68

More than 5 years have passed since last update.

MongoDB 3.0のWired Tigerのデータ圧縮とメモリ使用量制限を検証してみた

Last updated at Posted at 2015-06-25

MongoDB 3.0から従来のmmapベースのストレージエンジンから、Wired Tigerというストレージエンジンが使えるようになった。

主な変更点は以下のとおり。

  • mmapベース
    • MongoDB ver 3にはMongoDB ver 2にあったmmapベースに改良を加えたものが乗っている
    • MongoDBはOSのmmap()システムコールを読んで、OSにデータファイルをメモリに載せてもらう。
    • MongoDBはメモリを管理しない。OS任せ。
    • メモリ使用量を制限できない。OSから与えられるだけ使う。
    • メモリがいっぱいになるとOSが適宜スワップさせる
    • データ圧縮できない
    • コレクションレベルロック(ver 2のころはデータベースレベルロック)
    • インデックスとデータは分離できない(同一ファイル内に混在)
  • Wired Tiger
    • MongoDB ver 3からの新規
    • Wired TigerというストレージエンジンのOSSを組み込んでいる。
    • 自分でメモリを管理する
    • メモリ使用量制限もできる
    • メモリがいっぱいになるとWired Tigerが適宜スワップせせる。
    • データ圧縮できる
    • ドキュメントレベルロック
    • インデックスとデータを分離できる

今回は、メモリ使用量制限とデータ圧縮について調べる。

WiredTigerを指定して起動する方法

簡単に立ち上げるのであれば、mongodにオプションを渡せばOK

# ./bin/mongod --storageEngine wiredTiger 

細かく設定したいのであれば、以下の様な設定ファイル(yaml)を作りmongod-fで食わせて起動。

設定ファイル例

storage:
   dbPath: data                       //データディレクトリのパス
   engine: wiredTiger                 //ストレージエンジンの名称
   journal:
      enabled: false                  //journal(先行書き込みログ)を有効にするかどうか
   wiredTiger:
      engineConfig:
         cacheSizeGB: 1               //キャッシュサイズの上限
         statisticsLogDelaySecs: 0    //稼働統計ログをどれ位遅延して出すか
         journalCompressor: none      //journalの圧縮有無
         directoryForIndexes: false   //インデックスごとにディレクトリを分けるか
      collectionConfig:
         blockCompressor: none        //コレクションの圧縮 snappy と zlibが指定可能
      indexConfig:
         prefixCompression: false     //インデックスのプレフィックスの圧縮

メモリ使用量制限の検証

storage.wiredTiger.engineConfig.cacheSizeGBによりキャッシュサイズを制限できる。これ以上はメモリを使わなくなるということだろう。

storage.wiredTiger.engineConfig.cacheSizeGBを1GByteに設定して、MongoDBに適当にラッシュをかけてみる。

//100万文字(≒1M)の文字列を生成
> longstr = ""
> for(var i=0;i<100000;i++){longstr+="0123456789"}

//それを1万回(トータル10Gぐらい)挿入
> for(var i=0;i<10000;i++){db.hoge.insert({a:longstr})} 

> db.stats()["dataSize"]
10000300000 //10Gぐらい入った

この時点では物理メモリ使用量は172Mでしかなく、設定値が有効かどうかのテストにはなっていないため、適当に中身を検索してメモリに載せていく。

メモリ載せるためにドキュメントを片っ端から検索していく。find().forEach()を使って中身のvalueのサイズを表示させる。

> db.hoge.find().forEach(function(doc){ print(doc["a"].length);} );

以下はmongostatの抜粋だが、ご覧のとおり、途中でメモリ使用率が頭打ちになっていることがわかる。これは今まではなかった動きだ。

(mongostat抜粋)
getmore command % dirty % used flushes  vsize    res 
      0     1|0     0.0    1.3       0 476.0M 172.0M 
      9     2|0     0.0    5.3       0 476.0M 177.0M //クエリ開始
     12     1|0     0.0   11.3       0 492.0M 232.0M //物理メモリ使用量が増えていく
     21     2|0     0.0   21.9       0 596.0M 332.0M 
     20     1|0     0.0   32.0       0 692.0M 428.0M 
     32     1|0     0.0   48.2       0 844.0M 583.0M 
     24     2|0     0.0   60.3       0 956.0M 697.0M 
     19     1|0     0.0   69.9       0   1.0G 791.0M 
     17     2|0     0.0   78.5       0   1.1G 873.0M 
     19     1|0     0.0   76.6       0   1.1G 893.0M 
     23     1|0     0.0   78.7       0   1.1G 903.0M //900Mで頭打ちになった。
     24     2|0     0.0   70.6       1   1.1G 903.0M 
     24     1|0     0.0   72.6       0   1.1G 902.0M 
     23     2|0     0.0   74.6       0   1.1G 902.0M 

ちなみに、設定値を2Gにしてみた結果は以下のとおり

(mongostat抜粋)
getmore command % dirty % used flushes  vsize   res 
      4     2|0     0.0    1.2       0 297.0M 73.0M //クエリ開始
     24     1|0     0.0    7.3       0 417.0M 189.0M
     26     2|0     0.0   13.8       0 537.0M 313.0M
     29     1|0     0.0   21.2       0 681.0M 453.0M
     22     1|0     0.0   26.7       0 785.0M 559.0M
     22     2|0     0.0   32.3       0 889.0M 665.0M
     27     1|0     0.0   39.1       0 1017.0M 795.0M
     21     2|0     0.0   44.4       0    1.1G 895.0M
     19     1|0     0.0   49.2       0    1.2G 987.0M
     23     1|0     0.0   54.8       0    1.3G   1.1G
     27     2|0     0.0   61.8       0    1.4G   1.2G
     28     1|0     0.0   68.9       0    1.6G   1.3G
     16     2|0     0.0   72.8       0    1.6G   1.4G
     31     1|0     0.0   70.7       0    1.8G   1.6G //1.6Gで頭打ちになった
     40     1|0     0.0   78.1       0    1.8G   1.6G
     38     3|0     0.0   75.5       0    1.8G   1.6G
     38     1|0     0.0   79.9       0    1.8G   1.6G

ご覧のとおり、1.6Gで頭打ちになった。

今回の実験結果をまとめると

  • 1Gに設定すると、メモリ使用量は900Mで頭打ち
  • 2Gと設定すると、メモリ使用量は1.6Gで頭打ち

となる。設定値と頭打ちの値が微妙にずれている理由はわからずでした。。。

データ圧縮の検証

storage.wiredTiger.collectionConfig.blockCompressorのオプションで、コレクションの圧縮ができる。デフォルト値はsnappyで、snappyアルゴリズムによる圧縮・伸長が行われる。このアルゴリズムはgoogleが開発したもので、圧縮率はそこそこだが圧縮・伸長速度が速いらしい。他にもおなじみのzlibが選択できる。また、圧縮しないnoneも選択可能だ。

データ圧縮率と書き込み性能を計測

3つの圧縮方式ごとに、キャッシュ制限の検証と同じクエリ(10G程度の挿入)を行ってみた。結果は以下のとおり。

表1:10G程度挿入した時のデータ容量と挿入時間

圧縮方式 データ容量(byte) 挿入時間(sec)
none 10,038,214,656 170
snappy 491,556,864 109
zlib 41,123,840 286

ご覧のとおり何も圧縮しないnoneは10Gであるのに対し、snappyは490Mに圧縮されている。挿入にかかった時間も短い。一方zlibは41Mとかなり圧縮されているが、挿入にかかる時間は長い。

よってまとめると、

  • none: 圧縮せずに、挿入は普通
  • snappy: それなりに圧縮して、挿入が速い※
  • zlib: かなり圧縮して、挿入が遅い

となる。前評判通りだ。

※(2015/9/12追記) 「挿入が速い」と記載したが、必ずしもそうではない。当然圧縮する方が余分に計算するため、計算時間はかかる。しかし、その分データ量が減ってディスクIOは減ることによりIO時間は減る。この二つのバランスによって挿入の速さが決まる。今回は文字列という圧縮しやすいデータでありかなりデータ圧縮ができたため、ディスクIO削減による時間削減が圧縮の計算時間よりも大きかったため、トータルで速かったと思われる。

読み込み性能を計測

また、読み込みについても調査してみた。MongoDBを止めて一旦メモリを空にしてから、下記のクエリを用いて約5Gのデータをメモリに載せるまでの時間を計測した。

//1Mのドキュメントを5000個メモリに載せる
db.hoge.find().limit(5000).forEach(function(doc){ print(doc["a"].length);} );

結果は以下のとおり

表2:約5Gのデータをメモリに載せるまでの時間

圧縮方式 読み込み時間
none 62秒
snappy 53秒
zlib 59秒

予想通り、snappyが最も高速となった。しかし大きな差ではないため、データ伸長の時間が支配的ではないのかもしれない。

ちょっと自信はないが、まとめると

  • none: ディスクからメモリへの読み込みは普通
  • snappy: ディスクからメモリへの読み込みがやや早い
  • zlib: ディスクからメモリへの読み込みがやや遅い

ということなのだろう。

また、上記の実験では、どの方法でも利用するメモリ量は等しかった。
つまり、 ディスク上のデータが圧縮されたからといって、メモリ使用量が減るわけではない のでご注意。

テストした環境

  • OS: CentOS release 6.2 (on CentOS 7.0 KVM)
  • RAM: 14G 割り当て
  • CPU: Core i5 660 3.33GHz 4 Core割り当て
  • FS: ext4
  • MongoDB 3.0.4 (journalはオフ)
70
68
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
70
68