[Rails] memcache-clientやdalliでfork後のresetは大切

Railsのsession_storeやcache_storeとして、memcachedを使うことはよくあると思います。

今回は、memcachedのRuby用クライアントgem, memcache-clientやdalliにて、Passengerやunicornのfork後の初期化をきちんと書かないと、悲惨なことになるというお話です。

※他のgemやmemcached以外でも当てはまります。

必要な初期化処理

以下の処理を、config/environments/production.rbや、config/initializers/session_store.rbなど、どこでも良いので初期化コードに書いておく必要があります。

dalliを利用している場合
https://github.com/mdesjardins/dalli/tree/

# passengerの場合
if defined?(PhusionPassenger)
  PhusionPassenger.on_event(:starting_worker_process) do |forked|
    Rails.cache.reset if forked

    ObjectSpace.each_object(ActionDispatch::Session::DalliStore) { |obj| obj.reset }
  end
end

# unicornの場合
after_fork do |server, worker|
  if defined?(ActiveSupport::Cache::DalliStore) && Rails.cache.is_a?(ActiveSupport::Cache::DalliStore)
    Rails.cache.reset

    ObjectSpace.each_object(ActionDispatch::Session::DalliStore) { |obj| obj.reset }
  end 
end

memcache-clientを利用している場合
http://petelacey.tumblr.com/post/3073967460/on-forking-application-servers-and-memcached-in-rails

これをやらないと何が起きるのか

「本番環境でのみ」「たまに」、取得されるデータが壊れたり、違うデータが取得されたりします。

なんでそんなことに

Passengerやunicornは(他のもそうだと思いますが)、最初にRailsのプロセスを立ち上げたあと、リクエストを捌くworker processをforkして増やします。

この際、既にmemcachedへの接続が確立されている場合、そのsocketもそのままforkされるので、1個のコネクションを複数プロセスが共有する形になってしまいます。
2個以上のプロセスがほぼ同時にリクエストを発行した場合、結果が混ざったり順番が入れ替わったりして、壊れたデータや入れ替わったデータが取得されてしまいます。

ちなみに、SQLなどでも同じことは起こるのですが、サンプルのunicorn.conf.rbには初めからこのような初期化コードが入っているため、知らなくても動いてしまったりします。

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
end

上記の初期化コードは、確立されたコネクションをいったん切って、それぞれのプロセスで新たにmemcachedサーバに接続しなおしています。

恐ろしいことに

当然ながらworker processが2つ以上同時にリクエストしないと発生しないため、ローカル環境で1人で試していてもたいてい気づきません。

もちろん、webrickの1インスタンスで実行していても、再現しません。

さらに、複数workerでも、一定以上の頻度でアクセスしないと、あまり再現しません。

結論

ドキュメントはよく読みましょう。
本番に近い構成で、しっかりテストしましょう。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

baba

ゆとりプログラマー。 高校時代から趣味でプログラミングを初め、そのままコードを書き続けて現在に至る。慶應義塾大学環境情報学部(SFC)卒業。BPS設立初期に在学中から参加している最古参メンバーの一人。Ruby on Rails、PHP、Androidアプリ、Windows/Macアプリ、超縦書の開発などを気まぐれにやる。軽度の資格マニアで、情報処理技術者試験(15区分 + 情報処理安全確保支援士試験)、技術士(情報工学部門)、CITP、Ruby Programmer Goldなどを保有。

babaの書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ