前回、最近割と暇とか書いたらなんかエラーが増えてきて対応しなきゃって事案が発生した。Rubyなアプリケーションの運用は今の会社で初めてなんだが、こういうトラブルに直面すると知見が増える。
いうてもやることは結構あるっちゃあるんだけどね。直近だとうるう秒対応とか…。俺んとこはntpd止めることにした。で、うるう秒経過後に起動する、と。ワケあって本番のOSがgentooだし、何が起きるかわからんので。AWS上にいるAmazon Linuxや、GCE上にいるUbuntuはslewモードで乗り切る。しかしまあマルチクラウドも考えものだな…。運用しづらい。個人的には今のGentooなインフラをAWSに移したいんだが、会社としてまだその判断はしないようだ。まあインフラ周り見てるの俺一人しかいねーしな…。



環境

  • Ruby 2.0.0
  • Rails 4.0.1
  • Unicorn 4.6.3
  • Octopus 0.8.1
  • MySQL 5.6
症状

デプロイ時や、unicornのワーカ自動再起動時(monitでunicornの使用メモリを監視していて閾値超えたらワーカを殺している。unicorn_worker_killerというgemがあるが、それと近いことをmonitにやらせている。)に、下記のようなエラーが出て、結果ユーザにHTTP 500が返ってしまっていた。

ERROR — : NoMethodError: undefined method `query’ for nil:NilClass: SELECT xxxxx

前から「なんかたまにエラー出るな…」くらいに思っていたんだが、おかげさまでユーザ数も増えてきてこのエラーが無視できなくなってきたので調査することにした。

原因と対策

原因はOctopusで、issueに対策が書いてあった。この業界、「ググる」能力は必須だが、「イシュる」能力も同じように必要だな…。己のRuby力の低さもあいまってここまでたどり着くのに結構時間がかかった。最初はActiveRecordを疑ったし。

https://github.com/tchandy/octopus/issues/59#issuecomment-3134239

対策はこのissueに書いてある通りなんだが、unicornでは通常fork前にActiveRecord::Base.connection.disconnect!してコネクションを切って、fork後にActiveRecord::Base.establish_connectionする。しかしOctopusを使っているときは、

before_forkでActiveRecord::Base.connection.disconnect!

としていたのを

ActiveRecord::Base.connection_proxy.instance_variable_get(:@shards).each { |shard, conn| conn.disconnect! }

とする。同じく

after_forkでActiveRecord::Base.establish_connection

としていたのを

ActiveRecord::Base.connection_proxy.instance_variable_get(:@shards).each { |shard, conn| conn.clear_reloadable_connections! }

とする必要がある。

どういうことか

以下は俺なりの理解。
Octopusを使うとスレーブへのコネクションだけでなくマスターへのコネクションもOctopusの管理下に置かれる。Octopus::Proxyを見ると、@shards(= HashWithIndifferentAccess.new)にshards.ymlの情報を突っ込み、最後に@shards[:master] ||= ActiveRecord::Base.connection_pool_without_octopusとしてマスタの情報も突っ込んでいる。すなわち各DBへの接続情報は@shardsに入っている。

ここで、このOctopus::Proxyは、Octopus::Modelに書かれたconnection_proxy経由で取れる。また、Octopus::ModelではActiveRecord::Base.extend(Octopus::Model)しているのでActiveRecord::Base.connection_proxyが呼ぶことができる。よってActiveRecord::Base.connection_proxy.instance_variable_get(:@shards)で@shardsにアクセスできるので、それをeachで回して、@shardsに格納されたDBごとのコネクションをとってきて、before_forkでコネクションを切る -> after_forkでコネクションを貼りなおすといった処理を行う。

このようにしてOctopusを使っているときは、DBごとのコネクションをケアをしてやる必要がある。という理解。

だいぶ減った

毎朝slackにドメインごとにデイリーのエラー数を通知するようにしてるんだが、

uni1

対応して1週間くらい経過観察していたんだがとりあえずはこれで良さそうだ。502はunicornのダウンと思うかもしれないが、これはまた別の問題(対応済み)。

uni2

おわり

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <img localsrc="" alt="">

Set your Twitter account name in your settings to use the TwitterBar Section.