RubyKaigi2010 に行ってきた(2, 3日目)

RubyKaigi2010 に行ってきた(1日目) - Slow Danceに続き、今年のRubyKaigi に行ってきたレポートを書きたいと思います!例によって興味があった内容をまとめました。Chad Fowler の基調講演の内容は情熱プログラマー ソフトウェア開発者の幸せな生き方を読むのが一番いいと思います。
また、内容に誤りがありましたらお手数ですが指摘いただけますと助かります。

Ruby powering 9 million dining tables(Cookpad)

Cookpad CTO の橋本さん(@)によるCookpad のサービス、システム全般のお話と、id:secondlife さんが何故Cookpad に入社したかというお話を聴きました。

規模
  • 9.89 M UU / month
  • 83万レシピ
開発体制
  • ユーザにとって価値のあるものを作成するには
  • プロトタイプをどんどん作る
    • プロトタイプのうち、90% はリリースされない
    • どんどん作る環境としてruby, rails はとても適している
      • 食べみるは、ruby 初心者が一ヶ月で開発
  • Agile
    • ユーザに高速に価値を提供することができる
システム構成
  • MySQL 5.5 + Triton
    • 5.1 だったような気もする
    • スライドみようと思ったけど公開されていない
  • Ruby On Rails 2.3
  • Cent OS 5.3
LVS - Web  ---------------- LVS -- App(rails)
          |_ Cache_fs                   |_ Cache(フラグメントキャッシュ)
  • PC版とモバイル版はWeb もApp も別
  • 99% 以上のリクエストを200ms で返すことが出来ている
キャッシュ

使っているキャッシュは2種類。

  • ページキャッシュ
    • レシピページ、トップページ、カテゴリページ等で使っている
    • cache_fs
      • レンダリング結果をcache_fs に保持し、web ⇒ cache_fs とリクエストが行くようにしている(app にはいかない)
      • cache_fs はNFS マウント
      • キャッシュがなくapp に届いた時はapp からNFS を経由してキャッシュをためる
    • 実態はただのファイルなのでcron で1日1回消せばよく運用が楽
  • フラグメントキャッシュ
  • 3つのできないキャッシュ
    1. ようこそ〇〇さん等ユーザ依存のもの
    2. 広告
    3. アクセスログ
    • rails レベルでとっているため、web で返ってしまうとログに残らない
    • 対策としては、ページキャッシュ + ajax で動的部分をリクエストする
      • 以前Web+DB でニコ動もこの方法を使っていると読んだことがある
  • 検索結果のキャッシュ
    • 講演内容に検索結果もページキャッシュしているという部分があり、「クエリパターンを全部キャッシュしているの?」とか「新しいデータ作成されたらキャッシュの再生性を行うの?」とか疑問に思ったので、講演後に中の人に伺いました
    • 1日1回バッチでキャッシュを作成する
      • ログから上位x 件の検索パターンに対して検索結果のキャッシュを生成
      • 検索パターンはいうほど多く無いらしい
      • 1日1回しか作らないので、更新されても次の日までは検索結果に反映されない
      • 本当は良くないけど現在はそうやっいる
DB レプリケーションとacts_as_readonlyable
  • 更新直後はマスタからselect
    • 更新処理はpost で実行しているので、ApplicationController のbefore_filter を使って、post 以外はslave を見るようacts_as_readonlyable のフラグをセットしている
DSR とコネクションプーリングの問題

この問題についてはRails(ActiveRecord)でデータベースへのコネクションプーリングをさせなくする - ククラフトに詳細が書かれている。

  • cookpad ではDB へのアクセスもバランシングしていて、各slave の負荷が均等になるようにしている

App - LVS - DB

  • LVS ではDSR を使ってレスポンスはDB からLVS を通らずApp に返るようにしている
    • ここで問題があって、Rails 2くらいからAR がコネクションプーリングを行うようになった
    • DSR でDB から返ってきたコネクションをプーリングしてしまうと、次からLVS を通らず、一定のslave にアクセスし続けてしまいバランシングできない
  • 対策
    • MySQL のコネクションのコストってcookpad のようなWeb アプリにとっては誤差レベルなのでOFF にした
    • バランシングもされたし、MySQL 側のスレッド数も激減
      • それまでは、Passenger で複数のRails プロセスが起動していて、それぞれが5つプーリングしていたのでかなり無駄なスレッドが立ち上がっていた
マスタDBの分散
  • デュアルマスタ
    • マスター1がダウンした場合はマスター2 に自動切り替えするようにしている
      • ホットスタンバイ状態かな?
    • 切り離された元のマスタは手動で戻す
  • 他に、機能別にマスタを分割
ヘルスチェックの罠
  • とある時にやたらマスタDB の負荷が増える
  • App からマスタDB が生きているか確認している処理があって、その処理にstatus コマンドを使っていた。status コマンドは重い
    • 加えて、App がどんどん増えていくにつれマスタへの負荷が増えていた
  • 対策
    • ping でステータスチェックをするよう変更


やはり、問題が発生したら、原因分析と解決をしっかりとやっているという印象。

画像ファイル運用上の工夫
  • 画像系の規約として画像(パス)を保存しているtable にはphoto_saved_at というカラムを付けるようにしている
    • このカラムの値がNULL なら画像はなし
      • 画像を表示させる部分には全て同じヘルパを使うことでこの規約を適用している
  • 画像はCDN から配信している。キャッシュは長めで1年に設定
    • 画像が変更されたらどうするか
      • photo_saved_at のハッシュ値をパラメータとして付与して画像アクセスしている(js ファイルのキャッシュとかを防ぐ方法と同じ)
      • CDN はURL をキーにしてキャッシュしているのでこの方法でいける
バッチ処理
  • cron で処理を管理
  • 指示はHTTP を通じて行う
  • HTTP で命令が叩かれるのでworker 側には自由な言語を使うことができる
  • worker アプリはRails で実装
全文検索
  • Triton を使っている
  • MySQL のテーブルになっているので、結果を他のテーブルとJOIN することができる
  • MyISAM なのでひとつのマシンでインデクスを作成したあと、テーブルをcp で他のマシンのまくことができる
ログ
  • ログはMySQL のテーブルでとっている
    • CookPad の規模でも全然問題ない
    • 一方この量の解析をMySQL でやるのは厳しいのでHadoop を使っている
監視
    1. Web サーバのレスポンスタイムを監視。ログを流しておいて、200ms 以上かかったものを赤で表示している
    2. ステータスコードを監視して、どのくらいがリダイレクトして、どのくらいが200 を返したがとかを監視している
    3. mysql のshow full process list を監視してつまっているクエリがないかもチェック
    4. http の監視もしている。
    • etc.
オフィス
  • 以前は合宿をやっていたけど、今はオフィスが快適なので毎日合宿のようなもの
  • 壁一面がホワイトボート
質問
  • デザイナ - プログラマ間のコミュニケーションの取り方
    • デザイン専門というのはいない
    • デザイン面では公開前に必ず社長のチェックを受ける
  • ユーザにヒットするモノを作るには
    • 最初に実装されたものは大抵使いづらい。モデルユーザに使ってもらい使いやすくしていく
    • 90% の機能を捨てるという覚悟。本質に集中する
      • 公開している機能もどんどの削る
  • ミッションの共有をどうやっているか
    • 技術のための技術をしない
    • 目的ありきの技術
      • ここの認識がずれていると感じたら話しあって考える


ユーザ視点を本気でつらぬているということがヒシヒシ伝わってくる講演でした。

何故Cookpad に入ったのか(id:secondlife さん)
  • ものづくりの考え方に共感
  • エンジニア全員が技術+αを持っている
    • 話をしていてとても面白い
  • +αとは
    • コードの価値観
      • ここのコードがバグるのは絶対にあってはならないとかビジネスの視点を持っている
    • 多様な視点
    • ものづくりに真剣
  • 本気でユーザのことを思っていれば+αはついてくる
橋本さんへの質問

講演終了後にその場に来ているCookpad のエンジニアが紹介され、質問がある方は直接話かけてみて下さいという形式のフリータイムに突入。
早速橋本さんに質問。

  • 初心者エンジニアが育つにはどうすればいい?

個人的にも聞きたいこと。また、今自分がいる会社のエンジニアは大学時代にプログラムに触れていなかった人が大半のため、その人達が育ちやすい環境をどのように作ればいいと思うか、というあたりを伺ってみました。

  • 基本的にcookpad は中途しかとらない
  • 自身の経験を話すと期日までに作ると決めたら死ぬ気で作っていた
    • でも作っている時はとても楽しい
    • それこそ電車の中でも常にそのことを考えている。周りから見れば変な人だと思う
    • しかし、実際こうやっている時が一番のびた


講演内容は素晴らしいもので、何故大ホール枠じゃないんだろうと思ったほどです。
逆に、大ホールでないから、30分以上の時間を使えるし、質問タイムも長めに設けて下さったので企画部屋枠が一番だったのかもしれません。
本当に勉強になりました。ありがとうございます。

RWikiと怠惰な私の10年間

dRuby の@さんによるRWiki の活用事例、どうやって開発してきたのかの話。

仕組み
  • RWiki のプロセスを長く生かしている
    • そこにCGI を経由してアクセス
NoSQL(ネタ)
  • DB とかを使わないでログファイルに書き込む形式
    • DB とかいらない。なんでもプロセスに入れている
    • RWiki のプロセスが起動時にログファイルを読み込む
    • 基本的にデータはメモリから返して書き込み操作のみをDisk に書いている
  • メモリにはERB オブジェクトだけを保持
  • メモリで処理する
    • 全てのデータをオブジェクトにしてひとつのプロセスに配置
    • 要素が数万程度のArray やHash は普通に使える
活用事例
  • 3万5千ページと2万5千ページの二つのRWiki が1台のマシンで動いている
    • ここにはソースの変更履歴とかBTS の機能もある
  • これでも1.2 G くらいにしかプロセスがふくれない
    • Redmine が100 ページくらいで400M とかにいなって、Rwiki の方がいいと思った
問題点
  • 起動が遅い
    • 起動時にページをメモリに読みながらそのページにあるリンクも再帰的に読み込んで展開していた
    • リンクの展開にはキューを使い、同時に生成される木はひとつになるようにした
    • キャッシュとして、オブジェクトをMarshal.dump した状態でログファイルに書き込むようにした
      • ログには最低限の情報しか保存しないという方針だったけどあまりこだわらないことにした
  • 全文検索が遅い
    • 3万5千ページをscan メソッドで走査していた
      • すごいw
    • Senna を使うようにした
まとめ
  • 全てのオブジェクトをひとつのプロセスに配置するのは自然
    • 自然な設計なので変なトラブルにはまらない
  • メモリが大きくなるが100 ページとかなら全然問題ない


これで、RubyKaigi2010 のレポートは終わりです。
RubyKaigi からそろそろ1ヶ月が経ちますが、本当に良いイベントでした。昨年より多くの人と話すことができたのはシャイなRubyist界隈の自分にとって良い試みだったと思います。その甲斐あって、得られたものはとても大きかったです。
(あと、デモでto_f するところがto_i になっていてテストが落ちるという現場に遭遇したので「to_f 必要です」とツッコんだw)

最後に、RubyKaigi スタッフの皆さんを初め、スピーカー、スポンサー、参加者の全ての皆さんに感謝します。ありがとうございました。