Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Node.jsでのゲームサーバ開発 愛すべきバッドノウハウ3選 / node.js three bad know-how on developing a game server

qsona
November 07, 2015

Node.jsでのゲームサーバ開発 愛すべきバッドノウハウ3選 / node.js three bad know-how on developing a game server

東京Node学園祭2015で発表した内容です。補足記事はこちらです。
http://qsona.hatenablog.com/entry/2015/11/18/102059

qsona

November 07, 2015
Tweet

More Decks by qsona

Other Decks in Programming

Transcript

  1. 東京 Node 学園祭 2015 Node.js でのゲームサーバ開発 愛すべ き バッドノウハウ 3

    選 株式会社サイバーエージェント 森 久太郎 Twitter/GitHub/ はてなブログ : @qsona 1 / 104
  2. 自己紹介 Node.js でゲームのサーバ開発中 Node.js 歴 = 職業プログラマ歴 =2 年半 将棋

    , 対戦ゲーム , 音楽ゲーム が 好 き 息子 3 歳半 / 娘 10 ヶ月 2 / 104
  3. はじめに 「 フツウ 」 の サーバサイド開発に Node.js を採用しません か ?

    例 え ば 、PHP を避 け るの代わりに Node.js 3 / 104
  4. 速 い 楽し い 導入 が 楽 単体でサーバ立つ npm になんでも

    あ る フロントと同じ言語 開発盛ん etc 「 フツウに良 い」Node.js 「 一旦 Node で いい じゃん 」 の流れをつ く りた い 国内の採用事例はまだまだ少な い 4 / 104
  5. 先月の仮説 学習コスト が 高 い のでは? 東京 Node 学園 18

    時限目 LT 「 チーム開発に おい て Node 未経験者の学習コストを 下 げ る工夫 」 http://qsona.hatenablog.com/entry/2015/10/13/090847 5 / 104
  6. 個人的な見解 難し い 面は実際に あ ると思 う でも 、 それを補って余り

    あ る魅力も あ る あえ て 「 バッドノウハウ 」 をテーマにして 、 Node.js サーバ運用の 難し い と こ ろ 、 苦労感を伝 え た い 7 / 104
  7. バッドノウハウとは? ( 前略 ) 何で こ んな こ とを覚 え

    な い と いけ な い のだろ うか、 とストレスを感 じつつも 、( 中略 ) しぶしぶ覚 え な け ればならな い( 中略 ) そ う した雑多なノウ ハウの こ とを 、 本来は知りた く もな い ノウハウと いう 意味で 、 私はバッド ノウハウと呼んで い る 。 http://0xcc.net/misc/bad-knowhow.html ここ では あ まり覚 え た く な い ノウハウ 本来やりた く な い(け ど仕方な い) 手法 8 / 104
  8. ゲームサーバの特徴 定型的な APIが 少な い 単なる CRUDが ほぼな い RESTful

    と か 無理 ユーザに強 く 結びつ い て い る 「 誰に対しても同じデータを返す 」 よ う な APIが めったにな い マスターデータの存在 10 / 104
  9. マスターデータとは ゲームそのものに関してのデータ クエスト 、 イベント 、 敵 、 マップ 、

    などなど 運営側 が 用意するもの 個 々 のユーザとは紐付 か な い 11 / 104
  10. 同期コード例 f u n c t i o n s

    a m p l e _ s y n c ( p a r a m ) { v a r n u m = g e t N u m b e r S y n c ( p a r a m ) ; v a r t e x t = g e n e r a t e T e x t S y n c ( n u m ) ; c h e c k N g W o r d S y n c ( t e x t ) ; r e t u r n { n u m : n u m , t e x t : t e s t } ; } ※ 注 : checkNgWordSync は 、NG ならエラーを投 げ る こ のコード が、 非同期呼び出しになるとど う なる か 見て いく 16 / 104
  11. 非同期コード例 (plain) f u n c t i o n

    s a m p l e _ p l a i n ( p a r a m , c a l l b a c k ) { g e t N u m b e r ( f u n c t i o n ( e r r , n u m ) { i f ( e r r ) { r e t u r n c a l l b a c k ( e r r ) ; } g e n e r a t e T e x t ( n u m , f u n c t i o n ( e r r , t e x t ) { i f ( e r r ) { r e t u r n c a l l b a c k ( e r r ) ; } c h e c k N g W o r d ( t e x t , f u n c t i o n ( e r r ) { i f ( e r r ) { r e t u r n c a l l b a c k ( e r r ) ; } c a l l b a c k ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ) ; } ) ; } ) ; } ; 17 / 104
  12. 非同期コード (plain) の問題点 if (err) 句 が 無駄に多 い ネスト

    が 深 く なる ※ 注 : checkNgWordSync は 、NG なら callback にエラーを返し 、 OK なら何も引数なしで callback を呼ぶ 18 / 104
  13. async につ い て 直列な非同期コードを平た い 形に書 き 換 え

    る方法 が いく つ か 存在 async.waterfall async.series async.auto ( 直列 / 並列を自動で ) 弊社では長ら くasync.seriesが 主流だった 20 / 104
  14. ※ 次 か ら出るコードの注意点 v a r a s y

    n c = r e q u i r e ( ' a s y n c ' ) ; は省略 定義されて い な い 関数は 、 非同期メソッドとして定義されて い る ものとする 次頁 : async.series コード例 21 / 104
  15. f u n c t i o n s a

    m p l e _ s e r i e s ( p a r a m , c a l l b a c k ) { v a r n u m , t e x t ; a s y n c . s e r i e s ( [ f u n c t i o n ( n e x t ) { g e t N u m b e r ( p a r a m , f u n c t i o n ( e r r , _ n u m ) { i f ( e r r ) { r e t u r n n e x t ( e r r ) ; } n u m = _ n u m ; n e x t ( ) ; } ) ; } , f u n c t i o n ( n e x t ) { g e n e r a t e T e x t ( n u m , f u n c t i o n ( e r r , _ t e x t ) { i f ( e r r ) { r e t u r n n e x t ( e r r ) ; } t e x t = _ t e x t ; n e x t ( ) ; } ) ; } , f u n c t i o n ( n e x t ) { c h e c k N g W o r d ( t e x t , n e x t ) ; } ] , f u n c t i o n ( e r r ) { i f ( e r r ) { r e t u r n c a l l b a c k ( e r r ) ; } c a l l b a c k ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ) ; } 22 / 104
  16. f u n c t i o n s a

    m p l e _ p l a i n ( p a r a m , c a l l b a c k ) { g e t N u m b e r ( f u n c t i o n ( e r r , n u m ) { i f ( e r r ) { r e t u r n c a l l b a c k ( e r r ) ; } g e n e r a t e T e x t ( n u m , f u n c t i o n ( e r r , t e x t ) { i f ( e r r ) { r e t u r n c a l l b a c k ( e r r ) ; } c h e c k N g W o r d ( t e x t , f u n c t i o n ( e r r ) { i f ( e r r ) { r e t u r n c a l l b a c k ( e r r ) ; } c a l l b a c k ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ) ; } ) ; } ) ; } ; ※ 非同期コード例 (plain) 比較用に再掲 23 / 104
  17. async.series の問題点 i f ( e r r ) が

    減ってな い ネストは減った が、 コード量 が 増 え す ぎ と いう わ け で 、 async.waterfall にしてみた 結果 : i f ( e r r ) が な く なり 、 コード が 短 く なった 24 / 104
  18. async.waterfall コード例 f u n c t i o n

    s a m p l e _ w a t e r f a l l ( p a r a m , c a l l b a c k ) { v a r n u m , t e x t ; a s y n c . w a t e r f a l l ( [ f u n c t i o n ( n e x t ) { g e t N u m b e r ( p a r a m , n e x t ) ; } , f u n c t i o n ( _ n u m , n e x t ) { n u m = _ n u m ; g e n e r a t e T e x t ( n u m , n e x t ) ; } , f u n c t i o n ( _ t e x t , n e x t ) { t e x t = _ t e x t ; c h e c k N g W o r d ( t e x t , n e x t ) ; } , f u n c t i o n ( n e x t ) { n e x t ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ] , c a l l b a c k ) ; } 25 / 104
  19. async.waterfall の動作解説用 f u n c t i o n

    s a m p l e _ w a t e r f a l l 1 ( p a r a m , c a l l b a c k ) { v a r n u m , t e x t ; a s y n c . w a t e r f a l l ( [ f u n c t i o n ( n e x t ) { n e x t ( n u l l , 1 ) ; } , f u n c t i o n ( _ n u m , n e x t ) { n u m = _ n u m ; n e x t ( n u l l , ' t e x t ' ) ; } , f u n c t i o n ( _ t e x t , n e x t ) { t e x t = _ t e x t ; n e x t ( ) ; } , f u n c t i o n ( n e x t ) { n e x t ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ] , c a l l b a c k ) ; } 26 / 104
  20. async.waterfall の問題点 next に渡される引数の個数に応じて 、 次の関数に入る引数の個数 が 変わる ↓ あ

    る関数 が、 コールバックに渡す 引数の個数を増やす こ と が Breaking Change に繋 が る 27 / 104
  21. コード例 (OK) f u n c t i o n

    c h e c k N g W o r d ( t e x t , c a l l b a c k ) { c a l l b a c k ( ) ; } f u n c t i o n s a m p l e _ w a t e r f a l l ( p a r a m , c a l l b a c k ) { v a r n u m , t e x t ; a s y n c . w a t e r f a l l ( [ f u n c t i o n ( n e x t ) { g e t N u m b e r ( p a r a m , n e x t ) ; } , f u n c t i o n ( _ n u m , n e x t ) { n u m = _ n u m ; g e n e r a t e T e x t ( n u m , n e x t ) ; } , f u n c t i o n ( _ t e x t , n e x t ) { t e x t = _ t e x t ; c h e c k N g W o r d ( t e x t , n e x t ) ; } , f u n c t i o n ( n e x t ) { n e x t ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ] , c a l l b a c k ) ; } 28 / 104
  22. コード例 ( 動 か な い) f u n c

    t i o n c h e c k N g W o r d ( t e x t , c a l l b a c k ) { c a l l b a c k ( n u l l , { / * d e t a i l * / } ) ; } f u n c t i o n s a m p l e _ w a t e r f a l l ( p a r a m , c a l l b a c k ) { v a r n u m , t e x t ; a s y n c . w a t e r f a l l ( [ f u n c t i o n ( n e x t ) { g e t N u m b e r ( p a r a m , n e x t ) ; } , f u n c t i o n ( _ n u m , n e x t ) { n u m = _ n u m ; g e n e r a t e T e x t ( n u m , n e x t ) ; } , f u n c t i o n ( _ t e x t , n e x t ) { t e x t = _ t e x t ; c h e c k N g W o r d ( t e x t , n e x t ) ; } , f u n c t i o n ( n e x t ) { n e x t ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ] , c a l l b a c k ) ; } c h e c k N g W o r d の修正により 、 s a m p l e _ w a t e r f a l l が 動 か な く なって い る 29 / 104
  23. コード例 ( 修正後 ) f u n c t i

    o n c h e c k N g W o r d ( t e x t , c a l l b a c k ) { c a l l b a c k ( n u l l , { / * d e t a i l * / } ) ; } f u n c t i o n s a m p l e _ w a t e r f a l l ( p a r a m , c a l l b a c k ) { v a r n u m , t e x t ; a s y n c . w a t e r f a l l ( [ f u n c t i o n ( n e x t ) { g e t N u m b e r ( p a r a m , n e x t ) ; } , f u n c t i o n ( _ n u m , n e x t ) { n u m = _ n u m ; g e n e r a t e T e x t ( n u m , n e x t ) ; } , f u n c t i o n ( _ t e x t , n e x t ) { t e x t = _ t e x t ; c h e c k N g W o r d ( t e x t , n e x t ) ; } , f u n c t i o n ( u n u s e d , n e x t ) { n e x t ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ] , c a l l b a c k ) ; } 30 / 104
  24. neo-async GitHub: suguru03/neo-async async のクローン 機能を増やし 、 速度を向上させて い る

    元弊社エンジニア @suguru03 が フルスクラッチで作成 退職して語学留学へ 、 本日カナダに飛ぶよ う です 31 / 104
  25. neo-async.angelFall async.waterfall と基本的には同じ next に渡される引数の個数 が 変わっても 、 次の関数に入る引数の個数 が

    変わらな い 次頁 : コード例 ( 元 : 動 か な い => 現 : 動 く) ※async.waterfall を neo-async.angelFall に変 え ると 動 か な い サンプル が 動 く よ う になる 32 / 104
  26. v a r a s y n c = r

    e q u i r e ( ' n e o - a s y n c ' ) ; f u n c t i o n c h e c k N g W o r d ( t e x t , c a l l b a c k ) { c a l l b a c k ( n u l l , { / * d e t a i l * / } ) ; } f u n c t i o n s a m p l e _ a n g e l F a l l ( p a r a m , c a l l b a c k ) { v a r n u m , t e x t ; a s y n c . a n g e l F a l l ( [ f u n c t i o n ( n e x t ) { g e t N u m b e r ( p a r a m , n e x t ) ; } , f u n c t i o n ( _ n u m , n e x t ) { n u m = _ n u m ; g e n e r a t e T e x t ( n u m , n e x t ) ; } , f u n c t i o n ( _ t e x t , n e x t ) { t e x t = _ t e x t ; c h e c k N g W o r d ( t e x t , n e x t ) ; } , f u n c t i o n ( n e x t ) { n e x t ( n u l l , { n u m : n u m , t e x t : t e x t } ) ; } ] , c a l l b a c k ) ; } 33 / 104
  27. 技術的背景 Function.length を利用して い る 定義された仮引数の個数 が 取れる 次の関数 が

    受 け る 、 引数の個数を見て い る やや黒魔術に近 い 34 / 104
  28. Why BAD? こ れ 、 覚 え た いか… ?

    ただ 、 順番に実行するプログラミングをした い だ け なのに こ れでもまだ問題 があ る 例 え ば 、 条件分岐の問題 35 / 104
  29. 条件分岐の問題 f u n c t i o n s

    a m p l e _ s y n c ( p a r a m ) { v a r x = g e t X ( p a r a m ) ; i f ( x ) { v a r y = g e t Y ( ) ; v a r z = g e t Z ( ) ; d o S o m e t h i n g ( y , z ) ; } r e t u r n g e t W ( x ) ; } 36 / 104
  30. 非同期コード例 ( 良 く な い) f u n c

    t i o n s a m p l e _ a s y n c ( p a r a m , c a l l b a c k ) { v a r x ; a s y n c . a n g e l F a l l ( [ f u n c t i o n ( n e x t ) { g e t X ( p a r a m , n e x t ) ; } , f u n c t i o n ( _ x , n e x t ) { x = _ x ; i f ( ! x ) { r e t u r n n e x t ( ) ; } a s y n c . p a r a l l e l ( { y : g e t Y , z : g e t Z } , n e x t ) ; } , f u n c t i o n ( r e s u l t , n e x t ) { i f ( ! x ) { r e t u r n n e x t ( ) ; } d o S o m e t h i n g ( r e s u l t . y , r e s u l t . z , n e x t ) ; } , f u n c t i o n ( n e x t ) { g e t W ( x , n e x t ) : } ] , c a l l b a c k ) ; } 37 / 104
  31. 良 く な い 理由 同じ if 文 が2 つ

    あ る ネストを こ れ以上深 く しな い ためにした が… フロー が 分 か りづら い こ れ か らも同じ if 文 が 増 え る可能性 、 メンテ性悪 い GOTOが 欲し く なる ★★★★ BAD ★★★★ 38 / 104
  32. asyncblock 革命的にキレイに書 け る 、 フロー制御モジュール sync と defer があ

    る sync した場合 、 それ が 終わるまで待つ defer した場合 、 その結果 が 次に使われると こ ろで処 理を待つ async だと a s y n c . a u t o が 少し近 い 39 / 104
  33. asyncblock コード例 v a r a s y n c

    b l o c k = r e q u i r e ( ' a s y n c b l o c k ' ) ; f u n c t i o n s a m p l e _ a s y n c b l o c k ( p a r a m , c a l l b a c k ) { a s y n c b l o c k ( f u n c t i o n ( f l o w ) { f l o w . e r r o r C a l l b a c k = c a l l b a c k ; v a r x = g e t X ( p a r a m ) . d e f e r ( ) ; i f ( x ) { v a r y = g e t Y ( ) . d e f e r ( ) ; v a r z = g e t Z ( ) . d e f e r ( ) ; d o S o m e t h i n g ( y , z ) . s y n c ( ) ; } c a l l b a c k ( n u l l , g e t W ( x ) . s y n c ( ) ) ; } ) ; } 40 / 104
  34. asyncblock の技術 node-fibers GitHub: laverdet / node-fibers V8/Node 自体の拡張 source

    transformation AST を利用し 、 コードを書 き 換 え た上で 実行して い る 41 / 104
  35. 突然の _人人人人人_ >   黒魔術   <  ̄ Y^Y^Y^Y  ̄

    正規なやり方で 、こ のレベルになってほし い 42 / 104
  36. 非同期フロー制御の こ れ か ら generators (ES2015) co (GitHub: tj/co)

    async/await (ES2016?) い ずれも Promise(ES2015) 前提 Node.js 上での決定版はまだ無 い よ う に思 う 43 / 104
  37. まとめ 現状は " 覚 え た く な い ノウハウ

    " の集まり 非同期フロー制御の決定版 、 求む!! ...to be continued. 44 / 104
  38. マスターデータにまつわる話 入力支援 ( 管理画面 、 スプレッドシート等 ) 入力はエンジニア以外 が 行

    うこ と が 多 い 入力をやりやす く するのはとっても重要 管理 ( バックアップ 、 タグ付 け など ) データ が 正し いか の検証 利用 ( アプリ上での持ち方など ) 例 : マスターデータの入力支援の話 Final Fantasy Record Keeper のマスターデータを支 え る技術 (DeNA 渋川さん ) http://www.slideshare.net/dena_study/final-fantasy-record-keeper 46 / 104
  39. マスターデータの利用 Node アプリ起動時に DBか ら読み込み 、 整形してプロセス上のオブジェクトとして乗せる 通常の量なら十分乗る ID をキーにする

    99%く ら い(?) は ID で引 く ex) Quest マスタ { ' q u e s t 0 1 ' : { q u e s t _ i d : ' q u e s t 0 1 ' , m a p _ i d : ' m a p 0 1 ' , t a s k s : [ . . . ] } , ' q u e s t 0 2 ' : { q u e s t _ i d : ' q u e s t 0 2 ' , m a p _ i d : ' m a p 0 2 ' , t a s k s : [ . . . ] } } 48 / 104
  40. 有利な点 データストアとして最速 非同期呼び出しにならな い ( 起動時に ) 好 き なよ

    う に変形して持てる 使 い やす い よ う に 、 冗長に持つ リレーション => 実体を直接参照 49 / 104
  41. 注意点と対策 注意点 : DB と違 い、index が な い 大量にデータ

    が 有り 、ID 以外で引 く 場合に問題になる 対策 : 起動時にソートし 、 別に保持 index の代わり クエリ が 固定なら 、 起動時に検索を行 い それを保持 50 / 104
  42. 注意点と対策 (2) 注意点 : 誤って書 き 換 え てしま う

    可能性 があ る ただの JavaScript 上のオブジェクトなので 。。 やら か して障害にした経験 あ り 対策 : 起動時に Object.freeze を再帰的に行 う ' u s e s t r i c t ' 変更しよ う とした瞬間に TypeError ??? 15m 51 / 104
  43. Fat Service, Skinny Model Bad 度 ★★★☆☆ Node 度 ★★★☆☆

    ゲーム度 ★★★☆☆ か たさ か ため 53 / 104
  44. オブジェクト指向 オブジェクト = データ + 振る舞 い モデル と多分ほぼ同義 サービス

    … 複数のモデル間のやりとり よ くあ る例 : 口座間送金 55 / 104
  45. Fat Model, Skinny Controller とは Ruby on Rails で発生した考 え

    方 ( だと思 う) コントローラはなるべ く 単純な入出力を担当し 、 ロジックを持たな い ビジネスロジックは極力モデル が 持つ モデルをしっ か り設計すべ き DDD (Domain-driven design) 56 / 104
  46. ORM/ODM DBか ら取得したデータに 、 振る舞 い( メソッド ) を 持たせてモデルとする

    Rails で いう ActiveRecord Node では Mongoose (MongoDB との ODM) が 有名 57 / 104
  47. Fat Service, Skinny Model とは 弊社のゲームに おけ るコードの状態 ほぼ全てのロジック が

    services/ に置 か れる モデル が 作られる こ と が 少な い つまり 、 オブジェクト指向ではな い 58 / 104
  48. そ う なる理由 モデルを作るの が 大変 モデルを作るメリット が 薄 い

    非同期処理 が モデルに混ざると辛 い 問題 59 / 104
  49. モデルを作るの が 大変 弊社ゲーム開発では ORM/ODM を利用して い な い DB

    アクセスで 、 単なるデータオブジェクトを受 け 取る 理由? 1 つのテーブルのデータ単体では大抵意味をなさず 、 必ずマスターデータ が 関係する 60 / 104
  50. モデルを作るの が 大変 (2) モデルを作る時 、 た い て い

    以下のコード が 必要 初期化処理 (DBか らのデータとマスターデータを混合させる ) フロントへ返却する形に変換する処理 DB へ update する形に変換する処理 ここ は ES5 の setter を上手 いこ と使 え ば不要にで き る か も? 61 / 104
  51. モデルを作るメリット が 薄 い 特にソーシャルゲーム的な話 複数のデータを集めて き て ご にょ

    ご にょ … する仕様 が 多 い あ るものを一つの 「 モデル 」 の対象として考察する 必要性 が 薄 い 62 / 104
  52. 非同期処理 が モデルに混ざると辛 い 問題 コールバックスタイルとオブジェクト指向を マッチさせるの が 難し い

    一つ非同期メソッド があ れば 、 全部非同期メソッドになる覚 悟 が 必要 63 / 104
  53. コード例 f u n c t i o n C

    a r ( ) { / * . . . * / } ; C a r . p r o t o t y p e . d r i v e = f u n c t i o n ( ) { / * . . . * / } ; C a r . p r o t o t y p e . p l a y = f u n c t i o n ( ) { t h i s . d r i v e ( ) ; } ; f u n c t i o n S a g a w a ( ) { / * . . . * / } u t i l . i n h e r i t s ( S a g a w a , C a r ) ; S a g a w a . p r o t o t y p e . p l a y = f u n c t i o n ( ) { t h i s . t e l ( ) ; t h i s . d r i v e ( ) ; } ; Sagawa#telが 非同期呼び出しになると 、Car#playが 非同期になる 64 / 104
  54. 非同期呼び出し が 出来な い と オブジェクト生成前に 、 全部データを用意しな け れ

    ばならな い まれにし か 使われな い 附帯的なデータで あ っても 外部 API を叩 け な い 重 い 処理に setImmediate をはさむのも出来な い 65 / 104
  55. Why BAD? オブジェクト指向でも関数型指向でもな い、 いか にも手続 き 型なコードを書 き 続

    け て い る と いう 現状に 、 やや不安を覚 え て い る 67 / 104
  56. モデルを利用すべ き 場面 ゲームの中心的な部分の実装では モデルを利用したほ うが 良 いこ と が

    多 か った バトルゲームのバトル パズルゲームのパズル ブラウザゲームならフロントとロジック共通化も JavaScript の旨味 があ る 68 / 104
  57. グローバル変数 Date の上書 き Bad 度 ★★☆☆☆ Node 度 ★★★☆☆

    ゲーム度 ★★★☆☆ か たさ やわめ 70 / 104
  58. 既存の手法 例 え ば 、36 時間後の状態にした い 時 1. サーバマシンの時刻を

    36 時間すすめる 2. マスターデータを 36 時間巻 き 戻す 73 / 104
  59. 既存の手法の問題点 1. サーバマシンの時刻を 36 時間すすめる 影響 が 未知数 2. マスターデータを

    36 時間巻 き 戻す 確認にリアルさ が な い(ex. 表示される日付 が ずれる ) 「 曜日 」「 日付 」 に関する項目は確認で き な い 74 / 104
  60. そ こ で JavaScript の Date を変更する Date = (global.Date

    =) 変更内容 : new Date() ( 引数なし ) Date.now() それ以外は 、 そのまま 75 / 104
  61. 上書 き の理由 prototype 拡張ではな く、 上書 き じゃな い

    とダメ new Date() の対応 が 必要 prototype 拡張では 、 関数自体は変更で き な い 76 / 104
  62. 問題点 吐 き 出すログの時間までずれる 対策 : ログ吐 く 関数を以下のよ う

    に書 き 換 え る ログ吐 く 瞬間だ け、 時間戻す ログ吐 い たら 、 また時間をずらす 78 / 104
  63. Why BAD? 行儀 が 悪 い 行儀 が 悪 い

    行為には危険 があ る prototype 拡張とぶつ か った話 79 / 104
  64. process.on('uncaughtException') ... そして何もな か ったよ う に続行 Bad 度 ★★★★★

    Node 度 ★★★★★ ゲーム度 ★★☆☆☆ か たさ ふつ う 82 / 104
  65. 概要 問題 : 予期せぬエラーの発生時 、 サーバ が 終了してしま う 解決法

    : p r o c e s s . o n ( ' u n c a u g h t E x c e p t i o n ' ) ( または domain の利用 ) 83 / 104
  66. 背景 Node.js に おい て例外 がthrow された場合 、 プロセス が

    終了する サーバ自体 が 終了する cluster 利用時は 、 発生した workerが 終了 普通は 、 終了を検知して再起動する (pm2 など ) 84 / 104
  67. 再起動するとど う なる か 再起動には時間 がかか る ( ゲームの場合 )

    特にマスターデータの影響 DBか ら読み込んでプロセスにのせる 使 い やす い 形に変形 Object.freeze を deep に行 う 85 / 104
  68. と あ る日の再起動ログ ( 抜粋 ) 1 9 : 5

    3 : 5 1 i n f o i n i t i a l i z e m o d u l e s 1 9 : 5 3 : 5 2 i n f o c o n f i g u r e m a s t e r 1 9 : 5 3 : 5 2 i n f o l o a d i n g m a s t e r 1 9 : 5 3 : 5 5 i n f o [ m a s t e r c h a n g e r ] c h a n g e m a s t e r d a t a : e n e m y 1 9 : 5 3 : 5 5 i n f o [ m a s t e r c h a n g e r ] c h a n g e m a s t e r d a t a : g a c h a 1 9 : 5 3 : 5 6 i n f o [ m a s t e r c h a n g e r ] a d d e d m a s t e r : c o m b i n a t i o n _ s k i l l _ h a s h 1 9 : 5 3 : 5 6 i n f o [ m a s t e r c h a n g e r ] a d d e d m a s t e r : i t e m _ b y _ t y p e 1 9 : 5 3 : 5 6 w a r n [ m a s t e r c h a n g e r ] d e l e t e d 3 4 k e y s f r o m n o t i c e 1 9 : 5 3 : 5 7 w a r n [ m a s t e r c h a n g e r ] a d d e d 2 8 2 0 k e y s t o e n e m y _ s k i l l 1 9 : 5 3 : 5 7 i n f o l o a d i n g m a s t e r / c h a n g i n g m a s t e r : d o n e . t i m e : 4 8 8 2 m s 1 9 : 5 3 : 5 7 i n f o f r e e z i n g m a s t e r 1 9 : 5 4 : 1 1 i n f o f r e e z i n g m a s t e r : d o n e . t i m e : 1 4 2 5 3 m s 1 9 : 5 4 : 1 2 i n f o S t a r t t o i n i t i a l i z e N e w R e l i c . 1 9 : 5 4 : 1 3 i n f o s e r v e r s t a r t e d キャッシュへの読み込みに 3 秒 キャッシュの変形に 2 秒 Object.freeze に 14 秒 86 / 104
  69. 問題 1. あ るユーザのデータ が 壊れる + バグにより TypeError 発生

    2. そのユーザ が リクエストする度に発生する 3. 再起動 が 連続で かか り 、 到底間に合わな い 4. 全ユーザ が 利用不能に 87 / 104
  70. やり方 1 uncaughtException をハンドルする 起動時に呼ばれるコードで p r o c e

    s s . o n ( ' u n c a u g h t E x c e p t i o n ' , f u n c t i o n ( e r r ) { c o n s o l e . l o g ( e r r ) ; / / 障害端末を鳴らすコー ド / / 終了はしない } ) ; 89 / 104
  71. やり方 2 domain を利用する 例 え ば Express のミドルウェアに次のものを入れる v

    a r d o m a i n = r e q u i r e ( ' d o m a i n ' ) ; / / m i d d l e w a r e f u n c t i o n ( r e q , r e s , n e x t ) { v a r d = d o m a i n . c r e a t e ( ) ; d . o n ( ' e r r o r ' , f u n c t i o n ( e r r ) { / / 障害端末を鳴らすコー ド n e x t ( e r r ) ; } ) ; d . r u n ( n e x t ) ; } 90 / 104
  72. やり方 1 と 2 の比較 domain を使 う と 、

    リクエストの情報 が 落ち な い で済む ちゃんとレスポンスを返せる ユーザ情報をログに出せる 91 / 104
  73. キャッチしな い と起 こ る現象 TypeError を出すと即障害になる TypeError を出さな いこ

    と が 正義 TypeError を全 く 出さな い のは難し い a && a.b && a.b.c のよ う な書 き 方 が 流行る ( 不必要で あ っても ) 「 ナチュラルな握りつぶし 」 96 / 104
  74. 余談 : neo-async 誕生秘話 1. async.each の第一引数に undefinedが 渡って TypeError

    出た 2. 連続再起動 、 しばら く ユーザ が アクセスで き な い 障 害に 3. neo-async の誕生 !! 97 / 104
  75. Why BAD? 何由来 か 全 く 不明なエラーを 、 握りつぶして い

    る 予期せぬエラーにより 、 状態 が 不定に ステートの変更 が 中途半端で終わるなど リクエストスレッド が な い ので 、 アプリ自体に影響し う る 98 / 104
  76. callback(err) と throw err 基本的に 、 バグでな い 例外は callback(err)

    で通知すべ き try-catch は JSON.parse/stringify 等だ け 使 う 同期関数は … ? return Error する? 99 / 104
  77. 議論を深めた い callback(err) と throw err の使 い 分 け

    ? 単純に 「 通常の例外 / バグ 」 とは区別で き な い 非同期関数中で callback(err) 出来な く て困る話 try-catch 強力過 ぎ る 100 / 104
  78. 議論を深めた い 「 終了すべ き エラー 」 を明確にで き な

    いか ? で き れば TypeError は含まれな い でほし い 全てではな く、「ここ まで終了 」 のよ う な仕組み? 起動時のセットアップはそのまま それ以外のものは全部リセット 101 / 104
  79. BAD No.3 まとめ 今回紹介したのは 、 やっては いけ な い 手法で

    あ る とは いえ、 現状やらな い とサーバ運用辛 い 議論を深め 、 正し い やり方で出来るよ う になってほ し い 102 / 104
  80. 全体のまとめ 結論 、 サーバサイドの開発で問題になるのは 非同期フロー制御 エラー処理 古 く て新し い

    話題 ES2015, 2016 やそれを含む Node の新バージョンで解決 されて いくこ と が 望まれる 103 / 104