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

80
Node.js×MongoDB で 3 ででででででででででででで でで でで 2015 / 11 / 12 Atsushi Hashimoto Cyberagent Inc 1

Upload: leveragesevent

Post on 15-Apr-2017

8.993 views

Category:

Technology


1 download

TRANSCRIPT

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

Node.js×MongoDB で3 年間サービス運用してみた話ヒカ☆ラボ2015 / 11 / 12Atsushi   HashimotoCyberagent 、 Inc

1

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

自己紹介

○橋本 純(はしもと あつし)○32歳○サーバサイド エンジニア○Node.js 、 Java 、 Python 、 PHP

担当したサービス:ピグサバゲー、ピグアイランド、ピグライフ、ピグワールド、ピグブレイブ(今ココ)

2

ふふふっ。こんにちは、ハッシーです。

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

アジェンダ

ざっくりサービス説明Node.js で嵌ったMongoDB で嵌ったまとめ

3

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

ざっくりサービス説明

4

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

5

ライフ アイランド

カフェ ワールド

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

6

ブレイブ

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

サーバー構成

7

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

8

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

フロントWebsocket と Flash

全盛期は、同時接続 20 万9

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

10

実際の仕様バージョンやモジュールなど

○Node.js v 0.8.26 v 0.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..

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

ここまでで、ざっくり説明は終了です。

11

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

Node.js で嵌った

12

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

イベントループ系の話

13

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

イベントループをざっくり説明

14

に行く前に

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

15

Node.js は、イベントループを止めちゃダメ

イメージ的には、関数を積み上げて順番に処理していく感じ。

DB コールや Http リクエストなど、同期的に処理すると止まってしまう処理は、叩くだけにして、戻ったらクロージャを実行する感じ。(非同期に処理)

どこかで止めてしまうと、全てが止まる。。。

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

console.log 使っていると固まるような。。

16

エピソード1

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

17

console.log() は、時を止める

使うと標準出力へ同期的に書き出す(サイズが大きいと結構止まる)(本番のコードには残さないように)

通常のログは、ログ出力用のライブラリなど使ってください。 

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

高負荷でも無いのに、なぜか Httpリクエストがタイムアウトする。。。

18

エピソード2

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

19

重い or 回数の多い for 文のループも時を止める

原因は、アイテム数が多いユーザが特定の行動をすると、回数の多い for 文の処理となり、時が止まってしまった。

500 ループぐらいで、 setImmediate を挟んで対応。

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

時を止めないように起動時もがんばって非同期で書いたが、処理が複雑になってしまった。。。

20

エピソード3

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

21

初回起動時のみなら、同期的に行っても問題ない

コンフィグファイルの読み込みHttp サーバの起動DB 接続各種セットアップ ... などなど

起動時してすぐに、リクエストが飛んでこないなら、最初は同期的にやったほうが処理がシンプルになる。

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

コーディング時の失敗の話

22

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

require した時点で、処理が動くように書いたが、バッチ時など動いてほしくない時も動いてしまった。。。

23

エピソード1

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

24

Require した時点で動く処理は書かないほうがよい空関数をセットしたりなども出来ないので、面倒でもセットアップ処理などある場合でも、使う側で呼んだほうが良いと思います。

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

エラー時に、 try catch の部分で、なぜか callback が2回呼ばれた。。。

25

エピソード2

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

26

try catch finally を使ったほうがよい。

NG パターンは、 callback 内でエラーが throw されると、catch 内の callback が呼ばれるため、2回呼ばれる。

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

同期的にコーディングしていた部分に、非同期処理を入れることになったが、修正が大変だった。。。

27

エピソード3

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

28

基本 callback 形式で書いておいたほうが良い。

メソッドの粒度など、色々とあるかと思いますが、Private 以外の関数や修正が多そうな関数は、callback 形式で書いておいたほうが、無難かと。

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

スタックオーバーエラーにならなそうなコードで、スタックオーバーエラー。

29

エピソード4

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

30

スキップ系は、 setImmeiate 挟むようにする。

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

TypeError (ぬるぽ)が起きて、ギフトが受け取り放題になった。。。

31

エピソード5

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

32

ESLint などのリンター導入とテストの充実を図る

TypeError は、なんちゃってロールバックも出来なくなるので、致命的です。。。

無いように実装&テストせねばならないです。

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

その他

33

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

複数 CPU を使用したい場合、クラスターモジュールを使う必要がある。

34

エピソード1

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

35

でも、クラスターモジュール使ってません。

Node.js v0.4 の当時は、複数プロセス立ち上げることが必要だったので、歴史に引きづられているだけです。

なお、 v0.10 は、均等に処理が振られないので↑の手法を試すのも良いかもです。 v0.12 以上は直ってます。

Node.js を 0.12 以上で使うなら素直にクラスターモジュールを使用してください。

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

Node.js でバッチ処理を書いたら、メモリ 1.4Gbyte超えた当たりからめっちゃ遅い

36

エピソード2

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

37

リミットが 1.4Gbyte くらいなので注意です。

node app.js --max-old-space-size=8192 --nouse-idle-notification↑のような感じで起動すると8 G くらい使えます。

バッチとかでワザとメモリを使う仕様にしないと、あまり上限に当たらない気もしますが。。。

ググると、起動時パラメータの話とか色々とあると思います。

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

Websocket を使っているが、処理計測がしづらい。

38

エピソード3

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

39

力技で計測処理を入れました。。。

Java とかと違って、非同期の処理は測りづらいです。結果Controller層で、処理の終わりで計測メソッドを呼び出すようにしました。。。http のように、リクエスト&レスポンスが必ずある前提でwebsocket を使えば、仕込むのは容易だったのですが。。。

賛否あるかと思いますが、僕が次回やるなら仕込めるような形で実装します。

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

ここまでで、 Node.js の話は終わりです。

40

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

MongoDB で嵌った

41

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

注意

この資料は、 MongoDB2.2 系と 2.4 系で起こった出来事をありのまま描いた物です。3 系だと起こらないものもあるかもなので、過度な期待はしないでください。

42

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

MongoDB について

43

■コレクションのシャードキー単位で、自動で分散してデータ保持

■Shard はレプリカセット単位

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

MongoDB について

44

Shard に直接接続することもできるが、全データ取得できないため、通常は、mogos経由でアクセス。

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

シャーディング編

45

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

サービス公開時に、プライマリシャードに更新が偏ってエラーが出た。

46

エピソード1

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

47

予測し得るデータを、予めデータを投入しておく

MongoDB は、プライマリシャードというものがあり、空のコレクションは、まずそこに書き込まれる。ある程度、量が増えると chunk移動がおこり、データがレプリカセットごとに移動する。

先にデータを用意しておけば、 chunk移動をあらかじめ起こすことが出来るので、負荷分散出来る。

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

コレクションを追加したが、シャーディング設定が入ってなかった。。。

48

エピソード2

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

49

コンフィグファイルとのチェック機構を追加

プライマリシャードの件でもそうですが、一箇所に更新が固まると、まずいです。

シャーディング設定を入れないと、データが分散されないので、注意です。

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

チャンク移動時に、たまに前のシャードに書き込みが起きる。

50

エピソード3

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

51

mongod単位で見ると、 _id が複数ある状態に。。。Chunk の移動が自動で行われると、ロックをかけてから、データ移動という順番ですが、なぜか移動前のシャードに対してクエリが発行されている場合があります。

全部のクエリがそうでは無いので、自動ではなく手動でchunk移動するか?とか、検討中です。

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

ローカルではエラーにならなかったが、ステージング環境でエラーが発生

52

エピソード4

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

53

シャーディング環境でないと出ない update エラー

シャーディング環境だと、シャードキーが無い更新をするとエラーになります。(オプションを指定すれば大丈夫。)

ローカル環境と Dev環境を、シャーディング設定入れました。

そもそもコードのバグなので、 DB アクセス層を工夫すれば、起こらない問題でもあるかと。

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

データ編

54

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

文字列のデータが入るプロパティに、配列のデータが入ってる。

55

エピソード1

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

56

スキーマレスだけど、スキーマほしいです。

とあるデータは、プロパティが無い状態などもあり、データの全容が把握しづらかったりします。

RDBMS なら、テーブル定義で分かったりとか出来る部分なので、スキーマレスでもスキーマがほしい所です。

json-schema 入れたり、 Mongoose利用すれば解消します。

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

マスターデータは、 Excel と親和性が良い物にしたほうが良い。

57

エピソード2

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

58

結局マスタの作成は Excel なのです。

管理画面のフォームを作ったは良いが、 Excel アップロードしか使われなかったり。。。

エンジニア的には、フォームと Excel アップロード両方作成したり、 json とテーブルのインピーダンスミスマッチの解消をするコードを書かなければなりません。。。

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

ユーザデータの構成は、基本的に1コレクション、1ドキュメント

59

エピソード3

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

60

使い方にもよるかと思いますが、うまくいってます。各種コレクションごとに、ユーザコードを _id にして、ドキュメントがあります。

リファレンス式という使い方もあるかと思いますが、特に問題は出てません。

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

行動ログのコレクションを Capped Collection にしたら耐えられなかった件

61

エピソード4

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

62

テラバイト級は、 index すらメモリに入らず。。

元々の、時間ごとに普通のコレクションを作成して、ログを検索する仕様に戻す。

↑こちらの仕様だと、データ量が、ある一定量に到達するとnamespace が枯渇してエラーが出ます。なので、サーバーを切り替える仕組みにして運用していたのですが、それが大変だったから切り替えたかったのですが。。。

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

知らぬ間にオブジェクトサイズが肥大化しており、パースエラーが発生

63

エピソード5

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

64

定期的にドキュメントサイズを計測するようにした

対象のデータは、配列のプロパティだったのですが、不具合でコードのチェックが働いておらず、無限に追加していく形になっていた。

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

バッチ編

65

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

バッチで一括削除したはずのデータが読み込まれる

66

エピソード1

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

67

mongos にキャッシュが残っていたのが原因

db.adminCommand(‘flushRouterConfig’)

↑か、 mongos の再起動で解決する。

現状では、バッチ系を流した後は、上記コマンドを実施している。

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

ローカルでは問題ないのに、本番だとバッチの実行が完了しない。

68

エピソード2

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

69

更新対象の問題( Hoge でなければ無問題)

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

その他

70

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

ObjectId で、時間の範囲検索をしたい。

71

チップス的な

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

72

更新対象の問題( Hoge でなければ無問題)

先頭が 4 バイトの Unix 時間なので、↑の用にやるとできます。Node.js からやるなら、 ObjectID.createFromTime(startTime);というメソッドがあるので、↑推奨です。

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

ここまでで、 MongoDB の話は終わりです。

73

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

まとめ

74

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

75

固有の地雷に気をつけてください

今回の発表の内容のようなことが避けられればよいかと。

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

76

静的チェック系のモジュールの導入

現状は、 ESLint が有力。

現場では、 jshint と jscs と gjslint など使ってます。移り変わりが激しいですね (‘A`)

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

77

自由度が高くスタンダードな実装が無い

設計や実装方針の決めが重要

逆に、うまく設計や実装が出来るとすごく追加・修正がしやすいシステムが出来ると思います。

現場では、 MVC で実装してますが、 M が重くなりすぎて、色々と検討中です。(ただ、分かりやすいです。)

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

78

完璧なロールバックはできない

トランザクションが無いので、完璧なロールバックはできないです。現場では、エラーが起きた際に、更新前に戻すクエリを発行したりしています。

お金系処理は、別システムの API を叩いています。RDBMS のような完璧さは無いので、作るシステムによって考えてほしいです。マイクロサービスの 1 つとして使うのはありかと。

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

79

Node.js含め、各モジュールの更新頻度が高い

アップデート頻度や、モジュール自体のデファクトの移り変わりが激しいです。

「 power-assert」を使うなど、仕様が変わらなそうなものを使うのも良いと思いますが、基本はモジュールの変更に追従する方針になると思います。なので、モジュールを変更しても大丈夫なように、テストを充実させておく必要があるとおもいます。

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

80

最後に

Node.js の実装は、 Java より楽になっていると思います。環境構築も、 npm i で、一発で出来たりしますし。

MongoDB も複雑な設定をしなくても動いてくれます。

楽ちんではありますが、銀行業務などには向かないので、ケースバイケースで使用していくと、幸せになれるのでは?と、思います。以上です。