LoginSignup
5
6

More than 5 years have passed since last update.

MongoDBベンチマーク 1インスタンスで8万insert/秒達成

Last updated at Posted at 2016-12-25

この記事ではAWS ec2上のCentOS7にインストールしたGo1.7+mgoと、MongoDB3.2を使っています。

MongoDBは、遅い遅いと言われているとともに、シャーディングが複雑な割には性能が出ないとも言われています。しかし、AWS ec2でベンチマークしてみた結果、そこそこの性能が出せました。クラウドによってスケールアップが容易になったため、シャーディング無しの1インスタンスでも、大抵のサイトでは十分に活用できるのものと思います。

ベンチマーク条件

クライアント側はGo1.7+mgo、サーバ側はMongoDB3.2として、それぞれにec2のインスタンスを割り当てて、ネットワーク越しに1KBのドキュメントを20万回insertして、ベンチマークしました。

クライアント側のコードを示します。

mongo_bench.go
package main

import (
  "fmt"
  "strings"
  "os"
  "strconv"
  "time"
  mgo "gopkg.in/mgo.v2"
)

const lenDummydata = 970   // create 1Kbyte-size bson

type InsertDoc struct {
  D string `bson:"d"`
}

func main() {
  // confirm arg
  maxSessions := 100  // arg #1
  maxGoroutines := 10000  // arg #2, shold be a multiple of maxSessions
  numInserts := 200000  // arg #3, shold be a multiple of maxGoroutines
  if len(os.Args) >= 2 {
    maxSessions, _ = strconv.Atoi(os.Args[1])
  }
  if len(os.Args) >= 3 {
    maxGoroutines , _ = strconv.Atoi(os.Args[2])
  }
  if len(os.Args) >= 4 {
    numInserts, _ = strconv.Atoi(os.Args[3])
  }
  fmt.Println("maxSessions :=", maxSessions, "maxGoroutines :=", maxGoroutines, "numInserts :=", numInserts)

  // clear mongodb
  session, err := mgo.Dial("mongodb://172.31.18.129/")
  if err != nil {
    panic(err)
  }
  COL := session.DB("test").C("COL")
  COL.DropCollection()
  // create InsertDoc
  var doc InsertDoc
  doc.D = strings.Repeat("0", lenDummydata)
  // start timer
  time_start := time.Now()

  // multiplex mgo sessions
  for i := 0; i < maxSessions; i++ {
    go func() {
      session, err := mgo.Dial("mongodb://172.31.18.129/")
      if err != nil {
        panic(err)
      }
      COL := session.DB("test").C("COL")
      // multiplex goroutines
      for j := 0; j < maxGoroutines / maxSessions; j++ {
        go func() {
          for k:= 0; k < numInserts / maxGoroutines; k++ {
            // insert document
            err := COL.Insert(&doc)
            if err != nil {
              panic(err)
            }
          }
        } ()
      }
    } ()
  }
  // wait all docs inserted
  n := 0
  for n < numInserts {
    n, _ = COL.Count()
    time.Sleep(10 * time.Millisecond)
  }
  // print result
  fmt.Println(n, "docs inserted")    
  duration := time.Now().Sub(time_start).Seconds()*1000
  fmt.Println("TOTAL:", int(duration), "msec", int(float64(numInserts) * 1000 /duration), "docs/s")
}

$ go run mongo_bench.go DBセッション数 goroutine数 insert回数でベンチマークを実行します。後ろの引数は省略可能です。

goroutineの完了待ち合わせには、sync.WaitGroupを使うのが常套手段ですが、単純にMongoDB側の書き込み数を監視した方が、若干速くなるようです。こちらの方が非同期書き込みっぽいですね。

MongoDB側は、デフォルトindexのみで、journalingはenableです。他で試してみたところ、indexを増やすと、若干(数%)書き込みが遅くなるようです。またsnappyによる圧縮により、30%程度まで実データが圧縮されているようで、ストレージ性能の制約が出にくくなっていると思います。insert文字列を乱数生成することで影響を減らすことはできますが、乱数生成のオーバーヘッドを考慮して、今回はそこまではやりませんでした。

結果と考察

以下に結果を表にまとめます。
AWS ec2のインスタンスタイプ、DBセッション数、goroutine数を変えて、様々なベンチマーク結果を比較しています。

秒間insert数の平均値
(d/gは、d:DBセッション数、g:goroutine数)
(インスタンスタイプはクライアント〜DBの双方で同一)

インスタンスタイプ コア数 1/1 1/100 100/100 100/10000
t2.small 1 2,014 24,547 9,917 10,293
t2.medium 2 2,716 23,114 21,986 18,665
t2.xlarge 4 3,671 18,623 59,356 33,101
t2.2xlarge 8 3,727 21,828 84,428 58,290
m4.4xlarge 16 4,629 19,986 74,102 69,072

DBセッションを貼る本数、goroutineの多重数によって、結果が微妙に異なります。特に、DBセッション数=1のままでは、マルチコア環境でもMongoDB側が1コアしか利用されないため、性能がスケールしないことに注意が必要です。

DBセッション=goroutine数=100とすると、比較的良好な結果が出るようです。t2.2xlargeで8万insert/秒を達成しました。

一方、お金をかけてm4.4xlargeも試してみましたが、さらに速くはなりませんでした。t2.2xlargeとt2.xlargeの比率も倍増していませんので、CPU以外のボトルネックに到達したのかもしれません。類似の前提条件で10万insert/秒以上を出せる方がいれば報告してほしいものです。

5
6
0

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
5
6