中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

あまり頻発するような問題ではないようだ

http://d.hatena.ne.jp/hiroppon/20151216/1450251504
以前、MongoDB3.0系のバックアップを取り上げた。

色々検証したが、データファイル自体が破壊されるような事は起きなかった。
中のデータがある程度おかしいのかもしれないし、極稀にファイル破壊が起きるのかもしれないが
あまり大きな問題では無い様な気がする。

バックアップができない!?

ご存知の様にMongoDBのバックアップは

  1. fsyncLock
  2. Snapshot
  3. fsyncUnlock

の流れなのだが、、
最近、MongoDB3.2 系のドキュメントにこんな事が書かれていた

Compatibility with WiredTiger
Changed in version 3.2: Starting in MongoDB 3.2, db.fsyncLock() can ensure that the data files do not change for MongoDB instances using either the MMAPv1 or the WiredTiger storage engine, thus providing consistency for the purposes of creating backups.

In previous MongoDB version, db.fsyncLock() cannot guarantee a consistent set of files for low-level backups (e.g. via file copy cp, scp, tar) for WiredTiger.

まじか・・・ありえん!!
3.0 系に移行したばっかなのに、3.2系にまた移れってのか!?
データのコンシステンシーが多少失われてもまあ許容するが、データファイルが破壊されると厄介だ。
検証中だが、まだ問題は発生してない。。
うーむ。。。

MongoDB3.0.xの設定ファイルテンプレ

Advent Calendar にも投稿した内容ですが、MongoDB3系の設定周り。
僕はこんな感じで運用してますよ。という位の内容。

/usr/local/mongodb/ 以下に構築する場合の設定周り

1. ディレクトリ構成

バイナリをDLして適当に配置してください。

# mkdir -p /usr/local/mongodb/conf
# mkdir -p /usr/local/mongodb/logs
# mkdir -p /usr/local/mongodb/tmp
# ln -s <path to data path> /usr/local/mongodb/data
# ln -s <path to bin path> /usr/local/mongodb/bin

2. Keyfileを作る

MongoDB3系からセキュリティ周りが厳しくなり、replica-setへJOINさせる際にkeyfileによる認証が必要になりました。
StandAloneの場合は不要です。

openssl rand -base64 741 > /usr/local/mongodb/conf/mongod.key

設定ファイルテンプレ

しっかり設定しなければならないのは以下の2点くらい

storage.wiredTiger.engineConfig.cacheSizeGB: 100

インデックス構築や、cursor処理、メモリリーク(orz...)でも消費されるので
現状ではメインメモリの80% 程度に調整しておいた方が良さそうです。

replication.oplogSizeMB: 102400

これは用途によって適当に。。

processManagement:
  fork: true
  pidFilePath: /usr/local/mongodb/logs/mongod.pid

net:
  port: 27017
  maxIncomingConnections: 1000000
  unixDomainSocket:
    enabled: true
    pathPrefix: /usr/local/mongodb/tmp/
  http:
    enabled: false
    JSONPEnabled: false
    RESTInterfaceEnabled: false

security:
  authorization: enabled
  javascriptEnabled: true
  keyFile: /usr/local/mongodb/conf/mongod.key

operationProfiling:
  slowOpThresholdMs: 1000
  mode: slowOp

storage:
  engine: wiredTiger
  dbPath: /usr/local/mongodb/data
  directoryPerDB: true
  indexBuildRetry: true
  syncPeriodSecs: 60
  wiredTiger:
    engineConfig:
      cacheSizeGB: 100
      statisticsLogDelaySecs: 0
      journalCompressor: snappy
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: snappy
    indexConfig:
      prefixCompression: true
  journal:
    enabled: true

replication:
  oplogSizeMB: 102400
  replSetName: MyRS
  secondaryIndexPrefetch: all

MongoDB3系(WiredTiger)の現状

ご無沙汰してます。

最近全然更新出来てない訳ですが、MongoDBに愛想が尽きて、離れていた訳ではありません。
むしろガッツリ嵌ってます。。

最近は MongoDB3 系 WiredTiger を使いながら頑張っている訳ですが・・・
キリの良い所で書こうと思っていたのに、メドが立たないので近況だけでも書いておこうかと思った次第。。

で、、mongo3... コイツすぐ落ちる!!

まあ、普通にメモリーリークですが、、ちょっと目を離すと

Tasks: 169 total,   1 running, 168 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12.4 us,  0.7 sy,  0.0 ni, 86.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:  62916396 total, 62567524 used,   348872 free,    13332 buffers
KiB Swap:        0 total,        0 used,        0 free.    60212 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
16865 zenclerk  20   0 60.906g 0.058t   2744 S   0.0 98.3  12323:54 mongod
  859 syslog    20   0  322440   6900    216 S   0.0  0.0   0:02.80 rsyslogd

おい!舐めんな!!!
60GB使い切るか..


で、、最近 3.0.7が出ました。release note

なかでもこの2つは注目です

//jira.mongodb.org/browse/SERVER-20159">SERVER-20159 Out of memory on index build during initial sync even with low cacheSize parameter: そう。index張るとメモリー使い切って死ぬ・・・(メモリー足りてもリークしてたっぽい)
//jira.mongodb.org/browse/SERVER-20091">SERVER-20091 Poor query throughput and erratic behavior at high connection counts under WiredTiger: コネクションが多いとクエリーが重くなる・・・

早速試してますが、まあ改善した感じがします。
そろそろマトモになって来た感がありますね。
本番に使い始めてもよいかも〜


ただ、この大物が控えているのでまだ安心出来ません。。

//jira.mongodb.org/browse/SERVER-20306">SERVER-20306 75% excess memory usage under WiredTiger during stress test: 多分、僕の環境で起きてるのはコレなんだよなぁ・・

でも、逆にあとコレだけ解決してくれれば、いい感じになるはず!

MongoDB 3.0 リリースノート斜め読み

遂にMongoDB 3.0 が正式リリースされました!!
例によってリリースノートを斜め読みします。
http://docs.mongodb.org/master/release-notes/3.0/


が、、最初に一言で纏めると、まあ、、

目玉機能はロックレベルの話だけですよー



でわ。。

Pluggable Storage Engine AP

以下の2つからストレージエンジンを選べる。

MMAPv1
これまでのストレージエンジン。デフォルト
WiredTiger
3.0から追加されたストレージエンジン
WiredTiger

MongoDBの全ての機能をサポートしている。

MMAPv1とフォーマットが違うので既存のアップデートの場合、移行する際に色々必要。

ドライバも最新に上げないとダメ。

ドキュメントレベルロックが可能!!

touchコマンドはサポートしてない

MMAPv1 Improvements

コレクションレベルロック!!

領域確保のロジックが変更された

  • 大きなファイルの扱いが賢くなった
  • paddingFactorが廃止された。やった!

元々paddingFactorは賢い実装では無い。
稀に起きるかもしれないgrowing documentの為に
大半のそれ以外の処理に負がある方法を取るなんて馬鹿げた話だ。

ReplicaSet

ノード数が最大50になった。割とどうでも良い。

ステップダウンの挙動が変わった

  • stepdown の前にmap-reduce, index build などの長時間処理を殺す
  • (stepdown直前に入ったデータが)ロールバックされるのを避けるため次のPRIMARY候補のSECONDARYが追いつくまで待つ。

レプリカセットにJOIN時の同期が早くなった

writeConcernのmajority はvoting node のmajorityになった。細かい。。が、なるほど。

コンフィグのバリデーションが厳しくなった

セカンダリのコレクションには必ず_id インデックスが作られる。(バグフィックスに近い)

Sharded Clusters

セキュリティー周り。細々改善。

Improvements

クエリープラン改善とexplain改善

ログ周り改善

mongo_tools 周り色々改善。
うれしいけど細かい!!

この辺り使い倒してる人がどれだけいるんだろうか?
ウチあの辺この辺とか位?w

dropIndex, dropCollection, dropDatabaseしたときにバックグラウンドで動いているcreateIndexは中止されるようになった。

shard key を含むインデックスはcover query になり得る。

geo系クエリー強化

aggregateに$dateToString 追加

$eq 追加?なんの意味があるの?

MongoDB Enterprise Features

パス

その他

2.6系からしかアップグレード出来ない。

3.0からは 2.6.5 までしかダウングレード出来ない。

2.6.8未満のmongo shell から3.0に繋げると色々問題。

MongoDB aggregation の検証

だいぶ空いてしまったが、久々の更新!

Aggregate周りを色々検証したので載せておく。

基本的なTAG構造。

TAGを扱う上でオーソドックスなクエリーと性能を調査。
性能は、people 2000万件、hobies 3200万件、完全ランダムデータで計測。
月数千円で手に入るコンピューティングリソースを使ってます。

Model

Mike

:性別:男
:趣味: 映画、テレビ

Cate

:性別: 女
:趣味: テニス

Bob

:性別: 男
:趣味: 映画、テレビ、テニス

Data

RDBMSではこんな感じの正規化をする。

people
_id name sex
1 Mike male
2 Cate female
hobbies
pid name
1 TV
1 cinema
2 tenis
3 TV
3 tenis
BSON

一方MongoDBではこんな感じで持つだろう

{
  _id: 1, 
  "name": "Mike", 
  "sex": "male",
  "hobbies": [ "cinema", "TV" ] 
}
{
  _id: 2, 
  "name": "Cate", 
  "sex": "female",
  "hobbies": [ "tenis" ] 
}
{ 
  _id: 3,
  "name": "Bob",
  "sex": "male",
  "hobbies": [ "TV", "tenis", "cinema" ] 
}

問題1

趣味TVの男性の数を抽出

得たい結果
count(people._id)
2
sql

説明不要・・・

SELECT
  COUNT(people._id)
FROM
people
INNER JOIN
  hobbies
ON
  people._id = hobbies.pid
WHERE
  people.sex = ‘male'
  AND
  hobbies.name = 'TV';
mongodb

なんの変哲も無いクエリーだが・・・

db.people.find(
  { sex: ‘male’, 
    hobbies: ‘TV’ }
).count();
性能

Mongo爆速!!
countを舐める処理が賢いのが要因。

mysql5.6 MongoDB 2.6.3
94sec 1sec

問題2

趣味毎の男性人数を集計

得たい結果
hobbies.name count(people._id)
tenis 1
cinema 2
TV 2
sql

普通のgroup by

SELECT
  hobbies.name,
  COUNT(people._id)
FROM
  people
INNER JOIN
  hobbies
ON
  people._id = hobbies.pid
WHERE
  people.sex = 'male'
GROUP BY
  hobbies.name
ORDER BY
  COUNT(people._id);
mongodb

hobbiesを一旦$unwindでバラす。
ちょっと独特なので下で解説

  db.people.aggregate([
   { $match: { sex: 'male' }},
   { $project: { hobbies: 1, count:{ $literal: 1 }}},
   { $unwind: "$hobbies" },
   { $group: {_id: "$hobbies", count: { $sum: "$count" }}},
   { $sort: { count: 1 } },
  ]).
解説:パイプラインの各段階でのデータ

FIRST

  { _id: 1, "name": "Mike", "sex": "male",  "hobbies": [ "cinema", "TV" ] }
  { _id: 2, "name": "Cate", "sex": "female","hobbies": [ "tenis" ] }
  { _id: 3, "name": "Bob",  "sex": "male",  "hobbies": [ "TV", "tenis", "cinema" ] }

$match: { sex: 'male' }
最初に絞る。

  { _id: 1, "name": "Mike", "sex": "male", "hobbies": [ "cinema", "TV" ] }
  { _id: 3, "name": "Bob",  "sex": "male", "hobbies": [ "TV", "tenis", "cinema" ] }

$project: { hobbies: 1, count:{ $literal: 1 }}
フィールドも絞る。$literalは2.6系から。SQLとは逆の発想。

  { "hobbies" : [ "cinema",  "TV" ], "count" : 1 }
  { "hobbies" : [ "TV", "tenis", "cinema" ], "count" : 1 }

$unwind: "$hobbies"
$unwindで配列を展開してフラットなドキュメントリストにする

  { "hobbies" : "cinema", "count" : 1 }
  { "hobbies" : "TV", "count" : 1 }
  { "hobbies" : "TV", "count" : 1 }
  { "hobbies" : "tenis", "count" : 1 }
  { "hobbies" : "cinema", "count" : 1 }

$group: {_id: "$hobbies", count: { $sum: "$count" }}
あとは普通に$groupする

  { "_id" : "cinema", "count" : 2 }
  { "_id" : "TV", "count" : 2 }
  { "_id" : "tenis", "count" : 1 }
性能

やはりMongo(Aggregate)速い!
一見非効率なpipelineだが速度は早い。

mysql5.6 MongoDB 2.6.3
145sec 30sec

問題3

男性が同時に嗜んでいる趣味(趣味の関係性)

得たい結果
h1.name h2.name count
TV cinema 2
TV tenis 1
cinema TV 2
cinema tenis 1
tenis TV 1
tenis cinema 1
sql

Self Join が常套手段

SELECT
  h1.name,  h2.name,  COUNT(people._id)
FROM
  people
INNER JOIN  hobbies h1
ON  people._id = h1.pid
INNER JOIN  hobbies h2
ON  people._id = h2.pid
WHERE
  people.sex = 'male'
  AND
	h1.name != h2.name
GROUP BY
  h1.name, h2.name
ORDER BY
  COUNT(people._id) DESC;
mongo

hobbies配列を2個持たせて2重に$unwindする。

  db.people.aggregate([
   { $match: { sex: 'male' }},
   { $project: { 
       h1: "$hobbies", 
       h2: "$hobbies", 
       count:{ $literal: 1 }}},
   { $unwind: "$h1" },
   { $unwind: "$h2" },
   { $redact: { 
      $cond: { 
        if: { $eq: ["$h1", "$h2"]}, 
        then: "$$PRUNE", 
        else: "$$KEEP"} } },
   { $group: { _id: {
                      h1: "$h1", 
                      h2: "$h2"}, 
                   count: { $sum: "$count" }}},
   { $sort: { count: -1 } },
  ])
性能

Mongo(Aggregate)早すぎる!
$unwindを2重にして一時的にドキュメント数が配列数の2乗倍の数になっているのだが性能にはあまり影響しないようだ。
パイプラインの段数も多くなってるが同じく性能にあまり影響しない。
カジュアルに繋げて大丈夫!!

mysql5.6 MongoDB 2.6.3
195sec 36sec

実際、業務レベルでも4回$unwindする様な事をしても、充分実用に耐えているな。

問題4

男性の趣味数の分布

得たい結果
hobby_count count(pid)
2 1
3 1
sql

サブクエリーの結果を更にgroup by。辛い感じのSQL

SELECT
  hobby_count, COUNT(pid)
FROM (
  SELECT
    people._id AS pid,
    COUNT(name) AS hobby_count
  FROM people
  INNER JOIN hobbies
  ON people._id = hobbies.pid
  WHERE
    people.sex = 'male'
  GROUP BY hobbies.pid
) hobby_count_per_person
GROUP BY hobby_count
ORDER BY COUNT(pid);
mongo

パイプラインだと素直。

db.people.aggregate([
   { $match: { sex: 'male' }},
   { $project: {
       count: {$literal: 1}, 
       numhobbies: { $size: "$hobbies" } } },
   { $group: {
        _id: "$numhobbies", 
        count: { $sum: "$count" } } },
   { $sort: { count: 1 } },
  ])
性能

やはりMongoが早いが、$unwindでひたすらコンテキストを増やすタイプのクエリーより遅い。
これは全然わからないな。

mysql5.6 MongoDB 2.6.3
186sec 53sec

総括

集計に関しては明らかにMongoDBのAggregationFWが優秀だ。

MySQL

そもそもRDBMSは集計用途では無い。正規化してJOINする事こそが本領だ。
クエリーも複雑になりがちだし速度も出ない。
集計の性能は直感的で想像しやすい。

MongoDB

正規化する代わりにembedするので更新が有った時に全更新が辛い。(不可能な時もある)
この辺に目を瞑れるならMongoDBは充分有用だ。

特にAggregationFW はまさに集計用途に設計されており非常に早い。
またパイプライン処理はプログラマに近いかな。(イメージはシェルスクリプトと一緒)

しかしAggregateFWのPIPELINEの処理は中が全く想像できず
何をやれば性能がどうなるのか?想像しにくい面が有る。

AggregationFW特徴など色々
  • 複雑なPipelineを多段組んでも大丈夫そう。
  • Sharding環境でも効率的に動く。
    • $matchにshard keyが含まれていれば
    • 無関係なShardには処理を投げない。
    • バラバラに処理できる部分(map)は
    • 各Shardで処理を進め、集計が必要な部分(reduce)はPrimary shardが行う。