ニフクラ ブログ

ニフクラ/FJ Cloud-Vやクラウドの技術について、エンジニアが語るブログです。

ニフティクラウドでMongoDBは使えるか?

>>>>>> こちらはゲストによる寄稿記事になります

<2011/06/01 17:30 追記--

※ MongoDB JPの管理人 @doryokujin さんが指摘の記事を書いてくださいました
>>
「ニフティクラウドでMongoDBは使えるか?」を読んで、僕なりの考察を書いてみた

出来る限り勉強はいたしましたが、考察の足りないところ、明確にしておくべきところが不足しておりました。ユーザの方に誤解を与えてしまうことお詫びいたします。
是非、あわせてお読みください。

--2011/06/01 17:30>
 

 こんにちは、ニフティのモリフジです。

分析などを行っているうえでMongoDBを調べています。
寄稿記事として、こちらのブログで執筆させていただきました。

 今回、「ニフクラ上でMongoDBを動作させたときのパフォーマンス」についてご説明したいと思います。

■MongoDBとは

  MongoDBはMySQLのようなRDB、memcachedのようなKVS、HBase/CassandraのようなDHTとは毛色の違う、「document-oriented」なデータベースです。

注:今回の記事は、必要に応じてMongoDBの紹介や、利用方法に少し触れますが、インストールの手順やその他設定についての記事ではありません。

Document-orientedって?

 特徴としては公式ドキュメントでは下記4点があげられています   

  • ドキュメント(オブジェクト)がプログラム言語のデータ型にうまく対応している 
  • 埋め込みドキュメントと配列によって、ジョインの必要性が低減している 
  • スキーマの進化を容易にする動的型付け(スキーマレス) 
  • 高いパフォーマンスと、容易なスケーラビリティを確保するためにジョインや複数ドキュメントのトランザクションは無い

  •  私見も交え、端的にいってしまうと

    メリット
  • スキーマレスなので機能拡張の要件や、開発中・運用初期のAdhocなスキーマチューニングなどには「超」柔軟に対応できます
  • 面倒なこと考える前にひとつのドキュメントに詰め込めばいいです。ブログだったらエントリーにコメントも全部!


  • デメリット
  • トランザクションが無いために課金などのシステムにも使えません
  • 面倒だったら突っ込めっていったけど、ドキュメントあたり4MB(v1.8以降から16MB)しか使えない 
  • 埋め込みと正規化のハザマで苦しむことになる

  •  という、ある種の男らしい決断を必要とする反面、男らしく調整しながら突き進むときには最適なデータベースです。
    ■ShardingとReplica

      上記4番目の特徴としてあげた「容易なスケーラビリティ」を実現するのが「Sharding」と「Replica」機能です。これらの機能を利用する・しないによって書き込み性能・読み込み性能、次に紹介するmapReduceの性能が変わってきます。

    Sharding

      ドキュメントデータを複数のサーバで分割して保持する機能をShardingといいます。指定したドキュメントのキー(例えばユーザリストであれば名前など)をA-Dまではサーバ1で、E-Oまでをサーバ2、P-Zまでをサーバ3で、という具合に分割して保持します。これにより、書込性能・検索性能の向上を実現しています。

    Replica
      ドキュメントを保持しているサーバが破損した場合に備え、また、読み込み性能をバランスすることを目的として、保持しているデータを複数台のサーバにコピー(replication=複製)して保持しておく機能です。 
    ■MapReduce

      MongoDBには、もう一つ非常に大きな武器があります。それがMapReduceという機能です。   Google BigTableやHadoopといったキーワードを耳にした方ならなじみのあるキーワードで、MapReduceを利用される方は年々増加の一途をたどっていますが、一般的にはまだまだ認知されていないかと思います。

     MapReduceは巨大なデータの分析・解析という用途において非常に強力な力を発揮します。下記のような処理の一連の流れ全体で「MapReduce」と呼び、大きなデータを分割し、小さな単位で処理し、処理して縮めた結果を分析することで、巨大なデータの解析を可能にしています。

  • 巨大なデータを何らかの方法で切り分け、いくつかのブロックに分割します
  • ブロックごとに処理を行いKeyとValueのペアとして出力します(Map) 
  • ブロックごとに処理されたKeyとValueのペアを、Keyごとに再分割します(Shuffle & Sort)
  • Keyごとに収集して処理を行います(Reduce)
  • Keyごとに収集されたデータに対して統計的な処理を行います(Finalize)


  •  この機能は、当然、1台のマシンで行うときには、あまり意味がなく、Shardingを実現し、複数台のマシンがあるときに、より有効に機能します。分割されたブロックはそれぞれ異なるサーバでMap処理することが可能なためI/Oを節約し、Shardingで分割したサーバの台数分のCPUやMemoryで処理することが出来るからです。
     

     少し、MongoDBで実現できる機能について少しお話しておきます。

    対話shell

      MongoDBをインストール後、コンソールから$MONGO_HOME/bin/mongoを実行してみてください。対話shellが起動します。

    
    $ mongo
    > show dbs
    admin
    config
    mydb
    > use mydb
    switched to db mydb
    > show collections
    logs
    system.indexes
    >

     こんな感じで

  • db一覧を取得する(show dbs)
  • 利用するdbを変更する(use)
  • collection一覧を取得する(show collections)

  • などを行うことが出来ます。

     また、困ったときには

    
    > help
    db.help()                    help on db methods
    db.mycoll.help()             help on collection methods
    (略)
    > db.help()
    DB methods:
      db.addUser(username, password[, readOnly=false])
      db.auth(username, password)
    (略)
    > db.logs.help()
    DBCollection help
      db.logs.find().help() - show DBCursor help
      db.logs.count()
    > rs.help()
      rs.status()                     { replSetGetStatus : 1 } checks repl set status
      rs.initiate()                   { replSetInitiate : null } initiates set with default settings > db.listCommands() addShard: no-lock adminOnly  slaveOk   add a new shard to the system applyOps: no-lock   no help defined

     などと、対話shellでのhelpも充実しています。   

    豊富なコマンド群
     上記の対話shellを起動するmongo、サーバ起動のためのmongod、shardingのクライアントとして機能するmongos以外にも多くのコマンドが存在します。

    mongoimport
     データのバッチ投入に便利です。データベース/コレクションを指定し、json以外にもtsv, csvのデータも投入でき、また、その際のfield定義なども指定することが容易に出来ます。また、通常のファイルだけではなく標準入力も受けるため、非常に使い勝手のよいツールになっています。
    
    $ tail -f /var/log/access.log | perl ./filter.pl | mongoimport -d mydb -c mycol -f timestamp,host,path,ua,ip

    mongoexport
     データベース/コレクションを指定して、バックアップ目的などでデータの出力が可能です。JSONとcsvを選択できます。また、指定した条件にマッチしたドキュメントのを指定したフィールドだけ取得することも可能です。

    mongodump
     データベースをバックアップ目的でdumpすることが可能です(データはbinaryで保存されます)。指定したドキュメントだけを取得することが出来ます。

    mongorestore
     データベースのリストア目的でdumpしたデータから復旧します。
    ■Benchmark

      下記のような条件およびプロセスでベンチマークを行い、プロセス完了までの時間を計測しました。

    データ

      twitterのtweetを1.4GB (delete含めて707,259tweet)   

    条件 
  • shardingの有無(1台=無 vs. 3台) 
  • stripingの有無(有 vs. 無) 
  • ニフティクラウドスペック(mini vs large16)
  •   注:stripingについてはニフティクラウドにMySQLは載せられるのか?パフォーマンス大検証!をご覧ください

    <2011/06/01 17:30追記>

    shardのケースについて

    shardkeyにはtweetの増加の少ないものとして「userのid」、時間経過に伴う増加の多いものとして「tweetのid」を設定しました。
    また、indexは貼らずに検証しました(意図的に貼らなかったのですが、張ったケースを比較すべきでした。)

    プロセス 
    mongoimport
     おそらく日々のバッチ投入、またはログからのアクション更新のペースの頻繁なリテラシの高い企業ではリアルタイムデータ投入が行われるでしょう。このときにデータの投入がそもそももたるようでは困ります。したがってデータ投入はIOやshardingを測定する上で重要な項目です。

    find
     コレクションからデータを取得するコマンドです。MongoDBの場合は、ドキュメントを縦横無尽に条件付けてフィルタリングができるのでこの性能も無視できない重要な性能です。
  • 1条件 find({source:'web'})       
  • ネスト条件 find({'user.lang':'ja'})       
  • 否定 find({'geo':{$ne:null}})       
  • サイズ find({'entities.hashtags:{$size:2}})       
  • 比較 find({'retweet_cnt':{$gt:1}})   
  • mapreduce
     MongoDBを十二分に活用するのであれば、避けられないコマンドです。簡単な解析に利用することが出来ます(簡単、と書きましたがかなり使えます)。

  • 単純なグループ集計(source集計)
  • 単純なグループ集計(5分ごとのtweet数集計)       
  • 2条件のグループ集計(1時間当たり・1人当たりのtweet数集計)       
  • 少し複雑な集計(1時間当たりのhashtag集計)       
  • 少し複雑な集計(共起するhashtag集計)   
  • 結果

      実際に行った結果は下記の通りです(単位=ms, n=5)。

    スペックminilarge16
    sharding
    striping
    mongoimport549.201 520.438 630.101 690.042 259.671 104.757 263.050 193.803
    find38.378 42.293 13.618 14.998 1.044 1.030 1.124 1.132
    mapreduce226.64 229.46 107.39 106.85 52.53 52.10 26.08 26.08

    考察

    全体

      全体的にざっくりと見ると、当然といえば当然ですが、VMのスペックがimport/find/mapreduceに大きなパフォーマンスの差に影響を及ぼしています。

     また、IO性能に関わるstripingに関しては、import、特にlarge16以外ではほとんど意味のある効果を発揮していませんでした。これはminiの場合はIOよりもmemory/cpuがボトルネックになっているためだと考えられます。

     データサイズが1.4GBであるため、mini(memory=512MB)においてはfindやmapreduceにおいてIO性能の及ぼす影響が強いと予想していましたが、ほとんど見られませんでした。これは今後追求していければと思っています。

    mongoimport

     上記と重複しますが、スペックにおける影響が非常に強く、また、次に、large16のときにstripingの影響が出ていました。このimport処理では書き込み性能に影響が出るため、shardingの影響を予測していましたが、shardingによる性能の改善は見られないばかりか、1台で行った場合よりも劣化するという結果でした。

     この結果は、mongosまたはconfigサーバがボトルネックとなりうるという要因が予測されます。今後、例えばmongosを分割し、それぞれに対して同時にimportを行い、全体としてのパフォーマンスの改善が見られるかを検討する必要があるかと思います。

    find

        IO性能に関わるstripingはほぼ、findにおいては影響を見せませんでした。またshardingによるfindの性能は、miniでは、およそ2倍?3倍程度の改善を示していましたが、large16ではほとんど効果が見られませんでした。同時に、stripingも影響があまり見られませんでした。

     今回の程度のデータサイズであれば、cpu/memory性能が十分に高ければ分散させてIO性能を稼ぐことにあまり意義が無いと考えられます。

    mapreduce

     今回、一番ベンチマークを取った甲斐があるプロセスでした。stripingについては性能にほとんど影響が見られませんでしたが、shardingの効果がよく出ており、簡単とはいえ集計や計算がcpu/memory依存で行われ、mapreduceを複数台で実現することの意味が見出せたものと考えられます。

      今回、すべてのデータについては、分散はほとんど見られなかったこともあり、ざっくりとした考察だけを行っていますが、いかがでしたでしょうか。実際に使う上でshardingを適切に配置し「Largeシリーズ」を使っていただければ。「Largeシリーズ」を使っていただければ、性能がスケールすることも含め、比較的性能的にもよいパフォーマンスが得られたのではないかと思っております。

     なにより、VMが苦手なIOにあまり依存しない、という結果が出たということは、従来のRDBと比べ、利用シーンを選びはしますがニフティクラウドを利用するベネフィットがあるのではないでしょうか。
     

    <2011/06/01 17:30追記>

    find系のクエリについてshardキーの有無の検証

     
    指摘されたとおりでやるべきでした。@doryokujinさんの記事をご参照ください。
     

    sharding環境の有無でmongoimportに差が出なかったのは恐らくshardKeyの問題ではないでしょうか。前述しましたが、 shardingは事前に設定したshardKeyに従ってデータを各shardに分割しています。例えばこのshardKeyの値が "_id" を始めとした時間軸に対して増加するキーであった場合には、連続して挿入されるデータは分散されずに同じchunk、つまり同じshardに入ってしまいます。この場合にはshardingしていながら、実際にはシングルサーバーへ書き込みを行っているのと同じ、さらにはmongosサーバーを介することによってシングルよりも無駄なステップを消費しています。今回の結果の原因は、status_idをshardKeyとして使用しているような場合にはこれによるものと考えられそうです。

     
    途中の条件のところにも記載しましたが、今回はuser_idもkeyとしてセットしているため、分散は行われていると考えています。
    そのこともあり、シングルサーバーではないかと思っております(確認しようと思ったのですが、VMがなくなっていました><) 本来の、ボトルネックがどこにあったのかは、追検証できればと思っております。
     

    このようにMongoDBのmapreduceの特性を理解せずに高スペックマシンの方が高速に動作すると思い込んで毎月数十万を無駄する可能性があります。

     
    普段の開発では意識しているのですが、社員であること・モニター利用であることもあり、ご指摘の通り、費用対効果のことを失念していました。是非、御利用は計画的によろしくお願いいたします。