[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
SlideShare a Scribd company logo
Node.js×MongoDBで
3年間サービス運用してみた話
ヒカ☆ラボ
2015/11/12
Atsushi Hashimoto
Cyberagent、Inc
1
自己紹介
○橋本 純(はしもと あつし)
○32歳
○サーバサイド エンジニア
○Node.js、Java、Python、PHP
担当したサービス:ピグサバゲー、ピグアイランド、ピグライフ、
ピグワールド、ピグブレイブ(今ココ)
2
ふふふっ。こんにちは、ハッシーです。
アジェンダ
ざっくりサービス説明
Node.jsで嵌った
MongoDBで嵌った
まとめ
3
ざっくりサービス説明
4
5
ライフ アイランド
カフェ ワールド
6
ブレイブ
サーバー構成
7
8
フロント
WebsocketとFlash
全盛期は、同時接続20万
9
10
実際の仕様バージョンやモジュールなど
○Node.js v0.8.26 v0.10.29
○MongoDB 2.2、2.4
○Npmモジュール
express, ws, socket.io, async, request, lodash,
moment, ghooks, node-mongodb-native,
supertest, sinon, power-assert, istanbul, etc..
ここまでで、ざっくり説明は終了です。
11
Node.jsで嵌った
12
イベントループ系の話
13
イベントループをざっくり説明
14
に行く前に
15
Node.jsは、イベントループを止めちゃダメ
イメージ的には、関数を積み上げて順番に
処理していく感じ。
DBコールやHttpリクエストなど、同期的に処理すると止まっ
てしまう処理は、叩くだけにして、戻ったらクロージャを実
行する感じ。(非同期に処理)
どこかで止めてしまうと、全てが止まる。。。
console.log使っていると固まるような。。
16
エピソード1
17
console.log()は、時を止める
使うと標準出力へ同期的に書き出す
(サイズが大きいと結構止まる)
(本番のコードには残さないように)
通常のログは、ログ出力用のライブラリなど
使ってください。
高負荷でも無いのに、なぜかHttp
リクエストがタイムアウトする。。。
18
エピソード2
19
重い or 回数の多いfor文のループも時を止める
原因は、アイテム数が多いユーザが特定の行動をすると、
回数の多いfor文の処理となり、時が止まってしまった。
500ループぐらいで、setImmediateを挟んで対応。
時を止めないように起動時もがんばって
非同期で書いたが、処理が複雑になって
しまった。。。
20
エピソード3
21
初回起動時のみなら、同期的に行っても問題ない
コンフィグファイルの読み込み
Httpサーバの起動
DB接続
各種セットアップ...などなど
起動時してすぐに、リクエストが飛んでこないなら、
最初は同期的にやったほうが処理がシンプルになる。
コーディング時の失敗の話
22
requireした時点で、処理が動くように書
いたが、バッチ時など動いてほしくない
時も動いてしまった。。。
23
エピソード1
24
Requireした時点で動く処理は書かないほうがよい
空関数をセットしたりなども出来ないので、
面倒でもセットアップ処理などある場合でも、
使う側で呼んだほうが良いと思います。
エラー時に、try catchの部分で、なぜか
callbackが2回呼ばれた。。。
25
エピソード2
26
try catch finallyを使ったほうがよい。
NGパターンは、callback内でエラーがthrowされると、
catch内のcallbackが呼ばれるため、2回呼ばれる。
同期的にコーディングしていた部分に、
非同期処理を入れることになったが、修
正が大変だった。。。
27
エピソード3
28
基本callback形式で書いておいたほうが良い。
メソッドの粒度など、色々とあるかと思いますが、
Private以外の関数や修正が多そうな関数は、
callback形式で書いておいたほうが、無難かと。
スタックオーバーエラーにならなそうな
コードで、スタックオーバーエラー。
29
エピソード4
30
スキップ系は、setImmeiate挟むようにする。
TypeError(ぬるぽ)が起きて、ギフトが
受け取り放題になった。。。
31
エピソード5
32
ESLintなどのリンター導入とテストの充実を図る
TypeErrorは、なんちゃってロールバックも出来なくなるの
で、致命的です。。。
無いように実装&テストせねばならないです。
その他
33
複数CPUを使用したい場合、クラスター
モジュールを使う必要がある。
34
エピソード1
35
でも、クラスターモジュール使ってません。
Node.js v0.4の当時は、複数プロセス立ち上げることが必要
だったので、歴史に引きづられているだけです。
なお、v0.10は、均等に処理が振られないので↑の手法を試
すのも良いかもです。v0.12以上は直ってます。
Node.jsを0.12以上で使うなら素直にクラスターモジュール
を使用してください。
Node.jsでバッチ処理を書いたら、メモリ
1.4Gbyte超えた当たりからめっちゃ遅い
36
エピソード2
37
リミットが1.4Gbyteくらいなので注意です。
node app.js --max-old-space-size=8192 --nouse-idle-
notification
↑のような感じで起動すると8Gくらい使えます。
バッチとかでワザとメモリを使う仕様にしないと、あまり上
限に当たらない気もしますが。。。
ググると、起動時パラメータの話とか色々とあると思います。
Websocketを使っているが、処理計測が
しづらい。
38
エピソード3
39
力技で計測処理を入れました。。。
Javaとかと違って、非同期の処理は測りづらいです。結果
Controller層で、処理の終わりで計測メソッドを呼び出すよ
うにしました。。。
httpのように、リクエスト&レスポンスが必ずある前提で
websocketを使えば、仕込むのは容易だったのですが。。。
賛否あるかと思いますが、僕が次回やるなら仕込めるような
形で実装します。
ここまでで、Node.jsの話は終わりです。
40
MongoDBで嵌った
41
注意
この資料は、MongoDB2.2系と2.4系で
起こった出来事をありのまま描いた物です。
3系だと起こらないものもあるかもなので、
過度な期待はしないでください。
42
MongoDBについて
43
■コレクションのシャードキー単
位で、自動で分散してデータ保持
■Shardはレプリカセット単位
MongoDBについて
44
Shardに直接接続
することもできる
が、全データ取得
できないため、通
常は、mogos経
由でアクセス。
シャーディング編
45
サービス公開時に、プライマリシャード
に更新が偏ってエラーが出た。
46
エピソード1
47
予測し得るデータを、予めデータを投入しておく
MongoDBは、プライマリシャードというものがあり、空のコ
レクションは、まずそこに書き込まれる。
ある程度、量が増えるとchunk移動がおこり、データがレプ
リカセットごとに移動する。
先にデータを用意しておけば、chunk移動をあらかじめ起こ
すことが出来るので、負荷分散出来る。
コレクションを追加したが、シャーディ
ング設定が入ってなかった。。。
48
エピソード2
49
コンフィグファイルとのチェック機構を追加
プライマリシャードの件でもそうですが、一箇所に更新が固
まると、まずいです。
シャーディング設定を入れないと、データが分散されないの
で、注意です。
チャンク移動時に、たまに前のシャード
に書き込みが起きる。
50
エピソード3
51
mongod単位で見ると、_idが複数ある状態に。。。
Chunkの移動が自動で行われると、ロックをかけてから、デ
ータ移動という順番ですが、なぜか移動前のシャードに対し
てクエリが発行されている場合があります。
全部のクエリがそうでは無いので、自動ではなく手動で
chunk移動するか?とか、検討中です。
ローカルではエラーにならなかったが、
ステージング環境でエラーが発生
52
エピソード4
53
シャーディング環境でないと出ないupdateエラー
シャーディング環境だと、シャードキーが無い更新をすると
エラーになります。(オプションを指定すれば大丈夫。)
ローカル環境とDev環境を、シャーディング設定入れました。
そもそもコードのバグなので、DBアクセス層を工夫すれば、
起こらない問題でもあるかと。
データ編
54
文字列のデータが入るプロパティに、配
列のデータが入ってる。
55
エピソード1
56
スキーマレスだけど、スキーマほしいです。
とあるデータは、プロパティが無い状態などもあり、データ
の全容が把握しづらかったりします。
RDBMSなら、テーブル定義で分かったりとか出来る部分なの
で、スキーマレスでもスキーマがほしい所です。
json-schema入れたり、 Mongoose利用すれば解消します。
マスターデータは、Excelと親和性が良い
物にしたほうが良い。
57
エピソード2
58
結局マスタの作成はExcelなのです。
管理画面のフォームを作ったは良いが、Excelアップロードし
か使われなかったり。。。
エンジニア的には、フォームとExcelアップロード両方作成し
たり、jsonとテーブルのインピーダンスミスマッチの解消を
するコードを書かなければなりません。。。
ユーザデータの構成は、基本的に1コレ
クション、1ドキュメント
59
エピソード3
60
使い方にもよるかと思いますが、うまくいってます。
各種コレクションごとに、ユーザコードを_idにして、ドキュ
メントがあります。
リファレンス式という使い方もあるかと思いますが、特に問
題は出てません。
行動ログのコレクションをCapped
Collectionにしたら耐えられなかった件
61
エピソード4
62
テラバイト級は、indexすらメモリに入らず。。
元々の、時間ごとに普通のコレクションを作成して、ログを
検索する仕様に戻す。
↑こちらの仕様だと、データ量が、ある一定量に到達すると
namespaceが枯渇してエラーが出ます。なので、サーバーを
切り替える仕組みにして運用していたのですが、それが大変
だったから切り替えたかったのですが。。。
知らぬ間にオブジェクトサイズが肥大化
しており、パースエラーが発生
63
エピソード5
64
定期的にドキュメントサイズを計測するようにした
対象のデータは、配列のプロパティだったのですが、不具合
でコードのチェックが働いておらず、無限に追加していく形
になっていた。
バッチ編
65
バッチで一括削除したはずのデータが読
み込まれる
66
エピソード1
67
mongosにキャッシュが残っていたのが原因
db.adminCommand(‘flushRouterConfig’)
↑か、mongosの再起動で解決する。
現状では、バッチ系を流した後は、上記コマンドを実施して
いる。
ローカルでは問題ないのに、本番だとバ
ッチの実行が完了しない。
68
エピソード2
69
更新対象の問題(Hogeでなければ無問題)
その他
70
ObjectIdで、時間の範囲検索をしたい。
71
チップス的な
72
更新対象の問題(Hogeでなければ無問題)
先頭が4バイトのUnix時間なので、↑の用にやるとできます。
Node.jsからやるなら、
ObjectID.createFromTime(startTime);
というメソッドがあるので、↑推奨です。
ここまでで、MongoDBの話は終わりです。
73
まとめ
74
75
固有の地雷に気をつけてください
今回の発表の内容のようなことが避けられればよいかと。
76
静的チェック系のモジュールの導入
現状は、ESLintが有力。
現場では、jshintとjscsとgjslintなど使ってます。
移り変わりが激しいですね(‘A`)
77
自由度が高くスタンダードな実装が無い
設計や実装方針の決めが重要
逆に、うまく設計や実装が出来るとすごく追加・修正がしや
すいシステムが出来ると思います。
現場では、MVCで実装してますが、Mが重くなりすぎて、
色々と検討中です。(ただ、分かりやすいです。)
78
完璧なロールバックはできない
トランザクションが無いので、完璧なロールバックは
できないです。現場では、エラーが起きた際に、更新前に戻
すクエリを発行したりしています。
お金系処理は、別システムのAPIを叩いています。
RDBMSのような完璧さは無いので、作るシステムによって考
えてほしいです。
マイクロサービスの1つとして使うのはありかと。
79
Node.js含め、各モジュールの更新頻度が高い
アップデート頻度や、モジュール自体のデファクトの移り変
わりが激しいです。
「power-assert」を使うなど、仕様が変わらなそうなものを
使うのも良いと思いますが、基本はモジュールの変更に追従
する方針になると思います。なので、モジュールを変更して
も大丈夫なように、テストを充実させておく必要があるとお
もいます。
80
最後に
Node.jsの実装は、Javaより楽になっていると思います。
環境構築も、npm iで、一発で出来たりしますし。
MongoDBも複雑な設定をしなくても動いてくれます。
楽ちんではありますが、銀行業務などには向かないので、ケ
ースバイケースで使用していくと、幸せになれるのでは?と、
思います。以上です。

More Related Content

Node.js×mongo dbで3年間サービス運用してみた話