2014年06月

ghq のディレクトリ構成に合わせて既存の git ディレクトリを mv するやつ

ghq + peco を使い始めたけど、既存 git ディレクトリを ghq get し直して回るのは面倒くさいし git clone するのに時間がかかるので、簡単なシェルスクリプトを3分クッキングして ghq ディレクトリ形式(正確には go get 由来)に mv するようにした。

ghq-mv.sh
#!/bin/bash
# Usage: ghq-mv.sh target_dir ghq_root_dir
target=$1 if [ -z "$2" ]; then ghq_root=$HOME/src else ghq_root=$2 fi if [ -d "$target/.git" ]; then cd $target url=$(git remote -v | grep origin | head -1 | awk '{print $2}') basename=$(basename ${url%.*}) dirname=$(dirname $url | sed 's!git@\([^:]*\):!\1/!' | sed 's!https://!!' | sed 's!git://!!') cd - mkdir -p $ghq_root/$dirname echo "mv $target $ghq_root/$dirname/$basename" mv $target $ghq_root/$dirname/$basename
fi 

使い方はこんなかんじ
$ bash ghq-mv.sh 対象ディレクトリ {ghq root ディレクトリ}

xargs と組み合わせて  git ディレクトリを全部一気に移動した。対象になったものが .git を持つディレクトリでない場合は無視されて、git ディレクトリだけが移動される。
$ cd ~/gitrepos
$ \ls | xargs -I{} bash ghq-mv.sh {} ~/src  

1回使ったらもう使わないので捨ててもらって構わない。

天下一InfluxDB勉強会で話してきた

まとめページはこちら (togetter)。そのうち動画もあがるそうです。=> あがりました http://labs.gree.jp/blog/2014/06/10939/

InfluxDBの概要について話してきたので資料をおいておきます。対象 ver は現在の最新である v0.7 です。
 

[Ruby] 例えば、DNS Resolver をすげかえる

例えば、独自の仕組みで DNS キャッシュをする社内ライブラリがあった場合に、すべての名前解決を ruby 標準のものから、そのライブラリ経由に置き換えたい。

Ruby には標準で resolv-replace.rb というものがあって、これを使うと libc の resolver を Ruby で実装している Resolv クラスに置き換えることができる。

というわけで、これを参考に、すきな Resolver にすげかえられる gem を作った。 名前になやんだけれど、resolver_replace ぐらいにしておいた。名前かぶっとる\(^o^)/

使い方はこんなかんじで、ここでは例として、ruby 標準にある Resolv::DNS を使うようにすげかえている。

require 'resolv' # Resolve::DNS
require 'resolver_replace' # ResolverReplace

resolver = Resolv::DNS.new(:nameserver => ['210.251.121.21'],
                           :search => ['ruby-lang.org'],
                           :ndots => 1)

ResolverReplace.register!(
  getaddress: Proc.new {|host| resolver.getaddress(host) },
  getaddresses: Proc.new {|host| resolver.getaddresses(host) },
  error_class: Resolv::ResolvError,
)

これで net/http など大体はOK。

「大体」と言っているのは、mysql2 などの ruby ライブラリを使わずに C 拡張で実装しているような gem には効かないため。 そこでそういう gem のために plugin 機構を用意した。今のところ mysql2 だけ用意していて、以下のようにして適用できる。

ResolverReplace.load_plugin('mysql2')

まとめ

Resolver をすげかえるには、https://github.com/sonots/resolver_replace を利用できる。

後記: 今だと(?)標準機能の Resolve::DefaultResolver.replace_resolvers で置き換えられそう 

[Ruby] 例えば、ActiveRecord の connection_pool を止める

今の ActiveRecord は connection_pool ありきの実装になっているが、「永続接続が許されるのは中規模サイトまでだよねー」と誰かが言ったとか言わないとかで、永続接続を止める。対象は activerecord 4.1。

TL; DR

https://github.com/sonots/activerecord-refresh_connection を利用できる。

事前知識

Rails が起動すると、ActiveRecord::Base#establish_connection が呼ばれるが、実は establish_connection では connection は貼られず、設定情報が渡されるだけである。(正確には、すでにあった接続を破棄する処理も行われるが)

lib/active_record/connection_handling.rb#L37-L47

    def establish_connection(spec = ENV["DATABASE_URL"])
      resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations
      spec = resolver.spec

      unless respond_to?(spec.adapter_method)
        raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
      end

      remove_connection # disconnect connections in a poll
      connection_handler.establish_connection self, spec
    end

lib/active_record/connection_adapters/abstract/connection_pool.rb#L537-L541

ConnectionPool のインスタンスがセットされているけれど、connection が作られてはいないことがわかる。

      def establish_connection(owner, spec)
        @class_to_pool.clear
        raise RuntimeError, "Anonymous class is not allowed." unless owner.name
        owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
      end

lib/active_record/connection_adapters/abstract/connection_pool.rb#L261-L267

では、いつどこで connection が貼られるのかというと、ActiveRecord::Base.connection が呼ばれたときである。 ここで connection pool にキャッシュされ、すでにキャッシュされていればソレを利用するようになっている。

      # Retrieve the connection associated with the current thread, or call
      # #checkout to obtain one if necessary.
      #
      # #connection can be called any number of times; the connection is
      # held in a hash keyed by the thread id.
      def connection
        # this is correctly done double-checked locking
        # (ThreadSafe::Cache's lookups have volatile semantics)
        @reserved_connections[current_connection_id] || synchronize do
          @reserved_connections[current_connection_id] ||= checkout
        end
      end

lib/active_record/connection_adapters/abstract/connection_pool.rb#L349-L355

ちなみに #checkout は、connection pool に connection があれば取り出し、なければ新しい connection を作るメソッド。 新しい connection を作る処理をたどっていくと最終的に mysql2_connection などの adapter の connection メソッドにいきつく。

      def checkout
        synchronize do
          conn = acquire_connection
          conn.lease
          checkout_and_verify(conn)
        end
      end

で、その connection メソッドがいつ呼ばれるかというと、クエリを投げるときに呼ばれる。

lib/active_record/querying.rb#L37-L48

    def find_by_sql(sql, binds = [])
      result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
      ....
    end

永続接続をやめる方法

  1. アプリコードで with_connection を利用する
  2. クエリ毎に connection を貼る
  3. リクエスト毎に connection を貼る 

(1) アプリコードで with_connection を利用する

#with_connection というメソッドがあるので、それを使って接続を open しては close するようにアプリケーションコードを書くことができる。

ActiveRecord::Base.connection_pool.with_connection do
  Post.create(body: 'foo')
end

(2) クエリ毎に connection を貼る

#connection を呼ぶたびに新しく接続を open しては close すれば良い。が、close するタイミングがないな。。。

用途も思いつかないので、今回は考えないものとする。

(3) リクエスト毎に connection を貼る

Rails のリクエスト処理の最後に、プロセスが持っている connection pool の接続をすべて閉じてしまえば良い。 rails の Controller の after_action などでやっても良いが、Rack middleware にしてしまうのが、一番よさそう。 Sinatra でも使えるし。

module ActiveRecord
  module ConnectionAdapters
    class RefreshConnectionManagement
      def initialize(app)
        @app = app
      end

      def call(env)
        testing = env.key?('rack.test')

        response = @app.call(env)
        response[2] = ::Rack::BodyProxy.new(response[2]) do
          ActiveRecord::Base.clear_all_connections! unless testing
        end

        response
      rescue Exception
        ActiveRecord::Base.clear_all_connections! unless testing
        raise
      end
    end
  end
end

元々 rails がやっている ActiveRecord::ConnectionAdapters::ConnectionManagement を挿げ替える。 それのActiveRecord::Base.clear_active_connections! を ActiveRecord::Base.clear_all_connections! に置き換えただけである。

# config/application.rb
class Application < Rails::Application
  config.autoload_paths += %W(#{config.root}/lib)
  config.middleware.swap ActiveRecord::ConnectionAdapters::ConnectionManagement,
    "ActiveRecord::ConnectionAdapters::RefreshConnectionManagement"
end

サンプルの rails アプリを作って、mysql に接続、show processlist; で connection が残っていないことを確認した。

補足:switch_point のような別DBに connection を貼る ActiveRecord の拡張 gem を使っている場合でも、すべての connection がこれで削除される。


おわりに

「(3) リクエスト毎に connection を貼る」の方針でやる予定。
=> gem 作った。https://github.com/sonots/activerecord-refresh_connection

[Ruby] 動的に refine するクラスを指定したい

先日 perl の SQL::Maker を ruby に移植したのだけれど、そこでは perl の EXPORT を ruby の include で代用したと書いた。

まぁ、それはそれでいいんだけど、

class Hoge
  include SQL::Maker::Helper
  def hoge
    sql_raw('*')
  end
end

とした場合に、Hoge.new.hoge の中で sql_raw が呼べるのはいいんだけど、 Hoge.new.sql_raw('*') のようにもメソッドが呼べてしまうのがなんか嫌だなぁと思った。 (正確には private にしているのでコレでは呼べないのだが、send(:sql_raw) とでもすれば呼べてしまう)。

なので、実際に使うファイルスコープだけに制限したいよね、と思って、ファイルスコープと言えば、ruby 2.1 には refinement があるのでそれを使って実現してみようと思った。

refinement は普通は例えば、

module モジュール
  refine クラス do
    def method1
    end
    def method2
    end
  end
end
class Hoge
  using モジュール # 「クラス」がこのファイルでのみ拡張される
end

のようにして、拡張したいクラスを refine の引数に指定して使うのだけど、今回は拡張したいクラスが

class Hoge #<= こいつ

みたいな任意のクラスなのでちょっと工夫する必要がある。モジュール定義を

module SQL::Maker::Helper
  def self.included(klass)
    refine klass do
      def method1
      end
      def method2
      end
    end
    # klass.__send__(:using, SQL::Maker::Helper) # これダメだった...メソッドの中で呼ぶなと怒られた
  end
end

こんなかんじにして、利用するクラスのほうで

class Hoge
  include SQL::Maker::Helper
  using SQL::Maker::Helper
  def hoge
    sql_raw('*')
  end
end

のようにしたら、

Hoge.new.hoge #=> 中では sql_raw が使える
Hoge.new.sql_raw('*') #=> undefined method !!!

となっていい感じになった。けど、include と using 二回呼んでるのがなぁ...

あったらうれしい

def self.included(klass) の using 版 def self.usinged(klass) (名前が微妙 ^^;) みたいな using をフックするメソッドがあると、 using 1回で実現できるようになるのでよさそう。

おわりに

なにか良いやり方があったら教えてください!^^

A Ruby and Fluentd committer working at DeNA. 記事本文および記事中のコード片は引用および特記あるものを除いてすべて修正BSDライセンスとします。 #ruby #fluentd #growthforecast #haikanko #yohoushi #specinfra #serverspec #focuslight
はてぶ人気エントリー