Slide 1

Slide 1 text

東京 Node 学園祭 2015 Node.js でのゲームサーバ開発 愛すべ き バッドノウハウ 3 選 株式会社サイバーエージェント 森 久太郎 Twitter/GitHub/ はてなブログ : @qsona 1 / 104

Slide 2

Slide 2 text

自己紹介 Node.js でゲームのサーバ開発中 Node.js 歴 = 職業プログラマ歴 =2 年半 将棋 , 対戦ゲーム , 音楽ゲーム が 好 き 息子 3 歳半 / 娘 10 ヶ月 2 / 104

Slide 3

Slide 3 text

はじめに 「 フツウ 」 の サーバサイド開発に Node.js を採用しません か ? 例 え ば 、PHP を避 け るの代わりに Node.js 3 / 104

Slide 4

Slide 4 text

速 い 楽し い 導入 が 楽 単体でサーバ立つ npm になんでも あ る フロントと同じ言語 開発盛ん etc 「 フツウに良 い」Node.js 「 一旦 Node で いい じゃん 」 の流れをつ く りた い 国内の採用事例はまだまだ少な い 4 / 104

Slide 5

Slide 5 text

先月の仮説 学習コスト が 高 い のでは? 東京 Node 学園 18 時限目 LT 「 チーム開発に おい て Node 未経験者の学習コストを 下 げ る工夫 」 http://qsona.hatenablog.com/entry/2015/10/13/090847 5 / 104

Slide 6

Slide 6 text

今日の仮説 Node.js、 サーバサイド開発に 向 い てな い のでは? ( 元も子もな い よ う だ が) 割と一般的な意見 6 / 104

Slide 7

Slide 7 text

個人的な見解 難し い 面は実際に あ ると思 う でも 、 それを補って余り あ る魅力も あ る あえ て 「 バッドノウハウ 」 をテーマにして 、 Node.js サーバ運用の 難し い と こ ろ 、 苦労感を伝 え た い 7 / 104

Slide 8

Slide 8 text

バッドノウハウとは? ( 前略 ) 何で こ んな こ とを覚 え な い と いけ な い のだろ うか、 とストレスを感 じつつも 、( 中略 ) しぶしぶ覚 え な け ればならな い( 中略 ) そ う した雑多なノウ ハウの こ とを 、 本来は知りた く もな い ノウハウと いう 意味で 、 私はバッド ノウハウと呼んで い る 。 http://0xcc.net/misc/bad-knowhow.html ここ では あ まり覚 え た く な い ノウハウ 本来やりた く な い(け ど仕方な い) 手法 8 / 104

Slide 9

Slide 9 text

本セッションのコンテキスト ゲームの API サーバ開発に限って論じる 特にソーシャルゲームを想定 サーバ開発一般に通じる話 が 多 い はず ECMAScript5 までに限定する 現場の泥臭 い 話 が 中心 9 / 104

Slide 10

Slide 10 text

ゲームサーバの特徴 定型的な APIが 少な い 単なる CRUDが ほぼな い RESTful と か 無理 ユーザに強 く 結びつ い て い る 「 誰に対しても同じデータを返す 」 よ う な APIが めったにな い マスターデータの存在 10 / 104

Slide 11

Slide 11 text

マスターデータとは ゲームそのものに関してのデータ クエスト 、 イベント 、 敵 、 マップ 、 などなど 運営側 が 用意するもの 個 々 のユーザとは紐付 か な い 11 / 104

Slide 12

Slide 12 text

ここか らバッドノウハウ 3 選 12 / 104

Slide 13

Slide 13 text

BAD No.1 非同期フロー制御に関する戦 い 〜neo-async.angelFall に至るまで 〜 13 / 104

Slide 14

Slide 14 text

非同期フロー制御に関する戦 い 〜neo-async.angelFall に至るまで 〜 Bad 度 ★★★☆☆ Node 度 ★★★★★ ゲーム度 ★★☆☆☆ か たさ ふつ う 14 / 104

Slide 15

Slide 15 text

概要 問題 : callback 地獄 解決法 ( 一旦 ): neo-async.angelFall ※ES5 まで 15 / 104

Slide 16

Slide 16 text

同期コード例 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

Slide 17

Slide 17 text

非同期コード例 (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

Slide 18

Slide 18 text

非同期コード (plain) の問題点 if (err) 句 が 無駄に多 い ネスト が 深 く なる ※ 注 : checkNgWordSync は 、NG なら callback にエラーを返し 、 OK なら何も引数なしで callback を呼ぶ 18 / 104

Slide 19

Slide 19 text

非同期フロー制御 非同期フローを制御するモジュールにより 、 問題を解決する 弊社では async (GitHub: caolan/async) を利用して き た 19 / 104

Slide 20

Slide 20 text

async につ い て 直列な非同期コードを平た い 形に書 き 換 え る方法 が いく つ か 存在 async.waterfall async.series async.auto ( 直列 / 並列を自動で ) 弊社では長ら くasync.seriesが 主流だった 20 / 104

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

async.series の問題点 i f ( e r r ) が 減ってな い ネストは減った が、 コード量 が 増 え す ぎ と いう わ け で 、 async.waterfall にしてみた 結果 : i f ( e r r ) が な く なり 、 コード が 短 く なった 24 / 104

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

async.waterfall の問題点 next に渡される引数の個数に応じて 、 次の関数に入る引数の個数 が 変わる ↓ あ る関数 が、 コールバックに渡す 引数の個数を増やす こ と が Breaking Change に繋 が る 27 / 104

Slide 28

Slide 28 text

コード例 (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

Slide 29

Slide 29 text

コード例 ( 動 か な い) 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

Slide 30

Slide 30 text

コード例 ( 修正後 ) 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

Slide 31

Slide 31 text

neo-async GitHub: suguru03/neo-async async のクローン 機能を増やし 、 速度を向上させて い る 元弊社エンジニア @suguru03 が フルスクラッチで作成 退職して語学留学へ 、 本日カナダに飛ぶよ う です 31 / 104

Slide 32

Slide 32 text

neo-async.angelFall async.waterfall と基本的には同じ next に渡される引数の個数 が 変わっても 、 次の関数に入る引数の個数 が 変わらな い 次頁 : コード例 ( 元 : 動 か な い => 現 : 動 く) ※async.waterfall を neo-async.angelFall に変 え ると 動 か な い サンプル が 動 く よ う になる 32 / 104

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

技術的背景 Function.length を利用して い る 定義された仮引数の個数 が 取れる 次の関数 が 受 け る 、 引数の個数を見て い る やや黒魔術に近 い 34 / 104

Slide 35

Slide 35 text

Why BAD? こ れ 、 覚 え た いか… ? ただ 、 順番に実行するプログラミングをした い だ け なのに こ れでもまだ問題 があ る 例 え ば 、 条件分岐の問題 35 / 104

Slide 36

Slide 36 text

条件分岐の問題 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

Slide 37

Slide 37 text

非同期コード例 ( 良 く な い) 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

Slide 38

Slide 38 text

良 く な い 理由 同じ if 文 が2 つ あ る ネストを こ れ以上深 く しな い ためにした が… フロー が 分 か りづら い こ れ か らも同じ if 文 が 増 え る可能性 、 メンテ性悪 い GOTOが 欲し く なる ★★★★ BAD ★★★★ 38 / 104

Slide 39

Slide 39 text

asyncblock 革命的にキレイに書 け る 、 フロー制御モジュール sync と defer があ る sync した場合 、 それ が 終わるまで待つ defer した場合 、 その結果 が 次に使われると こ ろで処 理を待つ async だと a s y n c . a u t o が 少し近 い 39 / 104

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

asyncblock の技術 node-fibers GitHub: laverdet / node-fibers V8/Node 自体の拡張 source transformation AST を利用し 、 コードを書 き 換 え た上で 実行して い る 41 / 104

Slide 42

Slide 42 text

突然の _人人人人人_ >   黒魔術   <  ̄ Y^Y^Y^Y  ̄ 正規なやり方で 、こ のレベルになってほし い 42 / 104

Slide 43

Slide 43 text

非同期フロー制御の こ れ か ら generators (ES2015) co (GitHub: tj/co) async/await (ES2016?) い ずれも Promise(ES2015) 前提 Node.js 上での決定版はまだ無 い よ う に思 う 43 / 104

Slide 44

Slide 44 text

まとめ 現状は " 覚 え た く な い ノウハウ " の集まり 非同期フロー制御の決定版 、 求む!! ...to be continued. 44 / 104

Slide 45

Slide 45 text

BAD No.2 … の前に 、 少し休憩しな が ら マスターデータに関するノウハウ 45 / 104

Slide 46

Slide 46 text

マスターデータにまつわる話 入力支援 ( 管理画面 、 スプレッドシート等 ) 入力はエンジニア以外 が 行 うこ と が 多 い 入力をやりやす く するのはとっても重要 管理 ( バックアップ 、 タグ付 け など ) データ が 正し いか の検証 利用 ( アプリ上での持ち方など ) 例 : マスターデータの入力支援の話 Final Fantasy Record Keeper のマスターデータを支 え る技術 (DeNA 渋川さん ) http://www.slideshare.net/dena_study/final-fantasy-record-keeper 46 / 104

Slide 47

Slide 47 text

概要 マスターデータを アプリ上で う ま く 利用するためのノウハウ 47 / 104

Slide 48

Slide 48 text

マスターデータの利用 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

Slide 49

Slide 49 text

有利な点 データストアとして最速 非同期呼び出しにならな い ( 起動時に ) 好 き なよ う に変形して持てる 使 い やす い よ う に 、 冗長に持つ リレーション => 実体を直接参照 49 / 104

Slide 50

Slide 50 text

注意点と対策 注意点 : DB と違 い、index が な い 大量にデータ が 有り 、ID 以外で引 く 場合に問題になる 対策 : 起動時にソートし 、 別に保持 index の代わり クエリ が 固定なら 、 起動時に検索を行 い それを保持 50 / 104

Slide 51

Slide 51 text

注意点と対策 (2) 注意点 : 誤って書 き 換 え てしま う 可能性 があ る ただの JavaScript 上のオブジェクトなので 。。 やら か して障害にした経験 あ り 対策 : 起動時に Object.freeze を再帰的に行 う ' u s e s t r i c t ' 変更しよ う とした瞬間に TypeError ??? 15m 51 / 104

Slide 52

Slide 52 text

BAD No.2 Fat Service, Skinny Model 52 / 104

Slide 53

Slide 53 text

Fat Service, Skinny Model Bad 度 ★★★☆☆ Node 度 ★★★☆☆ ゲーム度 ★★★☆☆ か たさ か ため 53 / 104

Slide 54

Slide 54 text

概要 以下のよ う なノウハウ データはそのまま扱 い、 モデル化しな い ORM などを利用しな い 「 サービス層 」 にすべてのロジックを書 く 54 / 104

Slide 55

Slide 55 text

オブジェクト指向 オブジェクト = データ + 振る舞 い モデル と多分ほぼ同義 サービス … 複数のモデル間のやりとり よ くあ る例 : 口座間送金 55 / 104

Slide 56

Slide 56 text

Fat Model, Skinny Controller とは Ruby on Rails で発生した考 え 方 ( だと思 う) コントローラはなるべ く 単純な入出力を担当し 、 ロジックを持たな い ビジネスロジックは極力モデル が 持つ モデルをしっ か り設計すべ き DDD (Domain-driven design) 56 / 104

Slide 57

Slide 57 text

ORM/ODM DBか ら取得したデータに 、 振る舞 い( メソッド ) を 持たせてモデルとする Rails で いう ActiveRecord Node では Mongoose (MongoDB との ODM) が 有名 57 / 104

Slide 58

Slide 58 text

Fat Service, Skinny Model とは 弊社のゲームに おけ るコードの状態 ほぼ全てのロジック が services/ に置 か れる モデル が 作られる こ と が 少な い つまり 、 オブジェクト指向ではな い 58 / 104

Slide 59

Slide 59 text

そ う なる理由 モデルを作るの が 大変 モデルを作るメリット が 薄 い 非同期処理 が モデルに混ざると辛 い 問題 59 / 104

Slide 60

Slide 60 text

モデルを作るの が 大変 弊社ゲーム開発では ORM/ODM を利用して い な い DB アクセスで 、 単なるデータオブジェクトを受 け 取る 理由? 1 つのテーブルのデータ単体では大抵意味をなさず 、 必ずマスターデータ が 関係する 60 / 104

Slide 61

Slide 61 text

モデルを作るの が 大変 (2) モデルを作る時 、 た い て い 以下のコード が 必要 初期化処理 (DBか らのデータとマスターデータを混合させる ) フロントへ返却する形に変換する処理 DB へ update する形に変換する処理 ここ は ES5 の setter を上手 いこ と使 え ば不要にで き る か も? 61 / 104

Slide 62

Slide 62 text

モデルを作るメリット が 薄 い 特にソーシャルゲーム的な話 複数のデータを集めて き て ご にょ ご にょ … する仕様 が 多 い あ るものを一つの 「 モデル 」 の対象として考察する 必要性 が 薄 い 62 / 104

Slide 63

Slide 63 text

非同期処理 が モデルに混ざると辛 い 問題 コールバックスタイルとオブジェクト指向を マッチさせるの が 難し い 一つ非同期メソッド があ れば 、 全部非同期メソッドになる覚 悟 が 必要 63 / 104

Slide 64

Slide 64 text

コード例 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

Slide 65

Slide 65 text

非同期呼び出し が 出来な い と オブジェクト生成前に 、 全部データを用意しな け れ ばならな い まれにし か 使われな い 附帯的なデータで あ っても 外部 API を叩 け な い 重 い 処理に setImmediate をはさむのも出来な い 65 / 104

Slide 66

Slide 66 text

問題 が 多 い services/ に全部書 く の が80% の場合楽 66 / 104

Slide 67

Slide 67 text

Why BAD? オブジェクト指向でも関数型指向でもな い、 いか にも手続 き 型なコードを書 き 続 け て い る と いう 現状に 、 やや不安を覚 え て い る 67 / 104

Slide 68

Slide 68 text

モデルを利用すべ き 場面 ゲームの中心的な部分の実装では モデルを利用したほ うが 良 いこ と が 多 か った バトルゲームのバトル パズルゲームのパズル ブラウザゲームならフロントとロジック共通化も JavaScript の旨味 があ る 68 / 104

Slide 69

Slide 69 text

BAD No.2.5 グローバル変数 Date の上書 き 69 / 104

Slide 70

Slide 70 text

グローバル変数 Date の上書 き Bad 度 ★★☆☆☆ Node 度 ★★★☆☆ ゲーム度 ★★★☆☆ か たさ やわめ 70 / 104

Slide 71

Slide 71 text

概要 問題 : 時刻を操りた い 解決法 : Date を上書 き する 71 / 104

Slide 72

Slide 72 text

背景 時限式でリリースさせる機能・データに関しての動作 確認を行 い た い イベント 曜日限定クエスト 誕生日キャンペーン マスターデータで時間 / 日付 / 曜日などを指定してる 72 / 104

Slide 73

Slide 73 text

既存の手法 例 え ば 、36 時間後の状態にした い 時 1. サーバマシンの時刻を 36 時間すすめる 2. マスターデータを 36 時間巻 き 戻す 73 / 104

Slide 74

Slide 74 text

既存の手法の問題点 1. サーバマシンの時刻を 36 時間すすめる 影響 が 未知数 2. マスターデータを 36 時間巻 き 戻す 確認にリアルさ が な い(ex. 表示される日付 が ずれる ) 「 曜日 」「 日付 」 に関する項目は確認で き な い 74 / 104

Slide 75

Slide 75 text

そ こ で JavaScript の Date を変更する Date = (global.Date =) 変更内容 : new Date() ( 引数なし ) Date.now() それ以外は 、 そのまま 75 / 104

Slide 76

Slide 76 text

上書 き の理由 prototype 拡張ではな く、 上書 き じゃな い とダメ new Date() の対応 が 必要 prototype 拡張では 、 関数自体は変更で き な い 76 / 104

Slide 77

Slide 77 text

time-master 作った GitHub: qsona/time-master TimeMaster.WrappedDate 時刻をずらした Date TimeMaster.forward() で時刻をずらす TimeMaster.overwrite() でグローバル変数上書 き Fork & Star me on GitHub!! 77 / 104

Slide 78

Slide 78 text

問題点 吐 き 出すログの時間までずれる 対策 : ログ吐 く 関数を以下のよ う に書 き 換 え る ログ吐 く 瞬間だ け、 時間戻す ログ吐 い たら 、 また時間をずらす 78 / 104

Slide 79

Slide 79 text

Why BAD? 行儀 が 悪 い 行儀 が 悪 い 行為には危険 があ る prototype 拡張とぶつ か った話 79 / 104

Slide 80

Slide 80 text

80 / 104

Slide 81

Slide 81 text

BAD No.3 process.on('uncaughtException') ... そして何もな か ったよ う に続行 81 / 104

Slide 82

Slide 82 text

process.on('uncaughtException') ... そして何もな か ったよ う に続行 Bad 度 ★★★★★ Node 度 ★★★★★ ゲーム度 ★★☆☆☆ か たさ ふつ う 82 / 104

Slide 83

Slide 83 text

概要 問題 : 予期せぬエラーの発生時 、 サーバ が 終了してしま う 解決法 : 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

Slide 84

Slide 84 text

背景 Node.js に おい て例外 がthrow された場合 、 プロセス が 終了する サーバ自体 が 終了する cluster 利用時は 、 発生した workerが 終了 普通は 、 終了を検知して再起動する (pm2 など ) 84 / 104

Slide 85

Slide 85 text

再起動するとど う なる か 再起動には時間 がかか る ( ゲームの場合 ) 特にマスターデータの影響 DBか ら読み込んでプロセスにのせる 使 い やす い 形に変形 Object.freeze を deep に行 う 85 / 104

Slide 86

Slide 86 text

と あ る日の再起動ログ ( 抜粋 ) 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

Slide 87

Slide 87 text

問題 1. あ るユーザのデータ が 壊れる + バグにより TypeError 発生 2. そのユーザ が リクエストする度に発生する 3. 再起動 が 連続で かか り 、 到底間に合わな い 4. 全ユーザ が 利用不能に 87 / 104

Slide 88

Slide 88 text

そ こ で エラー が 起 き ても 、 プロセスを終了しな い よ う にした い 88 / 104

Slide 89

Slide 89 text

やり方 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

Slide 90

Slide 90 text

やり方 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

Slide 91

Slide 91 text

やり方 1 と 2 の比較 domain を使 う と 、 リクエストの情報 が 落ち な い で済む ちゃんとレスポンスを返せる ユーザ情報をログに出せる 91 / 104

Slide 92

Slide 92 text

だめ 1 (API docs) https://nodejs.org/api/process.html#process_event_uncaughtexception 92 / 104

Slide 93

Slide 93 text

だめ 2 (API docs) https://nodejs.org/api/domain.html#domain_warning_don_t_ignore_errors 93 / 104

Slide 94

Slide 94 text

だめ 3 Error Handling in Node.js https://www.joyent.com/developers/node/design/errors Joyent 公式のドキュメント 94 / 104

Slide 95

Slide 95 text

95 / 104

Slide 96

Slide 96 text

キャッチしな い と起 こ る現象 TypeError を出すと即障害になる TypeError を出さな いこ と が 正義 TypeError を全 く 出さな い のは難し い a && a.b && a.b.c のよ う な書 き 方 が 流行る ( 不必要で あ っても ) 「 ナチュラルな握りつぶし 」 96 / 104

Slide 97

Slide 97 text

余談 : neo-async 誕生秘話 1. async.each の第一引数に undefinedが 渡って TypeError 出た 2. 連続再起動 、 しばら く ユーザ が アクセスで き な い 障 害に 3. neo-async の誕生 !! 97 / 104

Slide 98

Slide 98 text

Why BAD? 何由来 か 全 く 不明なエラーを 、 握りつぶして い る 予期せぬエラーにより 、 状態 が 不定に ステートの変更 が 中途半端で終わるなど リクエストスレッド が な い ので 、 アプリ自体に影響し う る 98 / 104

Slide 99

Slide 99 text

callback(err) と throw err 基本的に 、 バグでな い 例外は callback(err) で通知すべ き try-catch は JSON.parse/stringify 等だ け 使 う 同期関数は … ? return Error する? 99 / 104

Slide 100

Slide 100 text

議論を深めた い callback(err) と throw err の使 い 分 け ? 単純に 「 通常の例外 / バグ 」 とは区別で き な い 非同期関数中で callback(err) 出来な く て困る話 try-catch 強力過 ぎ る 100 / 104

Slide 101

Slide 101 text

議論を深めた い 「 終了すべ き エラー 」 を明確にで き な いか ? で き れば TypeError は含まれな い でほし い 全てではな く、「ここ まで終了 」 のよ う な仕組み? 起動時のセットアップはそのまま それ以外のものは全部リセット 101 / 104

Slide 102

Slide 102 text

BAD No.3 まとめ 今回紹介したのは 、 やっては いけ な い 手法で あ る とは いえ、 現状やらな い とサーバ運用辛 い 議論を深め 、 正し い やり方で出来るよ う になってほ し い 102 / 104

Slide 103

Slide 103 text

全体のまとめ 結論 、 サーバサイドの開発で問題になるのは 非同期フロー制御 エラー処理 古 く て新し い 話題 ES2015, 2016 やそれを含む Node の新バージョンで解決 されて いくこ と が 望まれる 103 / 104

Slide 104

Slide 104 text

Thanks! contributors: @HAKASHUN (Twitter) @tito_net (Twitter) @suguru03 (GitHub) その他 、 アドバイスを頂 い た皆様 104 / 104