CubicLouve

Spring_MTの技術ブログ

ADMIN PINGとは

前提

Railsのバージョン

https://github.com/rails/rails/tree/v4.2.6

ADMIN PINGの正体

ADMIN PINGというコマンドはありません。

実態はMySQLのCOM_PINGというコマンドになります。

https://dev.mysql.com/doc/refman/5.6/ja/mysql-ping.html

https://github.com/mysql/mysql-server/blob/71f48ab393bce80a59e5a2e498cd1f46f6b43f9a/libmysql/libmysql.c#L931

pt-query-digestがCOM_PINGのコマンドを ADMIN PINGと見せています。

https://github.com/percona/percona-toolkit/blob/b118e39d1057058b59f0a606946f189f640a693e/lib/MySQLProtocolParser.pm#L590

クエリではないので、単純にtcpdumpしていてもADMIN PINGのコマンドを確認することはできません

(パケットの内容を16進表示して理解できる人には見えるかもしれません)

RubyでADMIN PINGを送っている場所

mysql2にpingを送るMysql2::Client#pingメソッドが用意されています。

https://github.com/brianmario/mysql2/blob/7879f1ee4f976e8b31a4378925f3b7621eb1b431/ext/mysql2/client.c#L1083

pingがbool値を返します。(pingが通ればtrue)

RailsでMysql2::Client#pingを呼び出しているのはActiveRecord::ConnectionAdapters::Mysql2Adapter#active?のみになります。

https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L89

ここまでで実態はつかめたかと

実際にActiveRecord::ConnectionAdapters::Mysql2Adapter#active?が呼ばれるまでの経由

これはRailsMySQLとのコネクションを確立するまでの流れを追ってみます。

http://blog.livedoor.jp/sonots/archives/38797925.html sonotsさんのblogがわかりやすいです。

Railsの立ち上げ時

ActiveRecord::Base#establish_connectionが呼ばれるが、この時点ではMySQLサーバーにコネクションは貼られない。

モデルクラスがロードされて、評価されると、pk_cache_adapterがPKを取得するためにMySQLサーバーに通信します。

findとかをMySQLサーバーからデータを取得する

AR::Base.find AR.find(:id)は下記が呼ばれる

find https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/core.rb#L126

AR::ConnectionAdapters::ConnectionPool#connection

findの中でconnectionが呼ばれると下記メソッドが呼ばれます。

connection https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L259

AR::ConnectionAdapters::ConnectionPool#checkout

ここでMySQLサーバーにコネクションを貼ったobjectがなければ、checkoutにいきます。

checkout https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L347

checkoutでは、 acquire_connectionでpoolからMySQLサーバーにコネクションを貼ったmysql2_adapterのobjectを取ってくる or 新しくMySQLサーバーにコネクションを貼ったmysql2_adapterのobjectを取ってきます。

acquire_connection https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L418 その後に、checkout_and_verifyがよばれます。

checkout_and_verify https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L454

checkout_and_verifyの中で、AR::ConnectionAdapters::AbstractAdapter#verify!を呼び出して、取ってきたobejctの中のMySQLサーバーのコネクションが生きているかを確認し、 もし死んでたら、MySQLサーバーへのコネクションを貼り直します。

veryfy! https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L327

この中で、active?が呼ばれて最終的にpingが飛びます。

ADMIN PINGを抑制するために

AR::ConnectionAdapters::AbstractAdapter#verify!で何もしないようにする

def verify!(*ignored)
  reconnect! unless active? # ここを削除する
end

このveryfy!はconnection_pool経由でobjectを取って来た際に、objectは存在するがMySQLとの接続が切れている可能性があるためにその確認をするためのメソッドかと思います。 (思わぬところでconnectionが切れている、idle状態でwait_timeout超える等々)。

APIはリクエスト毎にMySQLのコネクションを貼り直していることを前提にします。

1リクエスト内では、一度貼ったコネクションをもつobjectは AR::ConnectionAdapters::ConnectionPool#connectionの中で、@reserved_connections インスタンス変数に現在のスレッドのobject_idをキーにキャッシュされ、ここから利用されます。 (余談ですが、WEBrickはmulti threadなWEBサーバーです)

そうすると1リクエスト内で一度AR::ConnectionAdapters::ConnectionPool#connection経由でコネクションを貼ると、AR::ConnectionAdapters::ConnectionPool#checkoutまで到達しません。

なので、AR::ConnectionAdapters::ConnectionPool#checkoutに到達するのは、

最初の一回目でコネクションをはるとき or 1リクエストの中で@reserved_connectionsが開放され、かつARのコネクションプールが残っている場合

です。

最初の一回目はコネクションを貼った直後に切れていることはないと思うので、verify!を向こうにしてADMIN PINGを飛ばないようにしても問題なさそうです。

https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L347

      def checkout
        synchronize do
          conn = acquire_connection <- ここで貼ったコネクションが
          conn.lease
          checkout_and_verify(conn) <- この間で切れる場合にveryfy!の結果が効くがそんな刹那なタイミングで切れることはないかと
        end
      end

@reserved_connectionsが開放されARのコネクションプールが残っている場合において、 生存確認がないとMySQL側でコネクションが切られているときにエラーになりますが、MySQLサーバーがコネクションを切る場合はreconnectしてもつながらない場合のほうが多いかなと。

このMySQLサーバーとのコネクションが切れているconnection objectを使って通信すると Lost connection to MySQL のエラーが即座に返ってくるので、詰まることはないと思います。