activerecordの関連するモデルをまとめて取得するメソッドのincludesが多段の関連モデルでも使えた

例えば、以下のような3層にまたがる関連モデルがあるとすると、

class Team
  has_many :members
end

class Member
  belongs_to :team
  has_many :tasks
end

class Task
  belongs_to :member
end

この場合、Taskのインスタンスオブジェクトから、
関連するTeamオブジェクトの情報を呼び出す場合、

task = Task.last
task.member.team.name
=> "A team"

こうなって、SQL的には、

SELECT `tasks`.* FROM `tasks` LIMIT 1
SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

こうなります。
一見問題ないようにも見えますが、

仮にTaskオブジェクトを

Task.all

のように取得してeach等でループする場合、
Taskオブジェクトの数だけ、membersテーブルとteamsテーブルにアクセスしてしまいます。

tasks = Task.limit 10
tasks.each {|task| p task.member.team.name }
SELECT `tasks`.* FROM `tasks` LIMIT 10
SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 3 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 3 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 2 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 2 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

そこで登場するのが関連するモデルをまとめて取得するメソッドのincludesです。
includesメソッドを多段の関連モデルで呼び出す場合は、以下のように記述します。

tasks = Task.limit(10).includes(:member => :team)
tasks.each {|task| p task.member.team.name }
SELECT `tasks`.* FROM `tasks` LIMIT 10
SELECT `members`.* FROM `members` WHERE `members`.`id` IN (6, 4, 3, 2)
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` IN (5, 4, 3, 2)

MariaDB v10.0.3とMroonga v4.0.5インストール時にmakeでERRORになった話

MariaDBって

MySQLから派生したRDBMSのひとつ。MySQLのオリジナルコードの作成者であるモンティーさんがオラクル管理の現在のMySQLコードをフォークして始めた。

MySQLに比べて各処理のアーキテクチャがより高速に変更されていたり、Thread_poolや並列レプリケーションなどのRDBMSにしては前衛的な機能が盛り込まれていたりする。

大手ウェブサービスも移行していたり、各LinuxディストリビューションのデータベースもMariaDBに変更していたり、RDBMS界隈でわりとホットな話題。

Mroongaって

MySQLMariaDBでも)で全文検索を実現するストレージエンジンのひとつ。
全文検索エンジンであるGroongaをベースとしており、MySQLプラグインとしてインストールできるので設置が容易。

インストール

MariaDBのインストール

tarfileや各種パッケージからインストールする。特につまずくポイントはなし。
https://downloads.mariadb.org/mariadb/10.0.13/

Groonga install

rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
yum makecache
yum install -y groonga
yum install -y groonga-devel

yum install -y groonga-normalizer-mysql

※このときはdevelも必要でした。

Mroongaのインストール

上記のMariaDBを任意のディレクトリに配置するなどしていたので、ソースからビルド。
http://mroonga.org/ja/docs/install/others.html#build-from-source

cd /usr/local/src/
wget http://packages.groonga.org/source/mroonga/mroonga-4.05.tar.gz
wget https://downloads.mariadb.org/interstitial/mariadb-10.0.13/source/mariadb-10.0.13.tar.gz/from/http%3A//ftp.yz.yamagata-u.ac.jp/pub/dbms/mariadb
tar xvzf mroonga-4.05.tar.gz 
tar zxvf mariadb-10.0.13.tar.gz 
cd mroonga-4.05

./configure \
--with-mysql-source=/usr/local/src/mariadb-10.0.13 \
--with-mysql-config=/MARIA/product/bin/mysql_config
  • --with-mysql-source:インストール対象のソースのパスを記載。今回は先ほどダウンロードしたMariaDB
  • --with-mysql-config:実際にインストールされたバイナリーディレクトリにあるmysql_configファイル。

そんで、make。

make

このとき以下のようなエラーが出た。

/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:29:57: error: pcre.h: No such file or directory
In file included from /usr/local/src/mariadb-10.0.13/sql/item.h:3745,
                 from /usr/local/src/mariadb-10.0.13/sql/sql_lex.h:26,
                 from /usr/local/src/mariadb-10.0.13/sql/sql_class.h:474,
                 from ../mrn_mysql.h:51,
                 from mrn_path_mapper.cpp:30:
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1494: error: ISO C++ forbids declaration of 'pcre' with no type
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1494: error: expected ';' before '*' token
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In constructor 'Regexp_processor_pcre::Regexp_processor_pcre()':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1510: error: class 'Regexp_processor_pcre' does not have any field named 'm_pcre'
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In member function 'void Regexp_processor_pcre::init(const CHARSET_INFO*, int, uint)':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1521: error: 'PCRE_UTF8' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1521: error: 'PCRE_UCP' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1523: error: 'PCRE_CASELESS' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In member function 'void Regexp_processor_pcre::cleanup()':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1559: error: 'm_pcre' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1561: error: 'pcre_free' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In member function 'bool Regexp_processor_pcre::is_compiled() const':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1566: error: 'm_pcre' was not declared in this scope
make[2]: *** [mrn_path_mapper.lo] Error 1
make[2]: Leaving directory `/usr/local/src/mroonga-4.05/lib'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory `/usr/local/src/mroonga-4.05'
make: *** [all] Error 2

/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:29にて、pcre.hというヘッダファイルが見つからないようだ。
調べてみると、

ls -lh /usr/local/src/mariadb-10.0.13/pcre/pcre.h.in 
-rw-r--r-- 1 1001 1001 32K Aug  8 18:13 /usr/local/src/mariadb-10.0.13/pcre/pcre.h.in

.in付いてる。
仕方がないので、正しいかどうかは分かりませんが、

vi /usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h

にて、

#include "pcre.h"                 /* pcre header file */

#include "pcre.h.in"                 /* pcre header file */

に変更。

make

うまくいった!

あとは、make installして、設定用のSQLを実行するだけ。

make install
mysql -u root < /usr/local/share/mroonga/install.sql

RDSの"リードレプリカのマスター昇格機能"を使ってDDLオペレーションを実行する方法

AWSのRDSに、
ついにリードレプリカをマスターに昇格する機能が追加されました。

【AWS発表】Amazon RDS for MySQL - リードレプリカのマスター昇格機能を追加!

さて何故(なにゆえ)にこのような機能があるのかというと、
以下3つのケースが同記事にて紹介されています。

  • DDLオペレーションの実行
  • テーブルのシャーディング
  • 障害回復機能を実装する

"障害回復機能を実装する"ケースは、マスターで障害が発生した際に、
リードレプリカをマスターに切り替えることで復旧させる方法です。

既存のデータ復旧機能である"Multi-AZ 配備"に比べて良い点は、
非同期なので書き込みにおけるパフォーマンス劣化が防げたり、
ホットスタンバイとして寝かしておくのではなく
リードレプリカとして読み込み用として利用しておけたりする事でしょうか。

もちろん、同記事にあるようにこれは"非同期"のため、
障害時にデータの整合性が失われる可能性があるので、注意が必要です。

"テーブルのシャーディング"は、
同時多発的にマスター用途のインスタンスを作成するってところですかね。

さて肝心の"DDLオペレーションの実行"です。

そもそも何が問題なの?というと、

サービスやアプリケーションで利用中のテーブルに対して
ADD COLUMNやADD INDEX等のテーブルスキーマの変更を行うと、
該当のテーブルはロックされるため後続のクエリ全てがwait状態になること、
また実行中はある程度のマシンリソースを使うことにより、
稼働に影響を及ぼす可能性があるためです。

特にレコード数の多い大きなテーブルでは顕著に現れます。
サイズ、マシンリソースによってはひとつの変更に
数十分、数時間かかる可能性もあるでしょう。

そこで昇格機能を使うことで、
事前に伝搬中のリードレプリカ上でスキーマ変更を行い、
更新を一時的に止め、リードレプリカをマスターに昇格させ、
アプリケーションの向き先を昇格したマスターへ切り替える、
そんなことが可能なのです。

これなら昇格にかかる時間(数分)と
アプリケーションの向き先変更時間だけで、
数時間もかかる作業を行うことができるのです。

まず下準備です。

RDSのコンソール画面のNavigationにあるDB Parameter Groupsから、
新しいパラメータセットを作ります。

f:id:kazukiyunoue:20130505141638p:plain

リードレプリカに対して変更処理を行うため、
デフォルトのパラメータで設定されている
書き込み制限が外れたセットが必要なためです。

f:id:kazukiyunoue:20130505143203p:plain

適当な名前を付けて作成しておきます。
Edit Parametersから必要な値の変更をします。項目はread_onlyです。

元々は自動でリードレプリカの場合は読み込み専用になるようになっています。

f:id:kazukiyunoue:20130505141701p:plain

これを0にしておきます。

f:id:kazukiyunoue:20130505141724p:plain

インスタンス一覧に戻って、
Instance Actionsからリードレプリカを作成します。

f:id:kazukiyunoue:20130505142751p:plain

作成できたら、リードレプリカを選択した上でActionsからModifyを選択し、
パラメータの設定セットを先ほど作った書き込みが可能な
DDLオペレーション用のものに変えます。

f:id:kazukiyunoue:20130505143002p:plain

再起動が完了したら、
早速リードレプリカに対してスキーマ変更を実施しましょう。
サービスで利用していないのでどんな負荷がかかろうが問題ないですね。

変更中は伝搬が遅延する可能性がありますが、
変更中も変更後も問題なく伝搬は続行しているはずです。

これで準備は整いました。

あとは更新を一時的に停止し、スキーマ変更済みのリードレプリカを
マスターに昇格しましょう。

f:id:kazukiyunoue:20130505143040p:plain

その後アプリケーションの向き先を新マスターに切り替えるだけです。
あっという間にスキーマ変更済みのDBへと切り替えることができました。

実は書き込み可能なリードレプリカにしているのでマスターに昇格しなくても、
向き先を変更してそのまま利用することが可能です。
しかしバックアップ等はマスターにしかない機能なので、
必ずこのタイミングで昇格した方が無難ですね。

今の一連の作業はもちろんAPI経由でも実施可能なので、
Rails等のマイグレーションのタスクに組み込んでしまえば、

rake db:migrate

こんなコマンド一発で
スキーマ変更からDSN切り替えまでやれちゃうかもですねぇ、、

末恐ろしきRDS、、!

AWSのRDSは外部のMySQLとレプリケーションできない -マスターユーザの権限を見て分かったこと-

※ 2013/09/08 added
ついにレプリケーションの機能が付いたようです!
hereAmazon Web Services ブログ: 【AWS発表】 オンプレミスのMySQLデータをAmazon RDSへ移行する(その逆も)


RDSのインスタンスを新規に作成する際に
合わせて作成するマスターユーザの権限を見てみた。

マスターユーザというのは、

f:id:kazukiyunoue:20130414164558p:plain

ココでMaster Usernameとしていれたもの。

権限が、以下のような感じ。

                  Host: %
                  User: root
              Password: *<HASHPASSWORD>
           Select_priv: Y
           Insert_priv: Y
           Update_priv: Y
           Delete_priv: Y
           Create_priv: Y
             Drop_priv: Y
           Reload_priv: Y
         Shutdown_priv: N
          Process_priv: Y
             File_priv: N
            Grant_priv: Y
       References_priv: Y
            Index_priv: Y
            Alter_priv: Y
          Show_db_priv: Y
            Super_priv: N
 Create_tmp_table_priv: Y
      Lock_tables_priv: Y
          Execute_priv: Y
       Repl_slave_priv: N
      Repl_client_priv: Y
      Create_view_priv: Y
        Show_view_priv: Y
   Create_routine_priv: Y
    Alter_routine_priv: Y
      Create_user_priv: Y
            Event_priv: Y
          Trigger_priv: Y
Create_tablespace_priv: N
              ssl_type: 
            ssl_cipher: 
           x509_issuer: 
          x509_subject: 
         max_questions: 0
           max_updates: 0
       max_connections: 0
  max_user_connections: 0
                plugin: 
 authentication_string: 

サーバそのものに影響を及ぼすようなFILE権限であったり、
SHUTDOWN権限がなく、
MySQL接続によるインスタンス停止ができなくなっていたりする。
※コンソール画面やAPIからはもちろん停止可能。

さらにSUPER権限がないため、

外部のMySQL(例えばec2上のMySQLVPC先の自前サーバ上のMySQLなど)
とのレプリケーション設定である

mysql> CHANGE MASTER TO …

上記のようなCHANGE MASTER文を発行出来ない。

つまり、外部MySQLとのスレーブとしてのRDS利用は不可能だ。

さらに、Repl_slave_privもないことから、
外部からのレプリケーション利用のためのユーザも作成できないので、

外部MySQLとのマスターとしてのRDS利用も無理。

RDSには読み出し専用のスレーブ”リードレプリカ”を作成する機能があるので、
そちらを利用しておくんなましってことだろう。

とはいえ、外部とのレプリケーションが実現出来たとしても、
サーバに入って、バイナリーログ等をいじいじいじくれないのなら、
おそらくレプリケーション運用は困難を極めること必死だ。

※ほんとはRDSへの移設にレプリケーションを利用したかったのだけどね。

ちなみにマスターユーザはCreate user権限もGrant権限もあるので
新規にユーザを作成することは可能だけど、

Grantは自身の持つ権限しか付与できないので、
上記の制約はどんな後発のユーザでも当てはまる。

**

あと全然関係ないけど、
rdsadmin@localhostというALL PRIVILEGESを有したユーザがいるのだが、
※おそらくセッション数等を取得してる監視などのためのユーザ。

なぜか、

mysql> drop user rdsadmin;
ERROR 1396 (HY000): Operation DROP USER failed for 'rdsadmin'@'%'

なぜかこの人を消し去れない。。
マスターユーザにはCreate user権限があるので可能な気がするんだけど。。

いや、もちろん消し去る必要はないんだけど。。

どうやってんのかなって。。
まあどうでもいいことなんだけど。。

OmniAuth(OAuth2.0)でproxyを使う方法

YammerのOAuthで認証するアプリが作りたいんだけど、

proxyが必要なネットワーク内でかつ、
環境変数の”http_proxy”が使えないときのメモ。

※そんなマニアックスな人が世の中にどれだけいるか甚だ疑問ですが、、

結果的に、

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :yammer , ENV["YAMMER_CONSUMER_KEY"], ENV["YAMMER_CONSUMER_SECRET"], 
           :client_options => {:connection_opts => { :proxy => "http://proxy.sample.com:80" }}
end

こんな感じでいけました。

deviseだと、

config/initializers/devise.rb

  config.omniauth :yammer, ENV["YAMMER_CONSUMER_KEY"], ENV["YAMMER_CONSUMER_SECRET"],
                  :client_options => {:connection_opts => { :proxy => "http://proxy.sample.com:80" }}

こんな感じです。

※OAuth2.0仕様です。

仕組みとしては、

各OAuthプロバイダのライブラリ(ここではomniauth-yammer)が、
omniauth-oauth2ライブラリのOAuth2クラスを継承していて、

そこからoauth2ライブラリの接続用Clientクラスを生成するのですが、
その際に引き渡すオプション名がclient_optionsとなっていて、

さらに、

そのClientクラスがhttpクライアントライブラリである
faradayのインスタンスを生成する際に引き渡すオプション名がconnection_optsという顛末でした。

とりあえず通って良かったね!

Macbook Air 2012(mid) にvim-rubyを入れた

Macbook Air 2012(mid) が手に入ったので、いつものごとくvim-rubyを入れました。

以前のポスト

例のごとく、vimrubyに対応してるか確認すると、

mba:~ kazuki.yunoue$ vi --version | grep ruby
-python3 +quickfix +reltime -rightleft +ruby/dyn +scrollbind +signs

あら、対応してる。最近から?

以前の通り、

git clone http://github.com/vim-ruby/vim-ruby.git
cd vim-ruby
git checkout vim7.3

rake package

直接ソースからgemのパッケージを作成してinstallしようとすると、

mba:vim-ruby kazuki.yunoue$ rake package
rake aborted!
ERROR: 'rake/gempackagetask' is obsolete and no longer supported. Use 'rubygems/package_task' instead.

rakeのバージョンとの親和性が良くないのか怒られる。

仕方がないので、ココのpathogen.vim方法と思わせてManuallyに、

https://github.com/vim-ruby/vim-ruby/wiki/VimRubySupportA

./bin/vim-ruby-install.rb

これで~/.vim/下に必要なファイルがコピーされるので、

vi ~/.vimrc

に、

set tabstop=2
set shiftwidth=2
set expandtab
set autoindent
set nocompatible
syntax on
filetype on
filetype indent on
filetype plugin on

とか書いてしまえば、完了だ!