ハートレイルズの技術スタッフによるブログです。

ハートレイルズの技術スタッフによるブログです。
ウェブサービス、スマートフォンアプリの制作に関連する技術的な情報を発信していきます。

2014年12月12日金曜日

砲撃に耐える Web サービス

この記事は、そろそろ一般向けにサービスリリースしてみようかなと考えているエンジニア向けに書きました。
説明に Rails を用いていますが、考え方自体は Web アプリ一般に応用可能です。

結論 「cache publicに設計しよう」

出来るだけ多くのページを cache public にしましょう。
砲撃の来るページは cache public に出来るはずです。

この説明だけで意味の分かった方には以下の記事は読む価値はありません。時間を有意義につかってください。ごきげんよう。

砲撃に耐えよう

サービスリリースして、バグもなく順調に事が運び、運が良ければバズったり、どこぞの大手メディアに取り上げられる日が来るでしょう。
その直後、あなたの Web サービスには大量のアクセスが殺到します。
これが俗にいう「砲撃」です。サーバが砲撃に耐えられなければ、やってきたユーザはつまらないエラーページを見て去っていきます。
次の幸運の日まで、彼らが戻ってくることはありません。

砲撃に耐えるために、サーバを増強したりDBを分散したりいろんな方法論がすでに紹介されていますが、それより先にやるべきことはキャッシュの最適化です。
ここで、キャッシュについて整理しておきましょう。

キャッシュにもいろいろありますが、サーバサイドキャッシュとクライアントサイドキャッシュの2つに大きく分けることが出来ます。
(この名称は私が説明のために勝手に命名したので、もっと正しい名称をご存じの方は教えて下さい。)

サーバサイドキャッシュ

DBのクエリ結果や、 render した html をサーバのメモリ上に保存して再利用する技術です。
redis や memcached が使われます。
Rails の機能としては、 Russian Doll Caching というのが有名です。
ググればいくらでも記事が出てくるので詳細は割愛。

クライアントサイドキャッシュ

html や画像をブラウザが URL 単位で保存し、同じ URL へのアクセスが起きた時に通信を省略する機能です。
html ヘッダにいくつかの情報をのせておくことで実現します。
Rails の機能としては、 expires_in, refresh_when, stale? があります。
また、 sprockets が提供する assets は、デフォルトでクライアントサイドキャッシュがばっちり効くように設定されています。

ブラウザとアプリケーションサーバの間にキャッシュサーバが置かれている場合もあります。

このような場合、 html ヘッダが適切に設定されているとキャッシュサーバがアクセスを捌いてくれるので アプリケーションサーバまで砲撃が届かなくなります
キャッシュサーバはアプリケーションサーバと比べると仕組みが単純なので、一台でもかなりのアクセスに対応可能な上、増設も簡単です。
キャッシュサーバを提供してくれるクラウドサービスはいろいろあります。自前で建てる場合は varnish などがあります。
大きな会社だと社内から社外へのキャッシュサーバを用意していたり、プロバイダが帯域を節約するために用意している場合もあります。

Rails では、先に上げた public 以下や assets を除き、デフォルトではこれらのキャッシュサーバをあまり有効に使ってくれません。
今日の本題は、キャッシュサーバを最大限活用しましょう、というお話です。

キャッシュパブリック cache public とは?

(この名称も私の勝手な命名です。)
「動的なページ」とか「静的なページ」という言い方はよく聞きますね。
html を書き出すために db にアクセスが必要なページが「動的」、不要なページが「静的」です。
Rails では、public/ 以下のファイルおよび assets は「静的」です。
静的なページは全て cache public です。

が、動的なページも cache public に出来る場合があります。
cache public に出来るページとは「ある瞬間に100人からアクセスが有った時、100人全員に同じページを返却できる」ようなページのことです。

例えば twitter のタイムラインは cache public ではありません。
ログインしているユーザそれぞれ、見えるものがばらばらだからです。

では、特定のツイートのページ (例) はどうでしょうか。

リツイート数やお気に入り数は刻一刻変動するのでこれは動的なページですが、ある瞬間に100人からアクセスが有った時、100人全員が同じリツイート数やお気に入り数を見るはずです。

なので、このページは「ほぼ」cache public です。
「ほぼ」と書いたのは、ユーザがログイン済みの場合には一部にユーザ情報が出てるからです。


この、ごく僅かなユーザ別情報の部分を JavaScript で書き出すようにし、かつ、ユーザ名等の個別情報を ajax で別の URL から取るようにすることで、このページを完全な cache public にすることが出来ます。

砲撃の来るページは cache public に出来ます。

マイページのような、ログインユーザ毎に違う内容が表示されるページのURLは紹介する意味が無いので砲撃の対象になりえません。
(拡散するとすればスクリーンショットの形になるでしょう。)

それ以外の、ブログの記事ページや商品の紹介ページが、砲撃のターゲットです。これらのページから、ログインユーザの個別情報を切り分けてやることで、 cache public なページにすることが出来ます。
そうすることで、砲撃の殆どはキャッシュサーバが打ち返し、アプリケーションサーバが応答すべきはログインユーザの個別情報を含む小さな json だけになります。
これだけでもかなり砲撃に強くなりますが、万が一アプリケーションサーバが砲撃に耐え切れずエラーになっても、コンテンツの主要な部分はユーザに届くのです。
砲撃でアクセスしてくるユーザの殆どはログインアカウントなんて持ってませんし。

cache public な設計の実際

routes.rb を書いてURL設計をする際に、どのURLが砲撃の対象になりうるかを吟味しましょう。
controller 別に切り分けられれば実装がしやすくなります。
path で切り分けられるとさらに理想的です。

cache public にするつもりのページでは session と flash を使わないように実装します。
ApplicationController に current_user を定義するのをやめると良いでしょう。

cache public なページが current_user にアクセスしてユーザ固有情報を含む html を返却すると、発見困難かつ悲惨な事故に繋がります。事故を未然に防ぐため、cache public にするページでは current_user にそもそもアクセス出来ないようにすることを強くお薦めします。

cache public にするつもりの controller には、以下の宣言を追加します。
  before_action do
    expires_in(1.second, public: true)
  end
これで、この controller の全てのレスポンスには Cache-Control: public という html ヘッダが付与され、あとはキャッシュサーバがおおむねうまいことやってくれます。
「え、1秒でいいの?」殆どの場合はOKのはずです。1秒の寿命を指定すれば、秒間何千アクセスの砲撃が来ようとも、アプリケーションサーバには「1秒に1アクセス」しか届かなくなるわけですから。
ここで欲張って寿命を1分とか1時間にしても、dbを書き換えた時の応答が悪くなるだけです。

勘違いしがちなのですが、current_user を使っても
  before_action do
    expires_in(1.second, public: true) unless current_user
  end
として、未ログイン時だけ cache public にすればいいのでは、というのはうまくいきません。
こうすると、ログイン中のユーザのページが別のユーザに配信されてしまうことは防げますが、ログイン中のユーザに未ログインのページが配信されることになってしまいます。
cache public にするかどうかは必ずURL毎に決定する必要があります。

flash が使えないことにも注意します。ログアウト時に
redirect_to root_path, notice: "ログアウトしました"
とかやりがちですが、トップページが cache public なら、誰かのトップページだけに「ログアウトしました」を表示することは出来ません。
このようなメッセージも、 javascript で描画するようにするのが良いでしょう。
flash は cache public なページヘのアクセスでも容赦なく消されるのであてになりません。
(session[:messages] ||= []) << 'ログアウトしました'
のようにして、 session に積んでおきましょう。

Rails のよくあるパターンでは、ユーザのログインを sessions#new と #create に、ログアウトを #destory に置くことが多いでしょうから、 sessions#show でユーザ情報の json を取れるようにすると良いでしょう。
# routes.rb
resource :session, only: [:new, :create, :show, :destroy]
単数形の resource なので、 :id 無しの GET /session が #show に対応します。
ログインユーザのユーザ名、アイコンのURL等を json で返却し、Javascript で描画します。
session[:messages] は返却と同時にクリアしておくようにすると、 flash と同じような使い勝手になります。

SessionsController の他、マイページ等、 cache private なページだけが current_user にアクセスできるようにしましょう。

varnish と cookie

メジャなキャッシュサーバとして varnish がありますが、 varnish のデフォルト動作として、「リクエストに cookie がついてきた時はキャッシュを返さない」というルールがあります。
このデフォルト動作を活かしたまま、期待通りキャッシュを効かせるためには、以下のように path を設定します。
# config/initializers/session_store.rb
Myapp::Application.config.session_store :cookie_store, key: '_myapp_session', path: '/users'
そしてもちろん、ログイン・ログアウト他、ユーザ別のレスポンスをする全てのページを /users および /users/ 以下に配置する必要があります。
URL をこのように綺麗に切り分けられない場合は、 cache public なページヘのリクエストで予め cookie を削除するように varnish に設定が必要となります。
詳細は https://www.varnish-cache.org/trac/wiki/VCLExampleCacheCookies を参照してください。

まとめ

  • 砲撃を受けうるページは cache public にしよう
  • その為に、ログイン情報や flash message は分離し、 javascript で描画しよう
  • 事故を未然に防ぐため、 cache public にしたアクションからは current_user 等へアクセスできないようにしよう

2014年7月14日月曜日

prax で簡易サーバー

http://pow.cx/ は大変便利なのですが、 Mac 専用です。
その linux 版が prax https://github.com/ysbaddaden/prax です。

何が便利なのか

  • rack アプリケーションを任意のディレクトリに clone したら、~/.prax 以下にシンボリックリンクを張るだけでサーバーの設定がほぼ全部完了 (ゼロコンフィギュレーション!!)
  • rack アプリ毎の rack サーバーの起動終了が全自動
  • touch tmp/restart.txt でアプリを再起動
というような pow の利点を全てそのまま linux 上で享受できます。

prax 本体のインストール方法

README にインストール方法が書いてありますが、2014 年 7 月時点では以下のように unstable を使用するのがお勧めです。(そもそも本番で使うモノではないので躊躇せず行きましょう。)
私は個人的に rvm を使っていますが、アンチの方は適宜読み替えてください。
sudo git clone git://github.com/ysbaddaden/prax.git /opt/prax
cd /opt/prax/
sudo git checkout unstable
rvm use 2.1.1@prax --create --ruby-version
sudo ./bin/prax install
gem install rack
gem install mime-types
prax start -f
最後の -f は foreground です。慣れないうちはこれでログを目視しながら別のターミナルから作業するといいでしょう。だいたい使い慣れてきたら ^C で止めて、prax start と普通に実行すれば ok です。(rack と mime-types にアクセスできる環境下で実行するよう気を付けてください。mime-types が無くても実行可能ですが、無いと css や js をレスポンスする際に適切な Content-Type がつきません。CSSが適用されない現象が起きたらここを疑ってください。)

アプリの設定

cd
git clone git://example.com/myapp
cd myapp
rvm use 2.1.1@myapp --create --ruby-version
echo RACK_ENV=staging > .env
vi .praxrc
source "/home/kuboon/.rvm/scripts/rvm"
rvm use `cat .ruby-version`@`cat .ruby-gemset`
unstable の機能なのでまだドキュメントされていませんが、.env があれば load_env され、.praxrc があれば source されるので、上のような感じで設定します。(ちなみに master ブランチには .ruby-version を読み込む機能がビルトインされていましたが、rvm アンチな誰かが消したようです。その代わりに .praxrc 対応が入ったのでまあいいじゃないの。)

注意点としては、prax は最小限の環境変数で rack を立ち上げようとするので、source "$rvm_path/scripts/rvm" などと華麗にキメると $rvm_path が空で失敗します。~/.praxconfig に設定するとできるらしいので、興味ある方はソースを追ってください。ほぼ pure ruby です。

次に bundle したり rake db:setup したりして、rails s で立ち上がるところまで持ってきたら、
prax link
とすると ~/.prax/myapp というシンボリックリンクが作成され、http://myapp.devhttp://myapp.192.168.0.10.xip.io でアクセスできるようになります。

もしサーバーを example.com で公開していて、http://myapp.example.com でアクセスしたい場合には
cd ~/.prax
mv myapp myapp.example
という感じでファイル名を変更するとヒットします。

また、~/.prax/default を作成しておくと該当するアプリケーションがない場合に採用されます。

port forwarding

これも pow の機能ですが、 unstable 版には実装されているようです。~/.prax にシンボリックリンクを作成する代わりに、数値の入ったテキストファイルを作成します。
cd ~/.prax
echo 3000 > myapp2.example
こうしておくと、ポート 3000 で立ち上がっているサービスに http://myapp2.example.com でアクセスできるようになります。

2014年5月13日火曜日

ハートレイルズ流、リモートワークのススメ

ハートレイルズは 2006 年の創業以来、徹底してリモートワークに拘っています。

ハートレイルズはパートナーを含めてまだ 15 名程度の小さな組織ですが、この規模でも原則全員が異なる場所で働いている組織は、日本ではかなり珍しいのではないでしょうか。

ハートレイルズには海外を転々としながら働いている人や関東近郊以外の地方から働いている人、マジシャンを副業にしながらエンジニアとして働いている人など、様々な場所から、様々な関わり方で働いている人がいます。性格も体育会系な人や草食系な人、リア充な人から非コミュな人、オタクな人まで様々です。ハートレイルズはエンジニアやデザイナーを主体としている企業ですから、最低限 「プログラミング/デザインがきちんとできること」 は求められますが、それ以外はなるべくスタッフの多様性を維持しつつ、リモートワークを維持しつつ、組織を拡大していくというチャレンジをしています。

最近はおかげさまで全国各地のエンジニア/デザイナーの方々からスタッフ募集への応募をいただいている状況ですが、多くの方はそのモチベーションの一つとしてリモートワークを挙げていますので、今日はそのリモートワークについて、簡単に紹介したいと思います。

ハートレイルズってそもそもどんな会社?

ハートレイルズはウェブサービス、スマートフォンアプリの開発会社です。自社サービスの開発も行いつつ、受託開発も (モチベーティブな案件は) 積極的に行うようにしています。開発の規模は小規模なもの (1、2 ヵ月) から大規模なもの (半年以上) まで様々ですが、自社サービスの開発ではもちろん、受託開発でも要件定義から入り、運用までの全てのフェーズを担う (もしくはお客様と共同で担う) 体制で進めています。また、自社サービスの開発と受託開発でエンジニア/デザイナーの所属を分けておらず、時々の状況に応じてプロジェクトを編成しているのも特徴です。

なぜリモートワークに拘っているのか?

ハートレイルズがリモートワークに拘っているのは、それが働き方の最適解 (の一つ) だと信じているからです。リモートワークの明確な長所としては、通勤時間がなく、働く場所や働き方に裁量を持てる、ということが挙げられます。また、コミュニケーションのベースが非同期となることから自然と無駄な会議が減り、必要な会議 (もちろんリモートです) に必要な人だけが参加するというスタイルとなります。リモートワークは全体的に従来の働き方から無駄を削ぎ落とした働き方になるため、そこで生まれた時間をさらに仕事に当てたり、自己研鑽、あるいは家族のために当てることができるようになります。

逆に短所としては、人によりけりですが、運動不足になりがち、オンとオフの区別が曖昧になりがち、といった生活習慣や自己管理の問題や、雑談の場や飲み会が少なく、仕事以外でのコミュニケーションや人間関係に物足りなさを感じがちになる、といったことが挙げられます。

また、稀にリモートワークだと仕事をサボれるのではないかと低レベルな勘違いをしている方も見受けられますが、ハートレイルズがリモートワークに拘っているのは、個々の働き方を最適化することで、結果としてステークホルダーに最大の価値を届けるためです。仕事の結果がオフィスワークよりも劣るようなら、組織として生き残ることは難しいだろうという覚悟と誇りの元、日々働いています。

どうやってリモートワークを維持しているのか?

ハートレイルズはウェブサービスやスマートフォンアプリの開発会社ですから、リモートワークを維持する仕組みは、そのまま開発を円滑に進める仕組みとなります。この点、ハートレイルズはリモートワークならではの特別な仕組みは特に用意しておらず、むしろ開発に際しては当然のことを空気のように徹底してやることを重視しています。

参考までに、具体的なポイントを下記に列挙してみました。

「作業の見える化」

ハートレイルズでは原則全員が異なる場所で働いているため、全ての情報/状況は文章化され共有され、あらゆる作業に必ず担当者がアサインされ、あらゆる進捗が随時マネージメントされます。曖昧な状態というのはリモートワークでは厳禁で、見える化の徹底により全員が迷わず作業に没頭できる状態を維持することが、ハートレイルズの高速/高品質な開発の原動力です。

同時に、情報/状況の共有の停滞は組織の壊死と捉え、新鮮な情報/状況が共有され続けるようにマネージャーが常に配慮し、エンジニア/デザイナーが小まめなアウトプット (WIP PR ワークフローを採用) で応えることが、リモートワークを維持する一つのポイントとなっています。

※ 逆に言うと、ハートレイルズでは出社という概念が希薄なことから、ただ単に出社して椅子に座っているだけでは作業をしたとは見なされません。作業の質や量は全てオンライン上のアウトプットにより判断されます。

「レビューと QA」

見える化された作業は、すべからく第三者にレビューされ、必要に応じて専任のテストエンジニアにより QA されます。例外なく第三者のレビューを経ることで、ハートレイルズの会社としてのアウトプットの質を担保しています。なお、「作業の見える化」 の効能によりレビューは作業の早期から随時行われ、要件や設計上の問題点を早期に解消することに一役買っています。

ハートレイルズは受託開発において常駐というスタイルを好んでいませんが (ほぼ全てお断りしています)、その最たる理由は常駐するエンジニア/デザイナーがハートレイルズのマネージメントの外に出て行ってしまうため、アウトプットの質を担保するためのレビューを経ることが難しくなってしまうからです。属人的な作業の質や量の偏りをなるべく排除するため、受託開発においては人を貸すという感覚ではなく、あくまで会社として最大の価値を届けることを重視しています。

「マネージメント」

よく聞く話として、優れたエンジニアが自主性を持って働けばマネージメントは必要ない、という話がありますが、ハートレイルズはそうした前提に立ってマネージメントレスな組織を作ろうとは考えておらず、むしろマネージメントを重視しています。

ここで言うマネージメントとは一般的な進捗管理やリスク管理を含みますが、その他、コミュニケーションの円滑化、意思決定の迅速化も含めて、エンジニア/デザイナーがブレーキを踏まずに作業に没頭できる状態を常に維持するための、あらゆる業務や仕組み作りを含んでいます。

ハートレイルズはこの点に力を入れているため、プロジェクトが迷走することがなく、エンジニア/デザイナーのスキルによってソースコードの品質に多少の差が出ることはあっても (社内で基準とする品質は 「レビューと QA」 で担保されます)、適切なタイミングで適切なサポートを行うため、最終的にプロジェクトが失敗するということがまずありません。

なお、ここで勘違いしてほしくないのは、マネージメントが重要、イコール、マネージャーの頭数が必要、ではないことです。マネージメントをするのは何もマネージャーだけではありません。エンジニア/デザイナーが高いレベルでセルフマネージメントをすることで、マネージャーはよりマクロな視点での業務や仕組み作りに取り組むことができ、結果として会社全体としての生産性を高めていくことができます。

「コミュニケーション」

リモートワークにおいて、コミュニケーションが滞りなく取れることは生命線です。

ハートレイルズでは作業時間中は原則全員がオンラインでいることが求められますが、いくつかのツールを組み合わせることで、いつも側にいるような空気感でコミュニケーションを取っています。

  • 日常のテキストベースの会話 => HipChat
  • 日常の音声ベースの会話 => Google+ Hangout
    最近は Sqwiggle も試用しています
  • 隔週の社内定例会と社内勉強会 (交互) => Google+ Hangout
  • 随時の有志ベースのリモート飲み会 => Google+ Hangout
  • 随時の会社とスタッフの 1 on 1 => Google+ Hangout
    (もしくは HipChat の Video Call)

また、リモートワークにおけるコミュニケーションで重要なもう一つのことは、あまりリモートに固執し過ぎないことです。ハートレイルズでは 「たまにはみんなで会うか」 というノリでオフィスに集まったり (オフィスはあります)、「たまにはみんなで合宿でもするか」 というノリで泊りがけで合宿したりすることがあります。リモートでもどかしく感じることがあったら、素直にオフラインで会えば良い、そういう柔軟さがポイントです。

まとめ

ハートレイルズ流のリモートワークのスタンスについて、少しは掴めましたでしょうか?

こうしてまとめてみると、列挙したポイントは 「作業の見える化」、「レビューと QA」、「マネージメント」、「コミュニケーション」 と、リモートワークか否かによらず重要なことばかりです。逆に言うと、オフィスワークで "なあなあ" で作業を進めてプロジェクトの進行がグダグダになったり、議事録がオンラインに残らない会議で暗黙知が会社を支配していったり、といったよくあるパターンがリモートワークでは著しく生産性を下げるため、上記した通り、健全な開発会社としてやって当然のことを空気のように徹底してやる、ということが何よりも重要で、それが結果的に個々の働き方の最適解 (の一つ) に繋がると考えています。

以上、この記事を読んで、ハートレイルズと一緒に仕事をしてみたいな、もっと話を聞いてみたいな、と感じてくださった方はお気軽にご相談ください!よろしくお願いします!