Chromeに煽られたので、あまり使ってないサイトもhttps対応する
一年半ぶりの更新になります...φ(・ω・`)
その間にも「チェンクロパーティーシミュレーター」(以下、ccpts)に
大量の技術的な更新を入れていたのですが、
ついつい仕事を優先してBlogを書いてませんでした
- ccptsの主な更新内容
その中のトピックスの一つが「(ccptsの)https対応」で、
昨年の冬くらいに対応し、暗号化方式でなんやかんや悩んだ記憶があるのですが、
とりあえず置いておいて・・・Σ(・ω・ノ)ノ
昨日、Chromeがアップデートされ、「httpは安全ではない」と表示されるようになりました
実質まともに動かしているのはccptsくらいなのですが、
念のためポータル的なサイトの方も対応しておくことに...φ(・ω・`) *1
とはいえ、現在は「Let's Encrypt」という素敵な仕組みが存在し、
やろうと思えば数分でhttps対応が終わってしまうご時世です
ccptsをhttps対応した頃は、まだツール(certbot)の精度がいまいちで、
(特にnginxだと)多少苦労があったり、自動更新がうまくいかなかったりしましたが、
クライアントが「certbot-auto」に移行してからは、さらに簡単になりましたヽ(`・ω・´)ノ
基本的にはここに書いてある通りで、nginxでもapacheでも余裕です
Let's Encrypt の使い方 - Let's Encrypt 総合ポータル
- certbot-autoをwgetしてchmod +x
- certbot-autoを初回起動して必要なライブラリを取得
- certbot-autoをドメイン名つき起動し、証明書発行してもらう
- (途中でメールアドレスを求められるので入力)
- ローカルに生成された証明書をconfに書く
- portをあける
- 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あるあるですが、忘れやすいのでご注意を(´-ω-)
Rails+Reactアプリをbrowserifyからwebpack基盤に移行した件
ちょうど一年ほど前、「チェンクロ パーティーシミュレーター」(以下ccpts)を、
jQueryを使った制御から、React.js + Bacon.jsで大幅に書き換えました...φ(・ω・`)
そもそも、ccpts自体、
「モバイルで動くように*1、できるだけ今風の技術でクライアント側を構築する」
という目的を持って設計してました
昨年の時点ではReactが十分にメインストリームに乗ったと判断して、
基本的なアーキテクチャをReact+browserifyに載せ替えたわけですが、
「Reactで動くようにする」ことが目的だったので、細かいところは置いておいたわけです
それを今回、フレームワークを見直してきれいにした(している)というお話です(´・ω・)っ
解決したい問題とwebpack
昨年末、チェンクロで第3部が実装され、
システムが大幅にアップデートされました
こうなると、ccptsのシステムも3部対応で大幅に書き換えがいるわけですが、
以前構築してからここまで運用してみて、
最大の問題は「デプロイに時間がかかりすぎる」ということです
サーバサイドのデータを書き換える分には問題ないのですが、
クライアント側のjsをいじるとデプロイ時にbuildが必要になり、
私が使っているVPSだと10分以上かかってしまいますΣ(゚Д゚)ガーン
3部対応するにあたり、クライアント側を頻繁にいじる必要があるので、
さすがにこれは許容できません
そもそも、これだけコストがかかると、
「クライアントをいじらずにどうにかできないか?」と考えるようになってしまい、
本来的な設計から遠ざかってしまいます(´-ω-)
そこで、2016年末の時点での「今風のやり方」を探すことにしましたのですが、
その時点で主流になっていた(ように見えた)のがwebpackです
そもそも、Railsは「Asset Pipeline」がよくできすぎています
これを自前で実装するとコストがかかるので、
何とかして今風のコードと組み合わせて乗っけたい・・・というのがポイントになります
調べた結果、ざっくりとこんな感じのやり方が主流になっているように見えました...φ(・ω・`)
- クライアント側のコードを閉じた形で独立して構築する(Railsの管理外)
- webpackで一つのjsにまとめて、Railsのassets管理下に置く(これはpureなjs)
- あとはRailsのprecompile等に素直に任せる
当初、これを自前で書こうと思ったのですが、
もっと楽ができないのか・・・と思ったら、
「React on Rails」という良いものがあったわけです
1年前の記事でも最後に取り上げていましたが、
この時点では何が主流になるのかさっぱりわからなかったので、
そこまでは踏み込みませんでした(´-ω-)
このやり方の最大の決定打は、Rails5.1におけるDHHの方針です
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に切り替えました
Railsでbundlerを使っているわけで、この仕組みは全く違和感がありません
import/exportを正しく使う
以前は各modelやcomponentを呼び出す前に、
ブラウザのグローバルにBaconやReactのような各ライブラリを突っ込んでおき、
暗黙的に参照するというやり方でとりあえずやってました
これはCoffeeScriptとes6を共存させるのに必要だったのですが、
CoffeeScriptを排除したので、もう不要です
全部import/exportで書き換えました...φ(・ω・`)
問題は、どうしてもimport漏れが出てくることです
当初、動かしてみてはエラーを見て追加・・・みたいな、
超絶に効率の悪いことをしていたのですが、
ESLintで全部解決しました(`・ω・´)
ルールは自分の直感に反しないレベルでこんな感じに(´・ω・)っ
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゚Д゚)
ここに関しては情報も少なく、相当試行錯誤したのですが、
結果的に「古いライブラリは古いやり方、新しいライブラリは新しいやり方」と、
完全に切り分けることにしました
具体的にはこんな感じです...φ(・ω・`)
どうしても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のサーバサイドレンダリング」なのですが、
そのあたりは今後作業予定なので、できあがったらまた書きます...φ(・ω・`)
Elixirについて調べたついでに、BrainF**kインタプリタを書いてみた
最近、某D社の事例を含めて、Elixirの話題をよく見かけるようになりまして
もちろん、話題になっているからには、
何らかの「時代の要請」があるはずでして、そのあたりを調べてみたわけです
なぜ、Elixirが出てきたのか?
まず読んでみた本はこちらです(´・ω・)っ
- 作者: Dave Thomas,笹田耕一,鳥井雪
- 出版社/メーカー: オーム社
- 発売日: 2016/08/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
この本は言語の使い方について書いた本ではなく、
「設計思想」について書かれた本でして、
そのこと自体がElixirの出自を象徴しております
歴史の流れを復習してみると、かつてWebはPerlやJavaがメインで、
普通のWebであればだいたいPerlのCGIで書かれていたわけです
でも、ある程度大きな機能になるとPerlではきつくなってきて、
「もっとちゃんとした言語」が求められ、
RailsをきっかけにしてRubyがブレイクしました
Rubyのポイントは、まつもとさん自身が書いているように・・・
- 作者: まつもとゆきひろ
- 出版社/メーカー: 日経BP社
- 発売日: 2016/12/22
- メディア: 単行本
- この商品を含むブログ (2件) を見る
・・・多くの言語が「機能」からの要請で生まれてきたのに対し、
Rubyは「コードをこう書きたい」とか「こう書けると気持ちいい」といった、
「設計思想」を優先して生まれてきた、という経緯があります
その分、(特に1.9より前においては)実行速度に難があったのですが、
システムの大規模化・複雑化や、CPU速度の飛躍的向上により、
メリットがデメリットを大幅に上回るようになったわけです(`・ω・´)
一方で、時代はPCからモバイルへ変わっていき、
「大きなリクエストを素早く」ではなく、
「小さくて大量のリクエストを素早く」という要求に変わっていきました
古いアーキテクチャではこの要請に応えられない*1ため、
Nginxが登場したり、Apacheのアーキテクチャが変わり、
さらなる高速化のためWebSoketやそれに対応するNode.jsが出てきたりしたわけです
そして、CPUのアーキテクチャも変化しました
単純な速度ではもはや向上が見込めないため、
マルチコアにするのが当たり前になってきています
その結果、「小さくて軽い処理(をするAPIサーバ)」が求められるようになり、
Rubyではこれらの状況に対応できない・・・ということはないのですが、
Railsもスリム化できるようになり、Node.jsと組み合わせたりするようになりました
(勘違いしないでもらいたいのは、Rubyがダメということではありません
Rubyの強みは「時代の要請についていくセンス」と、
「変化を許容し、時に古いものを切り捨てる強さ」にあるので)
一方で、「小さく軽い処理のを組み合わせて一つの大きな処理を構成する」方式は、
まさに関数型言語が得意とする領域です
しかし、関数型言語は難しすぎます
古くはLisp・Haskellがありますが、これを「普通の人」が扱うのはとても無理*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で書いたことがありますし・・・
・・・それより前にはRubyで「実行系を作成する実行系」を書いたりもΣ(・ω・ノ)ノ
- ネタプログラミング言語クリエイターYouma (Gunma.web #8) - ぱろっと・すたじお
- ネタプログラミング言語クリエイター Youma
- BF風言語解析器 Windstorm
で、今回Elixirで実際に書いてみたのがこちらです(´・ω・)っ
大きなポイントは二点ありまして、まず一つ目は
「データの処理の流れを記述する」ということを強く意識させる文法です
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でいうオーバーロードに近いのですが、
ScalaやRubyでいうところの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の影響が強いです
「ある程度わかっている人ならば」Railsの仕組みをこれに移すことも可能かもしれません
しかし、RubyもRailsも最大のポイントは、先ほど書いたように、
「時代の要請についていくセンス」と
「変化を許容し、時に古いものを切り捨てる強さ」です
RubyはどんどんVMが高速化されていますし、
文法的にも「型」を導入しようとしています
(普通の言語なら危険信号なのですが、まつもとさんならまず大丈夫でしょう)
Railsもモジュール化・スリム化(API専用モードなど)が進み、
並行処理を意識したライブラリを組み込んだりもしています
なにより、個人的な感覚では、「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)の
システム部分をいじっていました...φ(・ω・`)
以前react化したり、Rails5に置き換えたりしたわけですが・・・
・・・どうしても「遅い」ってのが問題でして(´-ω-)
react化=表示自体を全てJavaScriptで制御となると、
JSを解釈するまでメイン部分が描画されないわけで、
どうしても体感で遅くなってしまいます
「部分的react化」により、見かけ上の描画を早くすることは可能ですが、
私のスキルが追いついていないので、それはいったん置いておいて・・・Σ(・ω・ノ)ノ
私にできるのは「APIの速度改善」かな・・・ということで、
APIの速度を高速化してみたわけです
Redisのキャッシュを「効率よく」使う
APIが遅い原因はわりとシンプルで、たいていの場合は「(同期的な)I/O」です
ある程度並列処理が許される計算処理に対し、
同一のリソースに触るI/Oは排他制御等の「待ち」が発生します
I/Oといっても「ファイル」とか「ネットワーク」とかいろいろありますが、
「ネットワークでアクセスするファイルI/O」が遅いのは間違いなく、
端的にいえば「DBはどうしても遅い」ということに(´-ω-)
以前だと、DBのデータをメモリに全部乗せるとかいろいろありましたが、
KVS(現在はほぼRedis)の登場により
「高速で非同期アクセスが可能なネットワークストレージ」が登場しました
DBに対して処理時間のオーダーが文字通り「桁違い」なので、
これをキャッシュ的に生かせば、DBへのアクセスが減り、速くなる・・・はずです
だからといって、何でもかんでもRedisに突っ込めばいいわけではありません
Redisもネットワークストレージなわけで、
自分のメモリ上で処理が完結するならそっちの方が速いわけです
この記事は非常にすばらしく、参考になります(`・ω・´) b
そもそも、「Key-Value Store」という名の通り、
「Keyに紐付いたデータ」を取り出すのは非常に高速ですが、
「Keyのリスト」とか取り出そうとした時点で破綻しますΣ(゚Д゚)ガーン
実際、私も以前MongoDBで作ったシステムをMariaDBで作り直しています
用途が増え、串刺しな検索する機能が増えた結果、
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キャッシュ対応前>
<Redisキャッシュ対応後>
<Redisキャッシュ対応後(ブラウザで時間測定)>
文字通り、桁が違いましたΣ(゚Д゚;≡;゚д゚)
まあ、元々は仕事でもりもり設計していた部分ではあるのですが、
仕事のコードは持ち出せなくても、ノウハウを自分のアプリに反映しておけば、
別なところに使い回しできますからね・・・
ちなみに、お仕事の場合はメモリキャッシュ(Railsのmemstore)も使ってまして、
扱うデータに応じてメモリかRedisか選んで格納するようにしています*1
あと、キャッシュはあくまでキャッシュであって、
「消えてもDBから"必ず"復元できる」というのも徹底しています
Redisを信用してないわけではないですが、データの安全性はRDBが圧倒的ですしね
assetsの取り扱いを変える
これで体感でもそこそこ速くなったので、
Google先生のパフォーマンスチェックサイトで計測してみたのですが・・・
testmysite.thinkwithgoogle.com
・・・モバイル/PCでそれぞれ40点とさんざんでしたΣ(゚Д゚)ガーン
アドバイスに「データを圧縮して返せ( ゚Д゚)y─~~」と書かれてまして、
そういえばRailsのprecompileに圧縮版があったな・・・と思い出しました
今まで非圧縮版にアクセスさせていたので、
ガイドの通りnginxを設定し、圧縮版にアクセスさせたところ・・・
今までassetsを圧縮版で返してなくて、Google先生のツールで「遅い! 40点!!(ノ゚Д゚)ノ彡┻━┻ 」と怒られていたので、nginxの設定を変えたらこれであるΣ(゚Д゚)ガーン pic.twitter.com/dJ0iBltQVs
— ぱろっと (@parrot_studio) 2016年10月29日
・・・ここまで変わりました(; д ) ゚ ゚
ここからモバイルアクセスの点を上げるには、
レンダリングをブロックしているJS/CSSを改善しないといけないらしいです
Remove Render-Blocking JavaScript | PageSpeed Insights | Google Developers
とはいえ、完全reactの状態では難しく、
ファーストビューだけでもサーバサイドでレンダリングする必要があるのですが、
その問題自体は認識していて、今後の課題ですかね・・・
こういうことが「さらっと」(=私の手の届く範囲で)できるといいのですが(´-ω-)
まとめ
- Redisキャッシュを使えばいいというわけではなく、使い方をちゃんと考える
- Railsのガイドはちゃんと読む(´・ω・`)
- 今後の課題:完全reactから一部をサーバサイドに差し戻す
アンドロイドはアイドルの夢を見るか(恋するハッカソン〜君色に染まるアイドル〜を解いた件)
ということで、8回目のPOHなのですが・・・
・・・前回あたりから「ゲーム」としてPRしていたり、
今回から会員登録しないとダメだったりと、
そろそろいいかな・・・とも思いまして(´-ω-)
しかも、先月は死ぬほど忙しくて、
普段飲まないエナジードリンクを飲みながら仕事していたレベルで、
こういう遊びをやっている余裕もなくてですね・・・
でも、仕事もやっと一段落して、そもそもそのエナジードリンクは、
以前のPOHで当たったやつだったりしたので、
会員登録くらいはまあいいかな・・・とかΣ(・ω・ノ)ノ
ただ、外部サイト連携で登録しようとしたら全然うまくいかなくて、
結局ID/PASSで登録するはめになったはちょっと・・・
何がおかしかったのは不明です
そんなこんなで先日挑戦しまして、
数時間で(現時点である問題を)全てクリアしました∠( ゚д゚)/
まだロックされているドレスは、
「お仕事」(イベントシーン)をあと20回くらい見ないと解放されないやつで、
面倒なので放置しています( ゚Д゚)y─~~
今回もほとんどは楽勝だったのですが、
水着問題はパフォーマンス的にやや難易度が高め(雑に書くと何秒もかかる)で、
浴衣問題と制服問題がBOSS・・・という感じでしょうか
(それでも初期の頃の問題に比べれば瞬殺のレベルですが)
一応、全部のコードがこちらに(´・ω・)っ
https://gist.github.com/parrot-studio/d657f61ce60969685995b6e7f22bf119
ということで、今回は浴衣問題と制服問題の話だけ書いていきます
浴衣問題
冷蔵庫の電気料金に関する問題です
設定温度より高いと2円/h、設定温度で維持できるなら1円/h
1日に何度か開け閉めすると、そのタイミングで温度が上昇
1日で電気料金はいくら・・・という問題です
この、「状態が変化する」系の問題は、
クロージャーの出番でございます(`・ω・´)
# クロージャー生成 count = 0 counter = lambda do count += 1 end # クロージャーを呼び出す counter.call puts count # => 1 counter.call puts count # => 2 3.times { counter.call } puts count # => 5
クロージャー(今回の場合はRubyのProcオブジェクト*1)は、
作られた時点での外部環境(countという変数)を参照しているので、
こういうことができるわけですね
今回の問題はこれを使えば簡単で、
クロージャーの中に問題の仕様をそのまま織り込んでしまい、
1時間ごとにcallするだけで終わりです
与えられているのは開け閉めする時刻なので、
その時刻まで状態を進めて、開け閉めした時点で温度を加算して、
また次まで進めて・・・というわけです
これを真面目に解こうとすると結構面倒だと思いますが、
仕様をそのまま書けば解けてしまうというのが面白いですね(`・ω・´) b
制服問題
ざっくり言うと、「52人で大富豪をやって、全員の順位を決めろ」という問題です
先ほどの浴衣問題が「業務系にありがちな問題」だとすれば、
こちらは「ゲーム系にありがちな問題」という感じでしょうか
まず面倒なのが、「カードの強弱」です
大富豪は3が一番弱く、10->J->Q->K->A->2という感じで強さが決まっており、
これを毎回比較するのはコストが高いです(´-ω-)
なので、データを読み込む時点で、3を0、4を1・・・2を12というように、
全部強さの順に数値化してしまい、単純な大小比較に持ち込みました
でもこれはたいした話ではなく、最大の問題は「場が流れる条件判定」です
問題通りだと、ある人がカードを出したあと、その人の次まで誰も出せなければ、
次の人が自分の手札を出せる・・・となっていますが、
そのまんま実装したら明らかに計算が無駄です(´・ω・`)
要は、「その人が出したカードが現時点で最強なら場を流す」ということなのですが、
そうなると「その人の出したカードが最強かどうかをどう判定するか?」という問題が
最初のうちは「2」が最強なので、2を出したら場が流れます
しかし、4枚2を出されたら、次の最強はAですし、
刻々と最強のカードは変わっていくわけです
これをどうしたものかな・・・と思ったのですが、
わりとシンプルに「使ったカードを排除していく」という手段をとりました
(コード内のuse!メソッド)
あとは「場が流れた」ことを-1で表現して、次の人が確実に出せるように単純化したとか、
ランキングを配列で表現したとか、いつものレベルの最適化でして、
52枚と枚数が決まっているため、計算量もほぼ一定で、パフォーマンス的にも難しくありません
まとめ
ということで、現時点ではこの2問がやや難しいといった感じで、
他は効率よく書けるかはともかくとして、「書けない」ということはないはずです
見た感じあと2問、まだ解放されていない問題があるようなので、
これがさっきの2問より難しいようなら、またこの記事に追記します...φ(・ω・`)