5月 19

mongoDB
「第3回 MongoDB 勉強会 in Tokyo」 : ATND
先週土曜日(2011年5月14日)にフューチャーアーキテクトさんのセミナールームにて行われたMongoDBの勉強会に行ってきました。


MongoDBの仕様・機能などの体系的な話からチューニングの話、開発に利用する話、プロダクションとして運用している話と、バランスよくまとまっていてとても勉強になりました。
RDBとの違いや特有の挙動をきちんと把握して利用すれば、十分プロダクションで利用出来るというビジョンが見えました。

詳細は各発表者の方のスライドを見るとわかると思いますが、当日会場でメモしたものでダイジェスト的にまとめました。
MongoDB勉強会は毎月開催(予定)しているそうなので、興味を持たれた方は是非参加して見てください。
MongoDBコミュニティー:MongoDB JP | Google グループ

【発表1】MongoDB全機能解説1

MongoDB特有の仕様や機能など、プロダクションで使う場合には特に注意が必要な部分を中心に説明されていました。

ロック

Read/Writeの各オペレーション時にDBに対してロックがかかる。
MongoDBをフロントエンドで使う場合、DBがロックして応答が返って来なくなるなどの問題が起きる可能性があるので、時間がかかる処理には注意が必要とのこと。

実際、単体のRead/Writeは十分高速でロックの待ち時間で問題が起こることはないが、複数のWrite処理をアトミックで行なったり({atomic: ture})、MapReduceで重い処理をさせるケース(ロックはかからないが処理が重いので)が問題になる。

mongos

mongosサーバーは協調する複数のmongodサーバーがある場合、それを一つのクラスターとして扱うためのルーティングサーバーで、それ自体ではストレージする機能はない。
アプリケーションは複数あるmongodサーバーの中でどれががマスターでどれがスレーブなのかを知らなくてもmongosサーバに問い合わせればいい。

database

32bit環境ではDBサイズが2.5GBまで扱えないので、要64bit環境とのこと。
参考:32-bit limitations

BSON

データはBSONという形式で保存されている。データ型が複数あり、どのデータ型で保存されるのかはドライバーに依存する。
→ 例えば、PHPの場合はMongoInt32やMongoInt64などのデータ型用のクラスが用意されてる。
オブジェクト内の順序もみているので、順番が入れ替わるだけで別のオブジェクトととされてしまう。
→ { ‘key1’: ‘value1’, ‘key2’: ‘value2’ } ≠ { ‘key2’: ‘value2’, ‘key1’: ‘value1’ }

Capped Collection

サイズを指定してCollectionを作成するとCapped Collectionになる。
サイズが固定なのでサイズを越えた場合は古いものから自動的に削除される。
とにかく書き込みが速い。でも削除できない。
_idにすらindexがつかない。indexを張ることもできるが遅くなる(それでも速い方)。
shardingすることはできない。
用途 → ロギング(ディスクサイズを圧迫しない)
   → キャッシング
   → MapReduceの一時的なCollectinとして

Insert周り

Bulk Insert

複数のdocumentを配列に入れて一括でインサートする「Bulk Insert」が速い。
ただ、どのくらいのサイズまでが最適なのかはスペック依存で、経験上16MBを超えるとエラーが出るそう。

fire and forget

インサートはデフォルトではデータベースからの応答を待たない
応答を確認するには、各種ドライバーでインサート時に応答を待つ処理を明示的にオプションとして指定する必要がある。
→ PHPではarray(‘safe’=>true)

トランザクション

RDBのように複数のオペレーションをトランザクション処理することは出来ないので、連続インサートする場合、途中でエラーになっても一連のインサートをロールバックすることは出来ない

getLastErrorメソッド
db.getLastError();

でオペレーションの最後に起こったエラーがわかるので、このメソッドは重要。

fsyncコマンド

メモリ上のデータを強制的にストレージにフラッシュするコマンド。

db.runCommand({fsync:1});

fsyncは自動的に60秒に1回走るようになっていて、mongod起動時に、–syncdelayオプションで変更可能だが10genとしては非推奨とのこと。
つまり、最長60秒間に行われたデータの更新は喪失する可能性がある。

Journaling(v1.7.5)

オペレーション前にJournalingファイルに保存し、オペレーションに失敗した場合、次回実行される。
100ms毎にジャーナリングされる。つまり、100ms間のオペレーションは喪失する可能性がある仕様。

Query

printjson

シェル(mongo)でデータを確認するときには

db.collection.find().forEach(printjson);

とするとJSON形式にフォーマットされ見やすくなる。

入れ子

入れ子になったオブジェクトをサブオブジェクト形式でfindする場合は、完全に一致したオブジェクトでないと取得できない。
サブオブジェクト内のあるキーだけを検索対象としたい場合は、dot notation形式({author.name: ‘Jane’})で問い合わせる。

var doc = { title: 'タイトル', body: '本文', author: { first_name: '太郎', family_name: '山田'} };
db.collection.insert(doc);
db.collection.find({author.first_name: '太郎'}); //◯ヒットする
db.collection.find({author: { first_name: '太郎'} }); //×ヒットしない

Update

Update

デフォルトでは最初にヒットしたオブジェクトのみを更新する。
→ ヒットした全てを更新対象としたい場合は{ ‘multi’: true } オプションを付ける。
→ 複数の更新をアトミックに行うには { ‘$atomic’: true } オプションを付ける。

Save

同じ_idのオブジェクトがあれば自動的にドキュメント全体を更新する。

modifier オペレーション

アップデートのオプションとして$inc/$set/$push/$popなどのオペレーションがある。
modifierオペレーションを指定しないでアップデートを行うとオブジェクト(ドキュメント)をまるごと上書きするのでとても怖い。

in-place update

データサイズが変わらない更新の場合は高速。(例えば、{ ‘$inc’; { key: 1 } } など。)
また、データの更新を高速に行うため、インサートされた際に実データサイズよりも大きめにストレージを使って保存している。
→ 多少のサイズの増加も高速に更新出来る。逆に言うと無駄にストレージを使う。

Snapshot

MongoDBはデータサイズが大きく増えた場合、データベースファイルの新しい領域にデータを格納し直す。この瞬間にfind()すると同じドキュメントが2個取れたりする可能性がある
find()で取り出した複数のドキュメントをupdate()するようなケースでは問題になるので、重複を排除して結果を返すsnapshot()を使う。

db.collection.find({name:'Jane'}).snapshot();

Indexについて

B-Tree indexes

インデックスを生成するオペレーションensureIndex()はデフォルトではDBをブロックする。
{ background: true } オプションを付けるとロックしないので、基本的にこれを必ず付けること。

db.collection.ensureIndex({name: 1}, {background:true});

参考:Indexing as a Background Operation

Sparse Index(v 1.7.4)

インデックスの設定されたフィールドを持っていないドキュメントは無視される。
→ コレクション全体的の中でSparse Indexに設定されたフィールドに値を持っているドキュメントがが少ない場合にパフォーマンスが出るインデックス。

Unique Index

インデックスを張ったキーが存在しないドキュメントを登録するとnullが挿入され、nullがユニークの対象となる。
→ インデックスを張ったキーが存在しないドキュメントをもう一つ登録すると重複エラーになる。
ただし、sharding環境では重複する可能性がある

Replication

  • Master-Slave:一般的なM/S構成
  • Replica Sets:自動フェイルオーバ機能などを提供
Master-Slave

Master->Slaveは非同期でコピーされる。
mongodをそれぞれ、–master、 –slaveを付けて起動するだけ。
→ slaveには直接書き込みできない。(mongos経由)

同期は各データベースがそれぞれローカルに自動的に作成するoplog(operation log)コレクションを介して行われる。

  1. スレーブ(セカンダリ)はマスターのoplogを監視している。 
  2. マスター(プライマリ)は更新系のオペレーションをoplogに書き込み、自分のデータベースに実行する。
  3. スレーブはマスターとの差分のoplogを自分のoplogへコピーし、そのオペレーションを自分のデータベースに実行する。

MySQLのバイナリログと同じようなイメージ。

Replica Sets

3台〜12台で構成でき、自動フェイルオーバー機能/自動リカバリ機能を持つ。
ノードタイプには、Standard(データあり。Primaryになれる。)、Passive(データあり。Primaryになれない。)、Arbiter(データなし。投票権はある。)がある。
フェイルオーバーは、起動時に設定するprimary値の高いもの > 最も新鮮なデータを持っているもの > 投票数の多いもの の順で順番付けされ、決定したセカンダリーがプライマリになる。
ダウンしているサーバーが多かったり、最終更新データが古すぎたりするとどのセカンダリーもプライマリにならないことがある。→ 手動で対応。

【発表2】ソーシャルアプリのプロトタイプ制作にMongoDBを活用

Sleepy.Mongoose

アプリケーションとデータベースの通信はSleepy.Mongoose(pythonで実装されたRestインターフェース)を利用して、curlで実装している。
→ Sleepy.Mongooseにはcount()が実装されていないのでforkしたものをGitHubに置いてあるとのこと。
bibrost / sleepy.mongoose

Cray Model

自動車開発のプロセスで粘土細工(Cray Model)で何度も何度も型を作ってから金型を作るように、プロトタイプの開発がしたい。
RDBのようにスキーマを定義して、チーム内で変更を共有して、不整合が起きないようにCREATE TABLEしたりALTER TABLEしたりするのは面倒。
MongoDBはアプリケーション上でいくらでもデータ構造を変更できるので、ソースさえ共有すればよく、途中でドキュメント構造が変わっても、手を止めることなくcollection名を変えるだけでいい。→まさに粘土細工のように失敗したら壊してまた作ればいい。

なぜ最終的にRDBにするのか

  • MongoDBを保守できるエンジニアがいない。
  • MongoDBをクリティカルなプロダクションで使うことが難しい(トランザクションがない)。

【発表3】MongoDBチューニング 〜スロークエリ撲滅までの道筋〜

オプティマイザ

RDBで多く採用されている一般的なオプティマイザは統計情報を元にしたコストベースで、本番環境と検証環境で統計情報が異なっていると実行計画が違ってきたり、悪い実行計画も行われる。
MongoDBのオプティマイザはコストベースではなく、新しいクエリー実行がある度に計算する仕様。(オーバーヘッド・CPUコストが高いが、最適な計画が行われる)

  • 新しいクエリー実行時に複数の実行計画を並行に実施して一番速いのを採用・学習し、他のものは途中で止める。
  • 100回データ更新が行われ時やindexが変更された時、最初の評価時からスキャンするドキュメントが大きく増えた場合(nscannedの値が10倍になったら)再評価を実施する。

explainメッソド

explain()で実行計画を確認できる。

db.collection.find(query).explain();

勘所としてはBasicCursorは全件スキャンなので×、millisが長いと処理時間が遅いので×、nscannedが多いとスキャン対象のドキュメント数が多いので×、など。

スロークエリの特定

  • slowmsオプションをつけて起動する。
    → ログファイルに保存される。
  • db.setProfilingLevel()を実行する。
    → db.system.profileコレクションに保存されていく。(デフォルト100ms)
  • db.currentOp()を実行する
    → 現在実行中のクエリが見られる。
  • 複合インデックス

    順序が大事。
    db.collection.find({x: 10}).sort({ y: 1});でも { x: 1, y: 1 }のインデックスが使われる。

    hintメソッド

    hintメソッドを使うと特定のインデックスを使わせることができる。
    インデックス設定されていれば、
    db.collection.find().hint({uid:1}); より db.collection.find().hint({uid:1, path:1}); の方がより絞られて高速になる。

    mongostat

    mongoやmongodなどと一緒にインストールされるmongostatコマンド。
    ロックされている割合やインデックスが利用されなかった割合などの統計情報が確認できる。

    killメソッド

    db.killOp(opId)で実行中のクエリをkillできる。

    【発表4】MongoDBを使用したモバイルゲーム開発

    東京ガールズスナップの構成

    Java(appサーバー2台)+ MongoDB(Replica Setで3台)で運用している。
    MongoDBに対応したSpring Data Documentを利用して開発した。

    MongoDBで良かったこと

    階層型にデータが持てるのでkey-value的に使えた。
    → ドキュメント内に配列を持つことができるので、RDBのようなリレーションや連結などしなくても直接$pushや$popで複数のデータを出し入れできる。

    バイナリデータが保存しやすい
    当初の想定していた利用法ではなかったが画像データのキャッシュストレージとして利用できた。
    → 4MBまでだったらバイナリデータをそのまま格納できる。

    MongoDBが問題になったこと

    • オペレーションミスが大変なことになる(update時に$setで指定しないと全部上書きする)
    • アプリ経由ではなくコンソールでデータを更新したらデータ型が変わってしまった(intがdoubleに)
    • ドキュメント設計の最適化に時間がかかった(ベストプラクティスがわからない)
    Tagged with:
    5月 02

    ATNDのAPIを利用してワンクリックで自分の登録しているイベントをiPhoneのカレンダーに取り込めます。

    通常のAPIへのアクセス方法を以下のように変更して、iPhoneのsafariでアクセスするだけです。

    http://api.atnd.org/events/?nickname=ms76&count=100&format=ics

    webcal://api.atnd.org/events/?nickname=ms76&count=100&format=ics

    すると、カレンダーに取り込む(照会する)かを聞かれるので

    照会を押します。

    取り込み完了です。

    カレンダーにアクセスをするとATNDのイベントスケジュールが表示されます。

    URLにアクセスするのも面倒かもしれないのでスクリプトを書きました。
    以下のテキストフォームにATNDのニックネームを入れて、「カレンダーを開く」を押すと上記の手順でアクセス出来ます。是非ご利用下さい。

    ※iPhone以外でも、Webcalに対応していれば同様のことが可能です。

    Tagged with:
    2月 07

    Osukini Cloudとは

    クラウドサービス「Osukini クラウド」|クラウドならSaaSes

    Osukini Cloudは日本ラッドが提供するSaas/Paasサービスのブランド「SaaSes」の1商品です。
    Cloudと銘打ってはいるものの、サーバーのスナップショットイメージを保存して再利用したり、稼働済みのサーバーのクローンを立ち上げたりする、スケールアウト向けの機能はありません。

    簡単に言うと、CPU・メモリ・ハードディスクを動的にスケールアップ・ダウンできる機能が付いたXenベースのVPSサービスです。
    仮想サーバーの仕組みをそのままサービスにしたイメージです。

    Osukini Cloudの使いどころ

    上述のように急激なアクセス増が予想されるスケールアウト必至のサービスには向いていないので、使いどころとしては

    • 当面は低スペックで構わないが後々のアクセス増対応に不安
    • 既存の格安VPSでどのプランで契約していいかわからない

    ようなケースが当てはまるかもしれません。

    さくらのVPSが価格の割に品質がよく、満足できるVPSサービスだと思いますが、現時点でスペックが固定なので、もう少し高スペックで同様に低価格なサービスを探している方にはOsukini Cloudはおすすめだと思います。

    スペックと価格

    一般的なVPSサービスのようにプランごとにスペックが決まるのではなく、1プランでスペック変更が可能というところが特徴ですので、最低スペックで始めて運用しながら随時変更することができます。(スケールアップ・ダウンには再起動・サービスダウンが伴うようです。)

    料金とスペックをまとめると以下のようになります。(全て月額)

      1CPU+1GB 2CPU+2GB 3CPU+3GB 4CPU+4GB
    80GB ¥1,000 ¥2,200 ¥3,800 ¥5,800
    160GB ¥1,900 ¥3,100 ¥4,700 ¥6,700
    240GB ¥2,800 ¥4,000 ¥5,600 ¥7,600
    320GB ¥3,700 ¥4,900 ¥6,500 ¥8,500

    CPUとメモリは連動で、4×4の16パターンの料金体系があり、月途中でスペックを変更した場合は、その月で最もハイスペックだった料金が請求される仕組みです。
    転送量に対する課金もありませんし、稼働時間に対する課金もありませんので、料金が非常に明確になります。

    利用可能なOSは以下の通りです。

    • CentOS ver5.5 32bit
    • CentOS ver5.5 64bit
    • Ubuntu ver10.04 32bit
    • Debian ver5.0(lenny) 32bit
    • Debian ver5.0(lenny) 64bit
    • Debian ver6.0(squeeze) 32bit
    • Debian ver6.0(squeeze) 64bit

    実際に使ってみて

    現在、4CPU/4GB+80GB(¥5,800)、CentOS5.5 64bitのサーバーを3台契約しています。
    ホームページ上では最短5分以内でスタートできると書いてありますが、1台目を契約した際は在庫がないとのことで、サービスの利用開始まで丸1日かかり、2台目・3台目は14、5分でした。必ずしも契約後すぐに利用できるわけではないので、注意が必要です。

    参考までに、サーバーの基本的なスペック情報は以下の通りです。

    システム情報

    $ uname -a
    Linux hostname 2.6.18-194.32.1.el5xen #1 SMP Wed Jan 5 18:44:24 EST 2011 x86_64 x86_64 x86_64 GNU/Linux
    

    CPU情報

    $ cat /proc/cpuinfo
    processor	: 0
    vendor_id	: AuthenticAMD
    cpu family	: 16
    model		: 9
    model name	: AMD Opteron(tm) Processor 6128
    stepping	: 1
    cpu MHz		: 2000.154
    cache size	: 512 KB
    physical id	: 0
    siblings	: 1
    core id		: 0
    cpu cores	: 1
    fpu		: yes
    fpu_exception	: yes
    cpuid level	: 5
    wp		: yes
    flags		: fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc pni monitor cx16 popcnt lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse
    bogomips	: 5003.13
    TLB size	: 1024 4K pages
    clflush size	: 64
    cache_alignment	: 64
    address sizes	: 48 bits physical, 48 bits virtual
    power management: ts ttp tm stc [6] [7] [8]
    
    processor	: 1
    vendor_id	: AuthenticAMD
    cpu family	: 16
    model		: 9
    model name	: AMD Opteron(tm) Processor 6128
    stepping	: 1
    cpu MHz		: 2000.154
    cache size	: 512 KB
    physical id	: 1
    siblings	: 1
    core id		: 0
    cpu cores	: 1
    fpu		: yes
    fpu_exception	: yes
    cpuid level	: 5
    wp		: yes
    flags		: fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc pni monitor cx16 popcnt lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse
    bogomips	: 5003.13
    TLB size	: 1024 4K pages
    clflush size	: 64
    cache_alignment	: 64
    address sizes	: 48 bits physical, 48 bits virtual
    power management: ts ttp tm stc [6] [7] [8]
    
    processor	: 2
    vendor_id	: AuthenticAMD
    cpu family	: 16
    model		: 9
    model name	: AMD Opteron(tm) Processor 6128
    stepping	: 1
    cpu MHz		: 2000.154
    cache size	: 512 KB
    physical id	: 2
    siblings	: 1
    core id		: 0
    cpu cores	: 1
    fpu		: yes
    fpu_exception	: yes
    cpuid level	: 5
    wp		: yes
    flags		: fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc pni monitor cx16 popcnt lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse
    bogomips	: 5003.13
    TLB size	: 1024 4K pages
    clflush size	: 64
    cache_alignment	: 64
    address sizes	: 48 bits physical, 48 bits virtual
    power management: ts ttp tm stc [6] [7] [8]
    
    processor	: 3
    vendor_id	: AuthenticAMD
    cpu family	: 16
    model		: 9
    model name	: AMD Opteron(tm) Processor 6128
    stepping	: 1
    cpu MHz		: 2000.154
    cache size	: 512 KB
    physical id	: 3
    siblings	: 1
    core id		: 0
    cpu cores	: 1
    fpu		: yes
    fpu_exception	: yes
    cpuid level	: 5
    wp		: yes
    flags		: fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc pni monitor cx16 popcnt lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse
    bogomips	: 5003.13
    TLB size	: 1024 4K pages
    clflush size	: 64
    cache_alignment	: 64
    address sizes	: 48 bits physical, 48 bits virtual
    power management: ts ttp tm stc [6] [7] [8]
    

    メモリ情報

    $ cat /proc/meminfo
    MemTotal:      4194304 kB
    MemFree:         67556 kB
    Buffers:         59036 kB
    Cached:        3588160 kB
    SwapCached:          0 kB
    Active:        2048284 kB
    Inactive:      1838268 kB
    HighTotal:           0 kB
    HighFree:            0 kB
    LowTotal:      4194304 kB
    LowFree:         67556 kB
    SwapTotal:     8393952 kB
    SwapFree:      8393880 kB
    Dirty:            1064 kB
    Writeback:           0 kB
    AnonPages:      239332 kB
    Mapped:          11304 kB
    Slab:           115088 kB
    PageTables:       8744 kB
    NFS_Unstable:        0 kB
    Bounce:              0 kB
    CommitLimit:  10491104 kB
    Committed_AS:   332088 kB
    VmallocTotal: 34359738367 kB
    VmallocUsed:      4340 kB
    VmallocChunk: 34359734027 kB
    

    ディスク性能(Read)

    $ sudo hdparm -Tt /dev/xvda3
    
    /dev/xvda3:
     Timing cached reads:   9604 MB in  1.99 seconds = 4818.05 MB/sec
     Timing buffered disk reads:  340 MB in  3.01 seconds = 113.05 MB/sec
    

    ディスク性能(Write)

    $ dd if=/dev/zero of=/tmp/test.tmp bs=1M count=256
    256+0 records in
    256+0 records out
    268435456 bytes (268 MB) copied, 0.63945 seconds, 420 MB/s
    
    $ dd if=/dev/zero of=/tmp/test.tmp bs=1M count=1024
    1024+0 records in
    1024+0 records out
    1073741824 bytes (1.1 GB) copied, 2.70829 seconds, 396 MB/s
    

    最後に

    運用して1週間程立ちましたが、スペック通り十分なパフォーマンスを安定して発揮してくれています。リプレース元のサーバーでは転送量課金もかかっていたので、だいぶコストを圧縮することが出来ました。

    格安VPSではスペックがネックになって利用シーンが限られていたと思いますが、Osukini Cloudはスペックの自由度が高いので、格安VPSと専用サーバーの間の領域を柔軟にカバーしてくれる数少ない優れたVPSサービスだと思います。

    Tagged with:
    preload preload preload