ぱろっと・すたじお

技術メモなどをまったりと / my site : http://parrot-studio.com/

性能と無関係にUnicornからPumaに移行した件

今回の結論を先に書けば・・・

CapistranoとPumaをあわせて使うとめっちゃはかどる」

・・・って話でございます

Unicornにこだわりがなければ、Pumaは便利だと思います、以上Σ(・ω・ノ)ノ

(私には必要だが一般的には無視していい)前置きというか経緯

そもそもCapistranoはお仕事で使ったことがありましたが、
チェンクロパーティーシミュレーター(以下、ccpts)ではまだ導入してませんでした

お仕事でごりごり使っていたのはRails4.2とかの頃で、
Passengerは重たいよねって認識*1から、Unicornを当たり前のように使っており、
ccptsでもNginx+Unicornを手動デプロイで運用してました

ccpts.parrot-studio.com

別に手動でも困ってなかったのですが、一つだけ問題がありまして

サーバダウンから再起動した際、サービスも一緒に起動してほしかったので、
init.dから(ccpts内の)unicornを起動する仕組みにしていたのですが、
実行ユーザーを考えてなかったので、いろいろなファイルがroot権限になってしまいましてΣ(゚Д゚)ガーン *2

それを直すのも面倒なのでそのまま運用していたのですが、違和感があったのも事実で、
そこを修正するついでにデプロイプロセスを改善したいってのもあり、
最初はDockerを検討したわけですよ、Dockerを

最近はいくらでもドキュメントがあるので、DB+Redis+Railsのコンテナを作成し、
サービスを起動するところまではできたわけです

・・・もちろん、ローカルで(´-ω-)

問題は「これをどうやってデプロイするか?」とか、
「修正からデプロイまでどうやって運用するか」なのですが・・・
ccptsのような「普通のサイト」だと、あまりに重すぎなんですよね

たぶん、「Dockerでデプロイする環境」は作れます
ただ、それが「メリットをデメリットが上回った状態」なのかというと、
激しく疑問だったわけです(´・ω・`) *3

とはいえ、「メリットをデメリットが上回った状態」を証明するには、
「もっと低コストなデプロイプロセスで回せる」ことを示す必要があり、
Capistranoならもっと低コストなはずだよね・・・?」という流れでのCapistranoでございます

CapistranoでPumaだと、Unironより何がいいのか?

前置きが長かったので、結論から先に...φ(・ω・`)

  • unicornと違い、Rails5.xでは標準でpumaがついてくる(追加でgemを管理しなくていい)
  • puma.rbをcapistrano3-pumaが自動生成してくれるので、pumaのconfigを書かなくていい


Rails5.0からWebrickを捨ててPumaに移行したのは、
ActionCableを使うのに、スレッドベースのAPサーバが必要だったからですが、
そもそもUnicornとPumaは昔からライバル関係にはあったわけです

とはいえ、Unicornのノウハウが成熟しているに比べ、
Rails5.0RC当時のPumaはまだ詰まるポイントが多く、
ccptsのRails5移行の時にも結構苦労した記憶があります(´-ω-)

それでもやはり、「Rails標準」の勢いは重要で、
久々にCapistranoの記事を探したら、みんなPumaとの連動になっていて、
私も試してみるか・・・と、軽い気持ちだったのですが、これがめっちゃ便利なのです

そもそも、Unicornはmasterプロセスとworkerプロセスが協調して動き、
workerを増減させたり、メモリを使いすぎたら再起動させたり・・・というのが、
無停止で行えるってのがポイントでした*4

とはいえ、そのworkerがDBへのコネクションを持ったままだと、
workerが作り直されるたびにリソースを食い尽くしてしまうので、
おまじないのようにこんなconfigを書いていたはずです

https://github.com/tablexi/capistrano3-unicorn/blob/master/examples/unicorn.rb

毎回そんなのが必要ならば、いっそう自動生成すればええやん・・・というのが、
capistrano3-pumaのえらい点でございます(`・ω・´)

github.com

例えば、deploy.rbにこんな感じで書くだけで、shared/puma.rbを自動生成してくれます
(sharedに作ってくれるあたり、実に気が利いてます)

# puma
set :puma_threads,    [4, 16]
set :puma_workers,    0
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/puma.sock"
set :puma_state,      "#{shared_path}/tmp/pids/puma.state"
set :puma_pid,        "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{release_path}/log/puma.access.log"
set :puma_error_log,  "#{release_path}/log/puma.error.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true # DBコネクション周りのおまじないがこの1行でOK

怖くて試していないのですが、どうもNginx等のconfigも生成できるようで、
ssl用の鍵指定までできるあたり、かなり本格的に使えるように見えます

もちろん、pumaの再起動も「cap [stage] puma:restart」とかするだけですし、
非常に簡単ですねヽ(`・ω・´)ノ

ccptsの場合、元々Unicornのソケットにリクエストを流していたので、
それをPumaに変えるだけ*5で、移行があっさり完了してしまいました


そんなわけで、あくまで運用上の観点から、UnicornをPumaに置き換えたわけですが、
Rails5系を使っていて、まだUnicornで動かしているって方は、
一度Pumaを評価してみてはいかがでしょうか(´・ω・)っ

*1: WebサーバとAPサーバを個別にスケールできない・・・とか、Webサーバで503を返して、APサーバ側を更新したり・・・といった運用を考えると、Passengerはめんどいのです(´-ω-)

*2: その時に書いた記事 https://parrot.hatenadiary.jp/entry/2014/10/29/123509

*3: デプロイのたびにコンテナ構築に10分とかかかり、そこから数百MBのコンテナを転送し、それを本番でpullしてきて・・・という流れを、修正のたびに回すのは(たとえ自動化しても)辛すぎます 「超軽量なコンテナを組み合わせたシステム(それこそ、AmazonのLambdaのような)」なら使いやすいのですが、Railsのような、それ自体が巨大ライブラリの塊で、モノリシックなシステムの場合、あまり向かないのではと(´-ω-) 極端な話、「サーバごとimmutableにしてデプロイ時に差しかえる」という(古典的だけど確実な)手もあるわけですし・・・

*4: Apache等もgraceful restartできますが、まあ置いておいて・・・Σ(・ω・ノ)ノ

*5: 一応、WebのrootをCapistranoを想定したpublicに変えましたが、Pumaに移行したから発生した修正ではないです

続きを読む

Chromeに煽られたので、あまり使ってないサイトもhttps対応する

一年半ぶりの更新になります...φ(・ω・`)

その間にも「チェンクロパーティーシミュレーター」(以下、ccpts)に
大量の技術的な更新を入れていたのですが、
ついつい仕事を優先してBlogを書いてませんでした

ccpts.parrot-studio.com

github.com

  • ccptsの主な更新内容

その中のトピックスの一つが「(ccptsの)https対応」で、
昨年の冬くらいに対応し、暗号化方式でなんやかんや悩んだ記憶があるのですが、
とりあえず置いておいて・・・Σ(・ω・ノ)ノ

昨日、Chromeがアップデートされ、「httpは安全ではない」と表示されるようになりました

forest.watch.impress.co.jp

実質まともに動かしているのはccptsくらいなのですが、
念のためポータル的なサイトの方も対応しておくことに...φ(・ω・`) *1

https://parrot-studio.com/

とはいえ、現在は「Let's Encrypt」という素敵な仕組みが存在し、
やろうと思えば数分でhttps対応が終わってしまうご時世です

letsencrypt.org

letsencrypt.jp

ccptsをhttps対応した頃は、まだツール(certbot)の精度がいまいちで、
(特にnginxだと)多少苦労があったり、自動更新がうまくいかなかったりしましたが、
クライアントが「certbot-auto」に移行してからは、さらに簡単になりましたヽ(`・ω・´)ノ

基本的にはここに書いてある通りで、nginxでもapacheでも余裕です

Let's Encrypt の使い方 - Let's Encrypt 総合ポータル

  1. certbot-autoをwgetしてchmod +x
  2. certbot-autoを初回起動して必要なライブラリを取得
  3. certbot-autoをドメイン名つき起動し、証明書発行してもらう
  4. (途中でメールアドレスを求められるので入力)
  5. ローカルに生成された証明書をconfに書く
  6. portをあける
  7. nginx or apacheを再起動

これだけです(`・ω・´) b

一度証明書を取得してしまえば、環境情報がサーバに保存されるため、
cronにセットしてしまえば、再取得も簡単にできます

$ sudo crontab -l

# 週に一回 "certbot-auto renew" を実行する(念のため、実行ログも取る)
2 5 * * 0 /path/to/certbot-auto renew >>/hoge/piyo/certbot.log 2>>/hoge/piyo/certbot_err.log

証明書の期限は90日で、1ヶ月くらい前から更新が可能になりますが、
更新が不要な場合はメッセージだけ出して無視してくれます(`・ω・´)

最悪、cronでの更新がうまくいかなくても、証明書作成時に登録したメールアドレスに、
「もうすぐ期限が切れます」というメールが届くので、手動で更新すれば大丈夫です
(以前はそのような運用をしてました)

あとは・・・問題なければhttpからhttpsにリダイレクトしてあげるといいのですが、
ccptsではやっているものの、ポータルの方はほっといてます

server {
    listen 80;
    listen [::]:80;
    server_name ccpts.parrot-studio.com;
    return 301 https://$host$request_uri; # redirect to https
}

server {
  listen 443;
  listen [::]:443;
  server_name ccpts.parrot-studio.com;

  # 以下略

}

慣れれば本当に数分のレベルですので、業務でいきなり導入する前に、
ぜひ自分のサイトでお試しを(´・ω・)っ


<2018/8/28追記

cronを使ってnginxの証明書を更新しようとした場合、
nginxのpathを見失って更新できないことがあります

その場合、crontabにPATHを明示する必要があります

$ sudo crontab -l

# PATHを明示
PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin # その他必要なPATH

# 以下同じ

cronあるあるですが、忘れやすいのでご注意を(´-ω-)

*1: はてなBlogも現在はhttpsに対応しています・・・が、mixed contentへの対応がやや面倒な感じです(´-ω-)

Rails+Reactアプリをbrowserifyからwebpack基盤に移行した件

ちょうど一年ほど前、「チェンクロ パーティーシミュレーター」(以下ccpts)を、
jQueryを使った制御から、React.js + Bacon.jsで大幅に書き換えました...φ(・ω・`)

parrot.hatenadiary.jp

ccpts.parrot-studio.com

github.com

そもそも、ccpts自体、
「モバイルで動くように*1、できるだけ今風の技術でクライアント側を構築する」
という目的を持って設計してました

昨年の時点ではReactが十分にメインストリームに乗ったと判断して、
基本的なアーキテクチャをReact+browserifyに載せ替えたわけですが、
「Reactで動くようにする」ことが目的だったので、細かいところは置いておいたわけです

それを今回、フレームワークを見直してきれいにした(している)というお話です(´・ω・)っ

解決したい問題とwebpack

昨年末、チェンクロで第3部が実装され、
システムが大幅にアップデートされました

こうなると、ccptsのシステムも3部対応で大幅に書き換えがいるわけですが、
以前構築してからここまで運用してみて、
最大の問題は「デプロイに時間がかかりすぎる」ということです

サーバサイドのデータを書き換える分には問題ないのですが、
クライアント側のjsをいじるとデプロイ時にbuildが必要になり、
私が使っているVPSだと10分以上かかってしまいますΣ(゚Д゚)ガーン

3部対応するにあたり、クライアント側を頻繁にいじる必要があるので、
さすがにこれは許容できません

そもそも、これだけコストがかかると、
「クライアントをいじらずにどうにかできないか?」と考えるようになってしまい、
本来的な設計から遠ざかってしまいます(´-ω-)

そこで、2016年末の時点での「今風のやり方」を探すことにしましたのですが、
その時点で主流になっていた(ように見えた)のがwebpackです

そもそも、Railsは「Asset Pipeline」がよくできすぎています
これを自前で実装するとコストがかかるので、
何とかして今風のコードと組み合わせて乗っけたい・・・というのがポイントになります

調べた結果、ざっくりとこんな感じのやり方が主流になっているように見えました...φ(・ω・`)

  1. クライアント側のコードを閉じた形で独立して構築する(Railsの管理外)
  2. webpackで一つのjsにまとめて、Railsのassets管理下に置く(これはpureなjs)
  3. あとはRailsのprecompile等に素直に任せる

当初、これを自前で書こうと思ったのですが、
もっと楽ができないのか・・・と思ったら、
「React on Rails」という良いものがあったわけです

github.com

1年前の記事でも最後に取り上げていましたが、
この時点では何が主流になるのかさっぱりわからなかったので、
そこまでは踏み込みませんでした(´-ω-)

このやり方の最大の決定打は、Rails5.1におけるDHHの方針です

weblog.rubyonrails.org

github.com

Rails用に仕組みを構築しちゃうあたりがさすがですが、
このwebpackerにreactのオプションも入っているくらいで、
もう迷う要素はなくなりました( ゚д゚)o彡゚

まだRails5.1はリリースまで時間がかかりますが、リリースされたとしても、
React on Railsにはreactのサーバサイドレンダリングという仕組みがありますので、
最終的にはRails5.1の上でReact on Railsという形になると思います

とはいえ、サーバサイドレンダリングをいきなり使うと、
React on Railsに依存しすぎてしまうので、まずは使わずに、
「今風の開発フローを構築するための基盤」として利用しております

実際の段取り

CoffeeScriptの排除とnpmからyarnへの移行

「できるだけクライアントのコードを client 以下に移す」というのが方針になりますが、
es6でクラス定数が使えない等々で、CoffeeScriptで書いたmodelが残っている箇所がありました

これらはAsset Pipelineとbrowserifyで動作していたわけですが、
webpackで一つにまとめるためには、全てes6で書き換える必要があります(´-ω-)

そこで、es6で書くことを優先して、クラス定数の方をあきらめました

(例としてmodelを一つ)
https://github.com/parrot-studio/cc-pt-viewer/blob/69c8e88185536e9c9c8af67c2b5e5caa53069f66/client/app/bundles/ccpts/model/Favorites.js

今回は目的が違うので、この手の妥協は仕方ありません(´・ω・`)

ついでに、npmによるパッケージ管理をやめて、yarnに切り替えました

yarnpkg.com

Railsでbundlerを使っているわけで、この仕組みは全く違和感がありません

import/exportを正しく使う

以前は各modelやcomponentを呼び出す前に、
ブラウザのグローバルにBaconやReactのような各ライブラリを突っ込んでおき、
暗黙的に参照するというやり方でとりあえずやってました

これはCoffeeScriptとes6を共存させるのに必要だったのですが、
CoffeeScriptを排除したので、もう不要です
全部import/exportで書き換えました...φ(・ω・`)

問題は、どうしてもimport漏れが出てくることです

当初、動かしてみてはエラーを見て追加・・・みたいな、
超絶に効率の悪いことをしていたのですが、
ESLintで全部解決しました(`・ω・´)

eslint.org

ルールは自分の直感に反しないレベルでこんな感じに(´・ω・)っ

cc-pt-viewer/.eslintrc at master · parrot-studio/cc-pt-viewer · GitHub

これでエラーが出ないようにガンガン修正していきました
rucobopと同じく、自動で修正する機能もあるので、とても楽です

jQueryの取り扱い

これは1年前と何も変わってませんが、Reactを使うからといって、
jQueryを "使うべきではない" という意見には賛同できません( ゚Д゚)y─~~

たしかに、jQueryで "DOMの操作をすべきではない" ですが、
jQuery界隈には大量のライブラリやノウハウの蓄積があり、
それを全てReactの文脈に落とし込めるとは思えないわけです*2

実際の現場で、jQueryなら数分でできたことが、
Reactだと3日かかります・・・といって、
企画側に納得してもらえませんよね(´・ω・`)

ギリギリまでjQueryを使わないようにしつつ、
どうしてもjQueryが優位な箇所だけ使っていく・・・
それは1年前の時点と変わっていません

しかし、以前はライブラリを全部グローバルに定義していたので問題ありませんでしたが、
全部閉じた環境に持っていこうとすると、jQueryの取り扱いがとたんに難しくなります(lll゚Д゚)

ここに関しては情報も少なく、相当試行錯誤したのですが、
結果的に「古いライブラリは古いやり方、新しいライブラリは新しいやり方」と、
完全に切り分けることにしました

具体的にはこんな感じです...φ(・ω・`)

https://github.com/parrot-studio/cc-pt-viewer/blob/0f289cc18827a5135388c77199fd7477fb174d0f/app/assets/javascripts/application.js

どうしてもjQueryの文脈が必要なものだけassets以下で管理して、
Reactを含む他の一切をwebpackで一つにまとめたわけです

React側からは「$」がグローバルアクセスできるので、
あとはReactのrefで生DOMにアクセスしてjQueryのアニメーションを適用するだけです
1年前の記事参照)

グローバル変数があるとESLintが警告を出すのですが、
.eslintrcで「$というグローバル変数だけ許可して」と定義しているので問題ありません
むしろこれにより、「$というグローバル変数が存在する」ということが明確になります(`・ω・´)

置き換えた結果と今後

方針を決めたのが11月で、12月に入ってからは実装されたキャラデータの暫定登録を進めて、
そこから基板入れ替え作業を進めましたが、12月末にはざっくり作業が完了しました

それ以降の開発速度は目に見えて速くなりまして、
やはり心理的な障壁がぐっと下がったのはでかいです(`・ω・´) b *3

コストが下がったことで、棚上げしていた問題、
例えば、Becon.jsのStreamをもっと整理したいとか、
そもそもUIを大きくいじりたいとか、そういうところにも手を出せるようになりました

でも、一番改善したいのはReact on Railsの機能である、
「Reactのサーバサイドレンダリング」なのですが、
そのあたりは今後作業予定なので、できあがったらまた書きます...φ(・ω・`)

*1: 基準としては「iPad miniで見た時に快適であること」です いつも使っているので(`・ω・´)

*2: もちろん、長期的には対応していくはずですが、現時点ではReactを「普通の人」がいじるにはコストが高すぎ、対応ライブラリの整備がすぐに進むとは思えません(´-ω-)

*3: svnからgitに移ることで、branchを切るコストが飛躍的に下がり、現在のような開発フローが構築された・・・というのと同じですね

Elixirについて調べたついでに、BrainF**kインタプリタを書いてみた

最近、某D社の事例を含めて、Elixirの話題をよく見かけるようになりまして

http://elixir-lang.org/

もちろん、話題になっているからには、
何らかの「時代の要請」があるはずでして、そのあたりを調べてみたわけです

なぜ、Elixirが出てきたのか?

まず読んでみた本はこちらです(´・ω・)っ

プログラミングElixir

プログラミングElixir

この本は言語の使い方について書いた本ではなく、
「設計思想」について書かれた本でして、
そのこと自体がElixirの出自を象徴しております

歴史の流れを復習してみると、かつてWebはPerlJavaがメインで、
普通のWebであればだいたいPerlCGIで書かれていたわけです

でも、ある程度大きな機能になるとPerlではきつくなってきて、
「もっとちゃんとした言語」が求められ、
RailsをきっかけにしてRubyがブレイクしました

Rubyのポイントは、まつもとさん自身が書いているように・・・

・・・多くの言語が「機能」からの要請で生まれてきたのに対し、
Rubyは「コードをこう書きたい」とか「こう書けると気持ちいい」といった、
「設計思想」を優先して生まれてきた、という経緯があります

その分、(特に1.9より前においては)実行速度に難があったのですが、
システムの大規模化・複雑化や、CPU速度の飛躍的向上により、
メリットがデメリットを大幅に上回るようになったわけです(`・ω・´)

一方で、時代はPCからモバイルへ変わっていき、
「大きなリクエストを素早く」ではなく、
「小さくて大量のリクエストを素早く」という要求に変わっていきました

古いアーキテクチャではこの要請に応えられない*1ため、
Nginxが登場したり、Apacheアーキテクチャが変わり、
さらなる高速化のためWebSoketやそれに対応するNode.jsが出てきたりしたわけです

そして、CPUのアーキテクチャも変化しました
単純な速度ではもはや向上が見込めないため、
マルチコアにするのが当たり前になってきています

その結果、「小さくて軽い処理(をするAPIサーバ)」が求められるようになり、
Rubyではこれらの状況に対応できない・・・ということはないのですが、
Railsもスリム化できるようになり、Node.jsと組み合わせたりするようになりました

(勘違いしないでもらいたいのは、Rubyがダメということではありません
 Rubyの強みは「時代の要請についていくセンス」と、
 「変化を許容し、時に古いものを切り捨てる強さ」にあるので)

一方で、「小さく軽い処理のを組み合わせて一つの大きな処理を構成する」方式は、
まさに関数型言語が得意とする領域です

しかし、関数型言語は難しすぎます
古くはLispHaskellがありますが、これを「普通の人」が扱うのはとても無理*2です(´-ω-)

その領域をScalaが埋めていくのかな・・・と思っていたのですが、
(少なくとも私は)某D社以外に大きな事例も聞かず、
「Play Framework」のWebにおける大きな採用事例も(少なくとも私は)知りません

そもそも、当の某D社がScalaを切った・・・とまではいかずとも、
Scalaで書き直しました!」と大々的にアピールしていたシステムを、
すでにElixirで書き直しているあたりで、だいだい状況は推測できます(´・ω・`)

他の関数型言語だと「F#」あたりなのですが・・・
あれは本を読んでみたものの、難易度的にはScalaと大差はなく、
(少なくとも現状では)Windowsっぽいイメージが強すぎてきついです

難易度を無視すれば、「小さなロジックを大量のプロセスで処理する」*3という、
理想的な設計で事例もある「Erlang」も使えるはずなのですが、
パラダイムのジャンプだけでなく、文法的な難しさがつきまといます

こんな状況の中、「関数型言語をもっとなじみがある文法で」、
より端的にいえば「Rubyが書きやすいんだから、Rubyみたいに書けて、
Erlangのように動く言語
があればいいんじゃない?」と考えた人がいたわけです

それがまさに「Elixir」でして、冒頭でも書いたように、
Elixir最大のポイントは「こう書けるとうれしい」という、
「設計思想」を優先して生まれた言語である、ということなのです(`・ω・´) b

ElixirでBrainF**kインタプリタを書いてみる

先ほどの本を読んだ感じ、設計思想にはものすごく共感できたので、
とりあえず私にとって「関数型言語におけるFizzBuzz」である、
「BrainF**k」の実行系を書いてみました...φ(・ω・`)

以前もScalaで書いたことがありますし・・・

parrot.hatenadiary.jp

・・・それより前にはRubyで「実行系を作成する実行系」を書いたりもΣ(・ω・ノ)ノ

で、今回Elixirで実際に書いてみたのがこちらです(´・ω・)っ

gist.github.com

大きなポイントは二点ありまして、まず一つ目は
「データの処理の流れを記述する」ということを強く意識させる文法です

  def execute(code) do # codeを実行するとは・・・
    code        # 受け取ったcodeを・・・
      |> parse  # 解釈し・・・
      |> eval   # 実行することである
  end

  defp parse(str) do # 解釈するとは・・・
    str  # 受け取った文字列を・・・
      |> String.replace("\n", "")  # 改行を取っ払って・・・
      |> String.codepoints  # 一文字ずつに分離し・・・
      |> List.foldl([], fn c, li -> li ++ [@cmap[c]] end)
        # コマンドのストリームに置き換えることである
  end

この書き方はとてもいいですね(`・ω・´) b

「|>」は前の関数の結果を次の関数の第一引数として渡す、
というだけの構文でしかないのですが、
これだけで「何かしたいのか?」がとても明確になります

それこそが「関数型言語風に宣言的なコードを書く」ことの、
最大の利点であり、それ自体は他の言語でも享受できるのですが、
文法として明確に組み込まれているのがポイントです

もう一点が、「関数の引数自体がパターンマッチになっている」ことです

  defp step(:pinc, coms, jump_map, ind, cind, pind, buf, result) do
    step(coms[cind+1], coms, jump_map, ind+1, cind+1, pind+1, buf, result)
  end

  defp step(:pdec, coms, jump_map, ind, cind, pind, buf, result) do
    step(coms[cind+1], coms, jump_map, ind+1, cind+1, pind-1, buf, result)
  end

# 略

  defp step(_, _coms, _jump_map, _ind, _cind, _pind, _buf, result), do: result

Javaでいうオーバーロードに近いのですが、
ScalaRubyでいうところのcaseやswitchのような、
パターンマッチングを、関数の定義自体でやっています

先ほど挙げた、以前書いたScalaコードを見てもらえばわかるのですが、
stepに相当するところの中でパターンマッチングしていて、
また自身を再帰的に呼び出しています

そのため、stepの処理自体が肥大化しているし、
新しいコマンドを追加する場合、case文のマッチングを増やさないといけません(´-ω-)

その点、Elixirのコードは一つ一つの関数が「定義」として小さくまとまっており、
「何をしたいのか?」が明確になって、見通しが良くなっています

「これだけのこと」ではあるのですが、
それでも「こう書けるととても楽」というのを優先したのがElixirでして、
まさにそれはRubyと同様の設計思想なわけです(`・ω・´)

Elixirは「使える」のか?

こんな感じで、私の好みにあうElixirですが、
今の私の仕事である「アーキテクト」の立場からすると、
少なくとも今は「ない」かな・・・とΣ(゚Д゚)ガーン

弊社CTOとも話をして、「この言語はいいね(`・ω・´)」という話にはなっていますが、
あくまでお仕事として考えると、あまりにもコストが高いかなと思います

「表面的な文法」はRubyに似ているため、
例えばRubyの初期のように「PerlのようにRubyを書く」のと同様、
RubyのようにElixirを書く」のは可能ですし、私もそのように導入します

しかし、Elixirの利点を最大限の享受するには、
「関数型的な設計思想」が必須になります

ElixirがRubyに強く影響を受けているのと同様に、
ElixirのWebフレームワークである「Phoenix Framework」は、
とてもRailsの影響が強いです

www.phoenixframework.org

「ある程度わかっている人ならば」Railsの仕組みをこれに移すことも可能かもしれません

しかし、RubyRailsも最大のポイントは、先ほど書いたように、
「時代の要請についていくセンス」と
「変化を許容し、時に古いものを切り捨てる強さ」です

RubyはどんどんVMが高速化されていますし、
文法的にも「型」を導入しようとしています
(普通の言語なら危険信号なのですが、まつもとさんならまず大丈夫でしょう)

Railsもモジュール化・スリム化(API専用モードなど)が進み、
並行処理を意識したライブラリを組み込んだりもしています

github.com

なにより、個人的な感覚では、「ElixirでRDBにアクセスすること」自体が、
せっかくのメリットを台無しにしていると思いますΣ(・ω・ノ)ノ

Node.jsの仕事をしたときに強く感じたのは、
とにかく「全てを非同期に書かなければならない」ということであり、
「同期的な処理をどう切り離すか?」を常に考えないといけません

こういったことを意識しなければならないのは、
少なくとも「普通のWebシステム」においてはオーバースペックですし、
例えばRubyでもそういった設計思想のシステムは十分組めます*4

あとは人的コストの問題です
Node.jsを書いたことがある人はそこそこ探せますし、
Rubyを書ける人なら(質を問わなければ)十分見つかるでしょう
(なぜ、FBような大規模システムで、PHPが使われているのかを考えてみてください)

今後、Elixirが何らかの形でメインストリームに乗った場合*5は別として、
現時点では「あえてRubyを切り捨てる」メリットがあまりに薄いのかな・・・と思います

メインのWebサーバをRuby+Railsで組み、
リアルタイム性を求められる領域をNode.jsで構築する、というのが、
今の時点では「普通にいけそうなWebシステム(=長く運用できるシステム)」だと思います

逆に「普通じゃないWebシステム」、
例えば「大量の動画をバックエンドでリアルタイムにエンコードし続けるシステム」ならば、
かなり有効に作用すると思います

・・・が、そういうシステムを必要とする会社は限られてますし、
そういう会社はシステムにかけられるコストが桁違いなので、
やはり「普通の会社」では難しいですよね・・・(´・ω・`)

*1: いわゆるC10K問題

*2: ここでいう「無理」とは、「扱える人がいない」のではなく、「扱える人を雇うコストが、一般のWeb企業において現実的ではない」くらいの意味で

*3: このプロセスをクラウド上で起動するのが「サーバレスアーキテクチャ」とか話題のあれですね

*4: というか、以前仕事で組みました http://parrot.hatenadiary.jp/entry/2013/09/03/112657

*5: 個人的にはそうあってくれると楽しいのですが・・・(´-ω-)

続きを読む

RailsをRedisで「効率よく」高速化してみる(+おまけ)

仕事でコードを書く時間が減ると、別なところでコードを書きたくなるもので、
久々にチェンクロパーティーシミュレーター(以下ccpts)の
システム部分をいじっていました...φ(・ω・`)

ccpts.parrot-studio.com

以前react化したり、Rails5に置き換えたりしたわけですが・・・

parrot.hatenadiary.jp

parrot.hatenadiary.jp

・・・どうしても「遅い」ってのが問題でして(´-ω-)

react化=表示自体を全てJavaScriptで制御となると、
JSを解釈するまでメイン部分が描画されないわけで、
どうしても体感で遅くなってしまいます

「部分的react化」により、見かけ上の描画を早くすることは可能ですが、
私のスキルが追いついていないので、それはいったん置いておいて・・・Σ(・ω・ノ)ノ

私にできるのは「APIの速度改善」かな・・・ということで、
APIの速度を高速化してみたわけです

Redisのキャッシュを「効率よく」使う

APIが遅い原因はわりとシンプルで、たいていの場合は「(同期的な)I/O」です
ある程度並列処理が許される計算処理に対し、
同一のリソースに触るI/Oは排他制御等の「待ち」が発生します

I/Oといっても「ファイル」とか「ネットワーク」とかいろいろありますが、
「ネットワークでアクセスするファイルI/O」が遅いのは間違いなく、
端的にいえば「DBはどうしても遅い」ということに(´-ω-)

以前だと、DBのデータをメモリに全部乗せるとかいろいろありましたが、
KVS(現在はほぼRedis)の登場により
「高速で非同期アクセスが可能なネットワークストレージ」が登場しました

DBに対して処理時間のオーダーが文字通り「桁違い」なので、
これをキャッシュ的に生かせば、DBへのアクセスが減り、速くなる・・・はずです

だからといって、何でもかんでもRedisに突っ込めばいいわけではありません
Redisもネットワークストレージなわけで、
自分のメモリ上で処理が完結するならそっちの方が速いわけです

postd.cc

この記事は非常にすばらしく、参考になります(`・ω・´) b

そもそも、「Key-Value Store」という名の通り、
「Keyに紐付いたデータ」を取り出すのは非常に高速ですが、
「Keyのリスト」とか取り出そうとした時点で破綻しますΣ(゚Д゚)ガーン

実際、私も以前MongoDBで作ったシステムをMariaDBで作り直しています

parrot.hatenadiary.jp

用途が増え、串刺しな検索する機能が増えた結果、
RDBのような構造化された構造の方が圧倒的に速くなったわけです

つまり、当たり前のことですが・・・

  • 参照が圧倒的に多い
  • 結果取得に計算時間がかかる
  • 頻繁に内容が更新されない
    • ライフサイクルを明確に定義できる
  • Key-Valueという単純な構造に落とし込める
    • 「Keyのリスト」にアクセスする必要がない
  • 非同期アクセスしても問題ない

・・・(Redisに限らず)こういう条件を満たせなければ、
キャッシュを生かせないか、無駄に複雑化してバグの温床になるだけです(´・ω・`)

ccptsでRedisキャッシュを使う

これをふまえてccptsのAPIを高速化していくわけですが、
ccptsのAPIは概ね「検索に対してアルカナデータのリストを返す」という構造をしています

以前はクエリに対してDBを検索し、アルカナデータを構築して返していたのですが、
一つのアルカナに関連するテーブルがたくさんあり、
効率的に取得してもどうしても遅くなるわけです(´-ω-)

しかし、「アルカナのデータ」は「データの更新」をしない限り一意で、
だからこそブラウザ側でもキャッシュさせたりしているので、
「アルカナのID - アルカナデータ」という構造をキャッシュしようと

つまり、「DBで検索してDBからデータを取得」から、
「DBでIDだけ取り出して、IDに対応するアルカナデータをRedisから取得」に変えるわけです

ここで重要なのは、検索自体はDBにやらせることです
「構造化されたデータの検索」は圧倒的にDBが高速ですからね・・・

他にも、「検索画面用に返しているマスターデータ」とか、
初期表示に使う「最近追加されたアルカナリスト」とか、
決め打ちで返せるものもキャッシュしてしまいます

問題は「キャッシュの生存期間」とか「更新」についてですが、
今回の場合「データの取り込み時」にキャッシュも更新してあげれば解決です

古いキャッシュにアクセスされる可能性も、
「Keyにデータバージョンを含む」ことで回避できます(`・ω・´)

以上を実装して、先日リリースしたのがこちらです

redisのキャッシュで高速化 · parrot-studio/cc-pt-viewer@42947de · GitHub


<Redisキャッシュ対応前>

f:id:parrot_studio:20161031094518p:plain

<Redisキャッシュ対応後>

f:id:parrot_studio:20161031094603p:plain

<Redisキャッシュ対応後(ブラウザで時間測定)>

f:id:parrot_studio:20161031094611p:plain

文字通り、桁が違いましたΣ(゚Д゚;≡;゚д゚)


まあ、元々は仕事でもりもり設計していた部分ではあるのですが、
仕事のコードは持ち出せなくても、ノウハウを自分のアプリに反映しておけば、
別なところに使い回しできますからね・・・

ちなみに、お仕事の場合はメモリキャッシュ(Railsのmemstore)も使ってまして、
扱うデータに応じてメモリかRedisか選んで格納するようにしています*1

あと、キャッシュはあくまでキャッシュであって、
「消えてもDBから"必ず"復元できる」というのも徹底しています
Redisを信用してないわけではないですが、データの安全性はRDBが圧倒的ですしね

assetsの取り扱いを変える

これで体感でもそこそこ速くなったので、
Google先生のパフォーマンスチェックサイトで計測してみたのですが・・・

testmysite.thinkwithgoogle.com

・・・モバイル/PCでそれぞれ40点とさんざんでしたΣ(゚Д゚)ガーン

アドバイスに「データを圧縮して返せ( ゚Д゚)y─~~」と書かれてまして、
そういえばRailsのprecompileに圧縮版があったな・・・と思い出しました

アセットパイプライン | Rails ガイド

今まで非圧縮版にアクセスさせていたので、
ガイドの通りnginxを設定し、圧縮版にアクセスさせたところ・・・

・・・ここまで変わりました(; д ) ゚ ゚

ここからモバイルアクセスの点を上げるには、
レンダリングをブロックしているJS/CSSを改善しないといけないらしいです

Remove Render-Blocking JavaScript  |  PageSpeed Insights  |  Google Developers

とはいえ、完全reactの状態では難しく、
ファーストビューだけでもサーバサイドでレンダリングする必要があるのですが、
その問題自体は認識していて、今後の課題ですかね・・・

developers.cyberagent.co.jp

こういうことが「さらっと」(=私の手の届く範囲で)できるといいのですが(´-ω-)

まとめ

  • Redisキャッシュを使えばいいというわけではなく、使い方をちゃんと考える
  • Railsのガイドはちゃんと読む(´・ω・`)
  • 今後の課題:完全reactから一部をサーバサイドに差し戻す

*1: 昔はmemcachedが定番でしたが、Redisでもたいして速度が変わらず、永続化も可能なので、永続化が必要ならRedis、そうでなければRailsのメモリって感じで、memcachedは使ってません(´-ω-)