Slide 1

Slide 1 text

Cra$ing  Rails4  Applica0on   Masatoshi  Iwasaki 5.  Streaming  Server  Events  to  Clients   Asynchronously

Slide 2

Slide 2 text

概要 •  Railsで非同期にサーバーからクライアントに レスポンスを送り続ける方法を解説   •  WebSocketではなく Server  Sent  Events(SSE)を 使う方法を解説。   •  これまでのplugin開発に継ぎ足してSSEを実装   – ここまでの章を読んで理解してないと多少つらい

Slide 3

Slide 3 text

Introduc0on •  ウェブアプリの大半は単一リクエスト&レスポ ンスで事足りる。   – だが、そうでない場合もある。   •  クライアントとの接続を保持したまま、サー バーがクライアントにデータを送りたい。   – いわゆるストリーミング   •  本章ではストリーミングをRailsで使う方法を紹 介する。

Slide 4

Slide 4 text

本章のゴール •  Railsアプリからブラウザに対して、CSSが変更 されたら通知を送るプラグインを作る。   •  プラグインはコントローラやアセットなどを持 つため、Rails  Engineを利用したプラグイン開 発を行う。

Slide 5

Slide 5 text

(補足)   読んでいてちょっと混乱したところ •  Pollingの話が二カ所出てくる   – ブラウザからサーバーへのpolling   – サーバー上でのファイル変更検知   •  CSSについては何が変更されたかを通知する だけ   – CSSのURLは変わらないので、ブラウザ側で変更 通知を受け取ったら再度同じURLからjsで再読み 込みする。   – CSS自体は送らないので、後で出てくるSSEではイ ベント名だけが重要でdataは空のまま。  

Slide 6

Slide 6 text

まずEngineを作る •  これまでと違って  –full    があるのが特徴。   •  これにより、config/routes.rbやapp  ディレクト リが出来てcontrollerやmodelの追加が可能 になる。  

Slide 7

Slide 7 text

Rails::Engineの継承 •  lib/live_assets/engine.rb  (画像左)で   Rails::Engineを継承しているのが分かる。   •  Rails::Engineは可能な限り先に読み込まれな いとまずいのでlib/live_assets.rb(画像右)で 読み込んでいる。  

Slide 8

Slide 8 text

Rails::Engineとは? •  Rails::Rail0eとほとんど同じ。   •  違いはいくつかの初期設定とroutes.rbに含ま れるルーティング情報がないこと。   •  よって、routesを設定に追加しないといけない。

Slide 9

Slide 9 text

Rail0eのroutesについて •  こんな感じで設定されている。 •   eager_load,  load_pathなどpath毎に設定項目が ある。

Slide 10

Slide 10 text

Ini0alizers •  Pathだけでは十分ではない。   •  Engineが自身をbootするために初期化しなけ ればいけない項目が他にもいくつかある。   •  これらはRails::Engine.ini0alizersを見れば分か る。

Slide 11

Slide 11 text

LiveStreamingコントローラ •  まずは初期バージョンとして、コネクションが 生きている限り1秒ごとに寝て起きてハローと 叫ぶコントローラから。

Slide 12

Slide 12 text

•  もちろんroutes.rbに登録する。   •  今回はhelloアクションなので、toオプションでコン トローラだけ指定してac0onはワイルドカードに。  LiveStreamingコントローラ

Slide 13

Slide 13 text

Pumaを使う •  標準で利用されるWebrickはレスポンスが終わら ないといつまでもバッファリングしてしまうため。   •  (現状Pumaくらいしかアプリケーションサーバー に選択肢はないと思うが)開発用に使うと言うこ とでadd_development_dependencyしてる。  

Slide 14

Slide 14 text

すると、こうなる •  ヘッダが一度だけ表示されて、以後Hello  World のみが繰り返されている。

Slide 15

Slide 15 text

(寄り道)ちなみに普通のGETだと •  GETのレスポンスが来てconnec0onが切れる。

Slide 16

Slide 16 text

サーバーからブラウザへデータを送る   手法の歴史的経緯 •  はじめにpollingありき   – ブラウザが継続的にサーバーに通信を送って受 け取るデータが無いか確認   – 当然だがオーバーヘッドが大きい  

Slide 17

Slide 17 text

Long  Polling •  ブラウザがサーバーに問い合わせたときに サーバー側で更新が無ければレスポンスを 返す前に時間を空ける   •  通常のpollingよりは回数が減るが、ブラウザ 間の互換性や無通信時間が長いと判断され てproxyにconnec0onを切られたりした。  

Slide 18

Slide 18 text

HTML5の登場 •  Server  Sent  Events(SSE)とWebsockets   •  違いは何か?   – Websocketsはブラウザとサーバーの相互通信を サポート   – SSEはサーバーからクライアントへの一方通行   •  Websocketsはブラウザの互換性が影響する が、SSEはそこを気にせず使うことができる。   •  本章ではSSEを採用してLive  Streamingを実現。

Slide 19

Slide 19 text

Event  Stream  Format •  SSEが採用するメッセージ形式   •  各メッセージは2つの改行で区切られる。   •  ここではJSON形式を使っているが、他の形式 でもいい。  

Slide 20

Slide 20 text

(補足)  SSE深掘り •  厳密にはテキスト形式で  text/event-­‐stream  とし て送信される。   •  最後に\nが2つ並んでいれば良いので、1メッ セージの行数はいくつでもいい。   •  dataフィールドは必須。   •  他にも予約済みフィールド名がある模様。   –  Retry:  接続が切れた後に再接続するまでのタイムア ウト時間(ms)   –  Event:  イベント名。JSのaddEventListnerでここに指定 したイベント名をそのまま利用できる。   参照先 h\p://www.html5rocks.com/en/tutorials/eventsource/basics/

Slide 21

Slide 21 text

コントローラ実装に戻る •  SSEを利用してサーバーにCSS再読み込みイベン トを送るようにした。   •  発表者注:この時点ではまだ  sleep  1が残ってい る。サーバーでCSSの変更を検知すると同時に file  systemのpollingを避けるのは次のセクション。  

Slide 22

Slide 22 text

JSの実装

Slide 23

Slide 23 text

ヘルパーメソッドの定義 •  これを定義することで、、  

Slide 24

Slide 24 text

Layoutファイルでのasset呼び出し •  live_assetsメソッドでjsを読み込むことができる。  

Slide 25

Slide 25 text

アプリ側アクションの作成 •  さっきまではpluginのコントローラでsseアク ションを追加していたが、これはアプリ側。    

Slide 26

Slide 26 text

マルチスレッドの設定 •  だが、これだけでは期待した通りに動かない   •  allow_concurrency  =  trueにしないとRailsは単 一スレッドでしか動作しない   – よって、allow_concurrencyを有効にする。   •  発表者注:マルチスレッドの話は本章であま り深く触れられていない。  

Slide 27

Slide 27 text

これで動くようになった •  デモをご覧ください。

Slide 28

Slide 28 text

残る問題 •  sleepして変更を検知するのは効率が良くない   – ファイルが変更されたときだけクライアントに通知 を送りたい。   •  テストが書けてない。   •  そもそもallow_concurrencyって何なんです か?  

Slide 29

Slide 29 text

スレッドを利用したファイル変更検知 •  単純な方法:Filesystem  polling   – 一定時間毎にファイルが変更されていないかを 調べに行く   – 長期間変更されていないときにやるのは無意味   – 監視するスタイルシートの数が増えるほどCPU負 荷が上がる。

Slide 30

Slide 30 text

変更検知をOSに任せる •  ほとんどのOSはファイルの変更を通知してく れるAPIを持っている。   •  Listen  gemを使うことでこれらAPIを簡単に利 用可能。   – ただし、各環境毎に別のgemが必要(後述)   – 今回はassets/stylesheetsを監視したいので、この ディレクトリを渡せば済む。

Slide 31

Slide 31 text

スレッドを使って実装

Slide 32

Slide 32 text

ファイル変更検知のテスト •  Integra0on  testは書けないが、ファイル変更 検知については切り出せたのでテストが書け る。  

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

(補足)   各環境のfilesystem  no0fica0on •  On  windows     •  On  *BSD     From  h\ps://github.com/guard/listen

Slide 35

Slide 35 text

イベントが正しく送られるかのテスト •  今度は先にテストを書く。   •  さっきのテストと違ってEngineが読み込まれたと きにstart_listnerは動いているという前提。  

Slide 36

Slide 36 text

CSS変更検知のリスナー実装 •  Engineの中で定義することで、pluginが読み 込まれた直後にlistnerが走るようになった。

Slide 37

Slide 37 text

CSS変更通知の流れ

Slide 38

Slide 38 text

まだ余計な待ちがある •  subscriberにeventが入るまでwhileで待っている   –  CPUの無駄遣い   •  Ruby  の標準ライブラリにあるQueueを使って解 決する。

Slide 39

Slide 39 text

スレッドとQueue •  ThreadとQueueを組み合わ せることで簡単に解決でき る。   •  Queueにアイテムがない場 合、Threadはブロックされ る。   – 逆にいえば、Queueに何か が入ったときだけスレッドが 起きる。

Slide 40

Slide 40 text

Queueを使ってテストを変更

Slide 41

Slide 41 text

Subscriberの変更 •  Queueに対して subscribeするように 変更。   •  Eachメソッドでblock を受け付けるように している。

Slide 42

Slide 42 text

 (補足)subscribeの意味 •  本章ではLiveAssets#subscribeは単に subscribersとして引数のArrayを登録している だけ。   – Subscribeメソッドのレシーバーが何かに対して subscribeするわけではない。  

Slide 43

Slide 43 text

コントローラ部分を変更 •  Sleepが無くなった。

Slide 44

Slide 44 text

最後の「無駄」 •  ファイルが変更されなかった場合、通信が無 いままずっと接続は維持される。   – サーバー、ブラウザ、proxyのいずれかで connec0onが切られる可能性がある。   •  ファイル変更がなくても一定時間毎にブラウ ザに対してpingを送ることで問題を回避する。

Slide 45

Slide 45 text

Timerのテスト実装 •  Queueに定期的にpingコマンドをsubscriberに 通知するようにすればいい。

Slide 46

Slide 46 text

Timerの実装 •  Timerの場合、一定 時間毎に起きて subscriberに通知す るため、キューは使 わずsleepを使って 起きたときに通知 すればいい。

Slide 47

Slide 47 text

Engineへの実装 •  10秒ごとにevent:  pingを送るようにする。

Slide 48

Slide 48 text

Code-­‐Loading  Techiniques •  requireでファイルを読み込む場合、起動前に すべてを読み込むことになる。   •  Autoload  を使うことで最初に必要になったと きに自動的に読み込むようにできる。   •  AutoloadにはRubyのものとRailsのものがある。   – Rubyのautoloadはatomicではない。そのため、あ るスレッドでload中のときに他のスレッドがload対 象となるクラスを参照していると参照が失敗する。   – いくつかのRuby実装がこの問題を解決中。

Slide 49

Slide 49 text

Railsのautoload •  Thread-­‐safeではない。   – そのため、デフォルトではallow_concurrencyが falseになっている。   – 動作スレッドを1つに絞ることでautoloadの問題を 解決している。   •  では、allow_concurrencyをtrueにしたというだ けで問題は解決するのか?

Slide 50

Slide 50 text

Eager-­‐Load  Techiniques •  Produc0onではautloadは無効になり、起動時 にすべてを読み込む(eager-­‐loading)。   – しかし、appディレクトリのものしかeager-­‐loadingし ない。   •  本章ではRubyのautoloadを使ったので、自前 でeager-­‐loadingを実装しないといけない。

Slide 51

Slide 51 text

SSESubscriberのEager-­‐loading •  SSESubscriberが最初に読み込まれないと、 loadが未完了の段階で複数のスレッドが動い てしまう。

Slide 52

Slide 52 text

Eager-­‐loadingに   なっているかどうかの確認

Slide 53

Slide 53 text

Eager-­‐loadingの効果 •  Unicornでも有効   – Unicornはpreforkを使うので、先に全部読み込ん でおけばリクエスト毎にautoloadされるコストが削 減される。   – 発表者注:その分、メモリ使用量が増える可能性 はある。   •  この方法であればUnicornでもlive-­‐streamingできる。   •  ただし、streaming用にプロセスが1つ占有されてしまう。  

Slide 54

Slide 54 text

Threded  vs  Non-­‐threaded •  銀の弾丸はない。   –  先ほどのPumaとUnicornの比較の通り。    •  何が一番適しているかは複数のウェブサーバー でベンチマークを取ってみるしかない。   •  Jrubyなども検討要素。   –  ただし、Jrubyでは配列操作がthread-­‐safedではない ので対処が必要。 

Slide 55

Slide 55 text

ご清聴ありがとうございました