Rails複数DB Casual Talksで話した

先日行われた Rails複数DB Casual Talks というイベントで複数 DB に関連するつらい話をした https://speakerdeck.com/eagletmt/fu-shu-dbtorails 。 r/w splitting を行う switch_point については以前の記事でもう少し詳しく書いてます。

質疑のときに出ていた複数アプリケーション間のモデルの共有方法についてちょっと補足。 僕は今年の新卒入社なのでそれ以前にどうやっていたかはよく知らないけど、本体と管理画面のようにモデルを共有するくらい密に結合しているアプリケーションにおいて1つのリポジトリに入れることは、つらさはあるけど理にかなっていると思う。 ちなみに以前は git submodule で共有していたこともあったらしいです。

gem とか git submodule とかでうまく分けられている事例があったら話を聞きたい……

複数アプリケーション + 複数 DB で不必要な establish_connection をしなくする方法、つまり必要なモデルだけロードする方法は1つわかっていて、App 1 から使われるモデル、App 1 と App 2 から使われるモデル、App 2 と App 3 から使われるモデル、…… みたいに細かく Rails Engine として切り出して、それらに合わせて各アプリケーションでそれぞれ必要なものを require するというもの。 ただ、この方法だとモデルファイルが1つのリポジトリの中で色んなディレクトリに散在してしまうことになって、それはそれでイマイチ感がある。 というわけで何かいい方法ないですかねーという話を最後にしたんですが、この状況に悩んでいるのはうちだけな気はしてます。

ISUCON4 本戦に参加して8位でした

ISUCON4 にクックパッド選抜の†空中庭園†《ガーデンプレイス》として @ryot_a_rai@__gfx__ と参加し、結果は8位でした http://isucon.net/archives/41187491.html 。 使用言語で大きくスコアが分かれることはないだろうということで、3人が共通して慣れている言語として Ruby を選びました。

最初に試しにベンチマークを一回実行しつつ app.rb を読んで、一回の実行だけでかなりスワップしていることと、入稿された動画が Redis に保存されていることに気付いて、まずそれを何とかしようとした。 LTSV 形式で書かれた nginx / Apacheアクセスログからレスポンスタイムの情報を出す access-log.rb を用意して、その結果から動画の配信が支配的だということがわかった。 Ruby の初期実装では全く使われていない ADS_DIR という意味深な定数があったので、そのディレクトリへ動画をファイルとして保存するように変更し、Ruby ではなく nginx が返せるようにした。 同時に、CPU コア数に対して多すぎる unicorn のワーカ数を減らしたり、unix domain socket を使うようにしたりする変更がされていた。

別々のサーバがそれぞれローカルにファイルを置くとなると、ファイルをどう配信するかが問題になる。 一番最初は、各サーバに ID を割り当て、動画がアップロードされたときに Redis に自分のサーバ ID も含めて保存するようにし、GET /slots/:slot/ads/:id/asset されたときにその動画がどのサーバにあるのか Redis から引いて、そのサーバへプロキシするようなものを golang で用意するような構成を考えていた。 しかしその構成を実装する前に、動画へのパスは GET /slots/:slot/ad が返す JSON に含まれている URL で決まるのでは? という話が出て、試しにその API から返す JSON に適当なクエリストリングをつけてベンチマークを実行したところ、クエリストリングがついた状態でアクセスがきたので、GET /slots/:slot/ad を変えれば動画のエンドポイントは変えられることがわかった。 そこで、JSON に含める動画の URL にサーバ ID を加えることにして、nginx の設定で自分のサーバ ID が渡されたら自分で配信し、そうでなかったらそのサーバ ID のサーバへ proxy_pass するよう設定した。 また、初期実装ではレポートの元となるログがローカルファイルに出力されているせいで複数台の構成でうまくいかないことがわかったので、ログも Redis に保存するようにした。

この状態で再度ベンチマークを実行したところ、CPU がほとんど使われておらず、I/O 負荷も全然高くなくてネットワークがボトルネックとなっていることがわかった。 最初は CPU が弱い1号機を Redis 専用サーバにして、残り2つをフロントかつアプリケーションサーバとして使おうと考えていたけど、3台ともフロントに置く構成でいくことにした。 また、サーバ間の無駄な通信を避けるために、GET /slots/:slot/ad がパスだけではなくホスト名も含んだ状態で動画の URL を返すようにして、nginx 間の proxy_pass が不要な形にした。 この状態で8000点近いスコアを出せて、結局ほぼこのときのスコアのまま終わってしまった。

この後、hiredis-rb を使うようにしてみたり、リダイレクトを一段減らしたり、Linux や nginx の設定でネットワークパフォーマンスを上げようとしたりしたけれども、どれも大して効果は出なかった。 リモートでのベンチマーク実行のスコアが全然安定せず、普通に±1000点くらいのバラつきがあったので、どれが効果があってどれが無いのか判断に困った。 スタンディングを見ても上位チームのスコアの差はだいたい2000点くらいの範囲に収まっていて、リモートのベンチマーク実行の不安定さを考えるとほとんど差が無いと思っていた。

帯域の制限に悩まされているときに、一度30万くらいの異常なスコアを出したチームがあった。 これを実現するには、どうにかして動画を配信せずに済む方法があると思った。 動画を返すときにちゃんと Last-Modified ついてるよなーという確認はしたんだけど、その先の Cache-Control には全然気付いていなかった。

競技後に聞いた話の中では、グローバル IP とプライベート IP の両方を使うという発想は全然なかったなーと反省した。 たしかに NIC 2つあるんだから、帯域の制限で困ってるんだったら両方使う発想は出てもよかった。 とにかく帯域に悩み続けて、メモリや I/O は十分に余裕があって CPU もほぼほぼ idle という状況だったので、app.rb 内の削れる処理に気付いたとしても「でも CPU は超余裕なんだよな……」となって進めなかった。 自分は今回が初めての参加だったけど、悔しいので来年もきっと出ます。

裸族の集合住宅を安定的に運用するのに必要なこと

ポートマルチプライヤ + eSATA ではなく USB 3.0 を使う

センチュリー 裸族の集合住宅5Bay SATA6G USB3.0&eSATA CRSJ535EU3S6G

センチュリー 裸族の集合住宅5Bay SATA6G USB3.0&eSATA CRSJ535EU3S6G

結局 UEFI ブートするディスクを作るにはどうすればいいの

今までずっと BIOS ブートばかりでセットアップしてたけど、今回の MB ではなんかブートできなかったので仕方なく今になって初めて UEFI ブートでセットアップした……

  1. インストールメディアから UEFI ブートする
    • 重要
    • 今回は archlinux-2014.10.01-dual.iso を USB メモリに dd して使ったけど、これは BIOS ブートも UEFI ブートもできるようになっている
    • たぶん MB の設定でどっちでブートするか変わってくるので、ちゃんと UEFI でブートしていることを確認する
    • あとたぶん Secure Boot とかは無効化しておく必要がある
      • 今回自分が使った ASUS の MB の場合、OS Type を Other OS に変えた
  2. EFI System Partition (ESP) を用意する
  3. grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=arch_grub --recheck --debug
    • いつも通り /mnt にインストール先のパーティションを mount して arch-chroot して諸々セットアップした後に、↑をやる
    • インストールメディアから UEFI でブートしてないと、ここでなんか efi 関係の失敗のメッセージが表示される
    • ↑で失敗しても最後に「Installation finished. No error reported.」と言って終わるので、この No error reported を信じてはいけない

switch_point について紹介した

もう数ヶ月くらい前になるけど Rails (ActiveRecord) で R/W splitting を行う switch_point という gem を書いた。 Rails アップグレード作業の中で、魔改造された acts_as_readonlyable をメンテすることに嫌気がさして、もっとマシな実装方法があるはずと思って勢いでコアの実装をして、それから実際のアプリケーションに組み込んで本番に投入していきながら機能追加やバグ修正を重ねて今の形になった。

先日の RubyKaigi 2014 の LT で、R/W Splitting in Rails というタイトルで switch_point の紹介をした。 今まで使い方を真面目に書いてなかったけど、LT 内で軽く紹介しつつ会期中に典型的な使い方を README に書いた。メソッドやクラスのドキュメントは全然書いてない (要るのかな…)。

LT の発表では言ってなかったけど、mirakui さんによる開発者ブログの記事 にあるように、switch_point は今のクックパッド本体アプリケーションで実際に本番で使われている。

switch_point は、

  • R/W splitting のみに注力
  • Rails の激しい変更についていきやすい設計・実装
    • 内部実装 (普通の Rails アプリケーションからは直接使われることの無いクラスやメソッド) になるべく依存しない
      • ActiveRecord の内部実装はなぜか変わりやすい…
  • あまり implicit な動作は行わない
    • いいかんじに Master/Slave の選択を行うようなことをなるべくサポートしない
    • あえて Master に SELECT したいときには自然にそれが行えるようにする

あたりに気をつけて作っていて、実際このままのコードで次の Rails 4.2 でも動きそう。 自分には必要無いので全く検証してないけど、Rails 3.1 とか 3.0 とか、もしかしたら更に前のバージョンでもそのまま動くかもしれない。 ちなみに switch_point という名前はレールと切り替えからの連想で付けていて自分ではわりと気に入ってる。

octopus はよくできてそうだと思う一方、以下のことが気になって乗っかる気になれなかった。

  • Rails バージョンアップ時が心配
    • アプリケーションが依存している gem の対応が遅いと、アプリケーションの Rails バージョンアップ時に足枷になりやすい
  • メンテされなくなったときが怖い
  • メインの機能はシャーディング
    • コード量やモンキーパッチ量が多くなっているのは、いいかんじにシャーディングを実現するためのように見える
    • でもシャーディングは要らなくて、R/W splitting だけが欲しい

シャーディングが必要な場合は引き続き octopus がよさそうな現状ですが、R/W splitting のみで十分な場合は switch_point も検討してもらえると嬉しいです。

https://github.com/eagletmt/switch_point

字幕検索

Elasticsearch の全文検索を使ってみたくて、字幕データ (.ass 形式) を Elasticsearch に入れてみたら楽しかった。 https://github.com/eagletmt/eagletmt-recutils/tree/master/caption-search

TS からの字幕抽出は assdumper でやってる (これまだ若干バグってて字幕テキストの最後に変な文字が入ったりしてる……)。

最初は各字幕の開始時刻・終了時刻も入れてたけど、それを知ってもあんまり嬉しくないと思って、字幕は単なるテキストとして入れた。 自分は kaede を録画システムとして使っていて、これをそのまま使えばタイトルやサブタイトルがファイル名に入るので、ファイル名と字幕テキストさえあれば十分だと思い、それしか Elasticsearch に入れてない。

自然言語処理技術とか検索技術とか全く知らないけど、日本語テキストがいいかんじに検索可能になって Elasticsearch + kuromoji 便利だった。 あと U 局アニメも字幕つけてほしい。

Linux デスクトップセットアップトラブルシューティング

最近クリーンインストールしたときに遭遇してちょっと手間取ったやつです。

X 動かすのに何インストールすればいいんだっけ

xorg (group), xorg-xinit, xf86-video-intel (intel-dri)

ターミナルで ASCII 文字も全角幅で表示される

ロケール (LANG) が ja_JP.UTF-8 になってない

ターミナルで▽とか☆とかがつぶれて表示される

UTF-8-CJK の設定し忘れ、ロケールの設定し忘れ https://github.com/eagletmt/misc/blob/master/ruby/ambiwidth.rb

uim を入れたのに C-j がきかない

gtk-query-immodules-2.0 --update-cache

bluetooth の設定の仕方忘れた

bluetoothctl でペアリング。udev の rule を書いて起動時に hci0 を開くようにする。 https://wiki.archlinux.org/index.php/Bluetooth

なんかブラウザのフォントのレンダリングが汚ない

fontconfig の設定とか書けばいいんだっけ…?