ActiveRecordHandlerSocketなんていうgemを作っています。

業務的に必要に迫られて、memcachedとかRedisとかいろいろ触ってたんですが、HandlerSocketに行き着きました。

んで、せっかくActiveRecordRDBMySQL使ってて、HandlerSocketも使うんなら、うまくActiveRecord的な感じでHandlerSocketを使えたらいいよね、見たいな。そんなノリ。

ちなみにまだ作り始め(1週間も経ってない。。。)で、業務側の環境に合わせているので、依存関係のgemはものすごく古いバージョンになってたりします。

やっとこせ、という感じで作ったのですが、アドバイスやらご指摘やらいただけたら超嬉しいです。

(早速、修正中です。)

何はともあれ、HandlerSocketなんてPluginを作られている DeNA/樋口さん や rubyインターフェースのgemを作られてる miyucyさんに感謝です。(どなたにも面識がないのですが。。。)

自分の初gemだったりするので、まずはプレゼン。

ActiveRecordHandlerSocket

rubygems.org | source code

ActiveRecordのインターフェースに近い形で、既存のモデルにHandlerSocketのインターフェースを組み込みます。

ファーストリリースバージョンの0.0.1では、insert/delete系は未サポートで、readonlyになっています。

前提

mysqlにhandlersocket pluginが組み込まれている状態からスタートします。

ActiveRecord、と言っておきつつ、基本的に、Railsのプロジェクトで使うことを想定しています。

下ごしらえ 1 gemのインストール

依存関係はActiveRecordhandlersocket gem

handlersocket gemは、ネイティブビルドですので、うまくインストールしてください。

あとは、ActiveRecordHandlerSocketをインストールして下さい。

下ごしらえ 2 Railsの環境設定

handlersocketへの接続のための設定をします。

データベースの設定を書いているYAMLファイル(config/database.yml)に、以下のような内容を環境別に追記します。

development_hs_read:
  host:     localhost
  port:     9998
  database: your_dev_database

test_hs_read:
  host:     localhost
  port:     9998
  database: your_test_database

production_hs_read:
  host:     localhost
  port:     9998
  database: your_database

データベースは通常ActiveRecordから接続しているデータベースを活用できます。

ホストとポート番号は、環境依存ですので、ご確認を。

ARへの組み込み

今回のActiveRecordHandlerSocketですが、暗黙的な拡張だとわかりづらさがまずところがあるため、明示的な宣言風の記述が必要です。

  • わかりづらい点は、、、

    • MySQLのインデックスに対して、findメソッドが増えるイメージ(インデックスに接続する)。
    • selectされるカラムも、事前に登録が必要。
  • さらに、、、

    • インデックスとの接続は意外とコストで、毎回接続しているとHandlerSocketを使っているメリットが無くなる。

ではサンプルコード。

require 'active_record_handlersocket'

class User < ActiveRecord::Base
  handlersocket :id, "PRIMARY", %W[id name age]
end

ActiveRecord::Base.handlersocketが組み込みのためのインターフェースです。

ActiveRecord::Base.handlersocket( key, index_name, fields )
  • key: このインデックスの割当につける名前。handlersocket経由のfindに対して命名規約的に使用する。
    • 別クラスで同名のkeyを指定しても上書きはされないが、同一クラスで同名のkeyを指定すると上書きされる。
  • index_name: テーブルに存在するインデックスの名前。PRIMARYインデックスを含む。
    • 現行のHandlerSocketはSQLをサポートしているわけではないので、重複のないインデックスを使うと、扱う側もわかりやすい。
  • fields: selectするカラムを配列で指定。
    • 自由指定。

find

さすがに、findを拡張するとややこしくなりそうだったので、hsというプレフィックスを使っています。

ActiveRecord::Base.hsfind_by_{key}(find_key, options = {})
#=> AR object or nil

# or

ActiveRecord::Base.hsfind_multi_by_{key}(*find_keys, options = {})
#=> Array

ActiveRecord::Base.handlersocketの第1引数に渡したkeyを利用してコールします。

例えば、、、

User.hsfind_by_id(1)
#=> #<User id: 1, ... >
User.hsfind_multi_by_id(1, 2)
#=> [#<User id: 1, ...>, #<User id: 2, ...>]

optionsには、2種類のオプションパラメータを指定可能です。

  • :operator: レコードを見つけるときの比較演算に使用する演算子を指定します。デフォルトは、=
    • > とか。
  • :limit: hsfind_multi_by_...の時かつ、:operatorで以上や以下などを使っている場合に有効。
    • 1つ1つのfind_keysに対してのlimitであるため、取得するレコード数の上限にはなりません。(次までにいい名前に変えておきます。)

※0.0.2以降は、:each_limitになる予定です。

benchmark・その他

1000件程度のデータのうち100件にランダムアクセスを100000回繰り返した時。MySQLサーバーはlocalhostで立ち上がっている。ロジックはRailsに依存。

                       user     system      total        real
InnoDB            19.120000   5.420000  24.540000 ( 29.167859)
HandlerSocket      5.700000   2.610000   8.310000 ( 13.612221)

(もうちょっと改善したい。。。)

そもそもでMySQL自体とても速いんですが、とりあえず普通にMySQLにアクセスするよりは早くなります。という話で、、、

HandlerSocketの利点は、、、(別のところでベンチマークをとっているのですが、今回は割愛。)

  • 大量のコネクションを張っても劣化しにくい。
  • データを増やしても劣化しにくい。

みたいなところで、memcachedなどと比べた時にパフォーマンスの高さを見せてくれました。

Railsで1サーバー運用、くらいでは全然生きてこない(むしろ手間なだけ)ですが、複数台のサーバーで一気にアクセスさせるといいかんじにメリットが出てきます。

実践ハイパフォーマンスMySQL 第3版

実践ハイパフォーマンスMySQL 第3版