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

Post on 15-Apr-2017

8.993 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Node.js×MongoDB で3 年間サービス運用してみた話ヒカ☆ラボ2015 / 11 / 12Atsushi   HashimotoCyberagent 、 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 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..

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

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↑のような感じで起動すると8 G くらい使えます。

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

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

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 も複雑な設定をしなくても動いてくれます。

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

top related