Railsでページキャッシュ( page caching )を使うべきでない理由

Rails4からはページキャッシュとアクションキャッシュが標準から除かれて外部のgemになりました(参考: [Rails 高速化] ページキャッシュ、N+1対策、SQLチューニング - 酒と泪とRubyとRailsと)。

アクションキャッシュはなんとなく中途半端で使いどころも難しそうな感じだったので理解できるのですが、ページキャッシュまで除かれちゃったのはどうしてなのかなと色々調べていたら、以下の記事が良い感じにまとまっていました。

Page and Action Caching Gem Extraction [Rails 4 Countdown to 2013] | The Remarkable Labs Blog

要約すると

  • sweepersとかobserversとか手動でexpirationの管理が必要で煩雑
  • アプリをスケーリングさせようとしたときにその煩雑さがネックになる
  • ページキャッシュを使うとページ要素が全て静的になる。それならmiddlemanでも使えばいい
  • ステートレスなキャッシングはVarnishとかのreverse proxyが負うべき役割だろう

expirationの管理については、コメント欄でも突っ込まれているようにmemcachedのexpired_atを使う方法などもありそうなところですが、そもそもページレベルでのキャッシュがRailsがカバーすべきところなのかという点については個人的に同意できるところです。ちょっとレイヤーが違うというか、スマートさに欠ける気がしなくもない感じなのです。

Rails4の方向性という意味ではturbolinksとの絡みなんかも考慮されたのかもしれませんね。

そうは言っても、実際にページキャッシュをうまく使えば10倍とかのレベルで速さが違ってきますので、例えば安サーバで運用される向きなどには程よい現実解として簡単には諦めきれないところもあるかと思います。上記の問題を理解したうえで、よく考えてキャッシュの範囲を決めるのが大事になるでしょう。

bundlerを1.0.10にするとPsych::SyntaxError: couldn't parse YAML at line 14 column 13のエラー

padrinoのadmin機能を試してみたいと思い、Blog Tutorialのサンプルを上からそのままやってみていたところ、http://localhost:3000/adminにアクセスし、認証を通過したところで

Psych::SyntaxError at /admin/
couldn't parse YAML at line 14 column 13

というエラーが表示されてしまいました。ちなみに環境はMacOS10.6.6, ruby1.9.2, gem 1.3.7, bundler1.0.10, padrino0.9.20です。

padrino固有の問題かと思ったのですが、調べてみたら結構いろいろな環境で同種のエラーが出ているようです。最終的に
rails error, couldn't parse YAML - Stack Overflow
上記の回答にあるように、bundlerのバージョンを戻してやれば良いようです。私の環境では

$ sudo gem uninstall bundler
$ sudo gem install bundler -v 1.0.9

これだけで現象が回避されるようになりました。bundlerのアップデートはしばらく様子を見た方が無難なようです。

MongoDBにおける関連(Relation)のスキーマ設計

前回、MongoDBでSNSつくるぞという記事を書いてから随分時間がたってしまいました。単に私がだらけていたということもあるのですが、一番ひっかかって時間を取られていたのが、MongoDBにおけるスキーマ設計の考え方です。

いまだに試行錯誤中ではありますが、現時点において私がこうあるべきと理解しているところをアウトプットしてみたいと思います。

1.One to Many のケース

たとえば注文と注文明細のケースを考えてみます。RDBで1対多のリレーションを設計する場合、

というように、注文明細を別テーブルにするのが普通かと思います。しかし、ドキュメント指向のMongoDBにおいては、RDBと違ってオブジェクト内に柔軟なデータ構造を実現できるため、

というように一つのCollection内にデータを埋め込んでしまうのが、パフォーマンスの点からも良しとされています。

ただし、以下の2点については注意すべきです。

まずはデータサイズの問題。MongoDBにおける1オブジェクトの最大サイズは4MBまでの制限があります。明細が巨大なデータ量になる可能性がある場合は、埋め込みにせずにCollectionを分離するのが無難です。

また、MongoDBはトップレベルのCollectionに対する検索は最適化されていますが、埋め込みデータについてはそうでもありません。注文明細を注文とは別の観点から検索する必要がある場合は、別Collectionに分けていたほうがよいでしょう。

まとめると以下の通りです。

  • 1対多のリレーションにおいてはパターン2が理想
  • データサイズが巨大になる可能性がある場合はパターン1
  • 柔軟な検索が必要となるケースでもパターン1のほうが無難

他にも留意すべき点はありますが、細かいところになるので以下の公式ドキュメントをご参照いただければと思います。
Data Modeling Considerations for MongoDB Applications日本語訳

1 to Many のケースは、シンプルなため比較的考えやすいのではないでしょうか。続いてMany to Many のケースです。

2.Many to Many のケース

ユーザが本をお気に入りにいれるケースを考えます。RDBで多対多のリレーションを設計する場合、

というように、中間テーブルを設けるのが普通かと思いますが、柔軟なデータ構造を実現できるMongoDBにおいては、中間テーブルを廃し

というような設計にすることができます。以下の資料を参考にしましたが、ここはいかにも「らしさ」がでていてるところではないかと思います。
MongoDB Schema Design • myNoSQL

ただし、One to Many のケースと同様、制限事項はあります。

多対多のこのケースでは、保持するのが他方のCollectionオブジェクトへの参照だけなので、パターン1とくらべればかなり多件数のデータを格納することができます。とはいえ、ObjectIDにしてもデータサイズは12byteかかるわけなので、あまりに件数が多すぎるとオブジェクト上限の4MBからあふれてしまいます。

1,000件程度なら問題なし、10,000件程度なら要考慮、100,000件レベルなら別Collectionといった感じになるでしょうか。Twitterでいくと、Favoriteは問題なくてFollowはアウトというイメージです。

もう一点考慮すべきは、関連(Relation)自身が独自のデータを持つケースです。お気に入りにレビューを書くことのできるケースを考えてみてください。ユーザと本のそれぞれにレビューデータを格納するのは冗長過ぎるので


というようにCollectionを分けることになります。こうなると「Favorites」はRelationというよりEntityと考えるべきかもしれません。

まとめると以下の通りです。

  • 多対多のリレーションにおいてはパターン4が理想
  • 件数がかなり多くなると考えられるケースはパターン3が無難
  • リレーション自身がデータを持つ場合はパターン3

3.まとめ

理想としてはパターン2やパターン4を使いたいところですが、色々制約もあるので、結局迷ったらRDBと同じ要領で設計してやるのが無難かなというのが、今のところの率直な感想です。

ただし、今回の説明では分散環境についての考慮がありません。スケーラビリティを実現する設計を行うためには、当然のことながらMongoDBにおけるShardingの仕組みをある程度わかっていないといけないでしょう。次回はそのあたりについて書いてみるつもりです。

rvmからrailsを利用しようとすると'no such file to load -- openssl'のエラー

環境はUbuntu10.04です。

rvmからrailsを利用しようとすると、server実行時に

/home/masa/.rvm/gems/ruby-1.9.1-p378@rails2/gems/rails-2.3.8/lib/initializer.rb:271:in `rescue in require_frameworks': no such file to load -- openssl (RuntimeError)

というエラーが出力されました。

Ubuntuでaptからrubyをインストールする場合、通常は

sudo apt-get install ruby build-essential libopenssl-ruby ruby1.8-dev

というようにコマンドを実行します。そうすると、例えば

/usr/lib/ruby/1.8/openssl

といったような場所にopensslがインストールされるわけですが、rvmで実行されているrubyからはこのパスがみえないため、上記のエラーとなってしまいます。

解決手順は以下のとおりです。

手順1. 以下のコマンドで、rvm管理下にopensslをインストールする。

rvm package install openssl

手順2. いったん、インストール済みのrubyを削除する。

rvm remove 1.9.1

手順3.以下のオプションでrubyを再インストールする。(パスは環境によって読みかえてください)

rvm install 1.9.1 -C --with-openssl-dir=$HOME/.rvm/usr

上記手順2と3は、利用したいすべてのversionで必要となります。手順1については省略可能です。その場合、手順3のパスに

--with-openssl-dir=/usr/lib/ruby/1.8/

というように既存のパスを設定します。

参考:http://rvm.beginrescueend.com/packages/openssl/

Ubuntu10.04でrubygemsを最新版にする方法

Ubuntuというよりdebian系の話ですが、単純にapt-getでrubyおよびrubygemsをインストールした場合、そのあと

sudo gem update --system

としても

gem update --system is disabled on Debian. RubyGems can be updated\

みたいなエラーとなってしまいます。aptでの管理と矛盾が出ないように抑制されているためです。

8.10を使ってたころはrubyはaptで入れたあとに、ここにあるような感じでgemは直接インストールみたいなチップスがあったのですが、このとおりやってみてもどうもパスが通らなかったりで上手くいきません。

調べて見たところ、最近はrubygems-updateなるものがあるようですね。現在は、Ubuntu上でruby環境を整えるには以下の手順でよいようです。

1. rubyのインストール

sudo apt-get install ruby-full build-essential

2. rubygemsのインストール

sudo apt-get install rubygems

3.rubygems-updateのインストールと実行

sudo gem install rubygems-update
sudo update_rubygems

あとはrailsとかを必要に応じて

sudo gem install rails

みたいにaptでなくgemで入れていけばOKです。

昔と常識が変わっていて戸惑ってしまいましたが、結果的にはかなりスムーズにインストールできるようになっていますね。Web上ではまだ昔の情報が残っていて混乱しそうなので、記事に起こしてみました。

参考:
https://help.ubuntu.com/community/RubyOnRails

msysgitをインストールするとコマンドラインで日本語が文字化け@Windows Vista

Windows上でRuby on Railsのpluginをgitからインストールするため、こちらの記事を参考にmsysgitをインストールしました(ファイルはGit-1.7.0.2-preview20100309.exe)。
インストール自体はうまくいき、gitからプラグインを取得できたのですが、なぜかコマンドラインで日本語が文字化けするように。文字コードを直せばよいのかと思い、こちらのページを参考に

chcp 932

と打ち込んでみましたが、どうも直りません。

さらにあちこち探してみたところ、以下のページを発見。
Windows Vista のコマンドプロンプトが文字化けしている場合の対処方法 :: drk7jp
まさにずばりでした。記事の中のCMD.EXEのプロパティはファイルそのものでなく画面のタイトルバー上で右クリックすると出てきます。何回も出てくるエラーを無視し続けてフォントサイズを設定してやると無事日本語が表示されるようになりました。

Rails2.3.2からCGI::Session.generate_unique_idが使えなくなっていた

Rails2.2.2で作ったアプリを2.3.5に移行しようとしたのですが

uninitialized constant CGI::Session

というエラーが出てしまいました。調べてみたところ、ユニークIDを生成するために使っていた

CGI::Session.generate_unique_id

が2.3.2以降なくなっていたためでした*1
これはRuby1.9*2で標準装備された機能を利用するようにしたためで、

ActiveSupport::SecureRandom.hex

または環境がruby1.8.7または1.9以降の場合は直接

SecureRandom.hex

を代わりに呼んでやればいいようです。
修正の方向性としては正しいものだと思いますが、一応publicなメソッドであったからにはエイリアスくらい残しておいてほしかった気もします。まぁ、こんなマイナーな機能使ってた人も少ないでしょうが。

*1:[http://groups.google.com/group/paperclip-plugin/browse_thread/thread/c45922d0dc2fee83/984c3087c4b12c63:title]参照

*2:1.8.7でも利用可。[http://d.hatena.ne.jp/rubikitch/20080508/ruby187:title=こちら]を参照。