ActiveRecord使ってMySQLの日付関数呼び出す時の注意

AWS+RDSでRailsを使ったりしてると、RDSのタイムゾーンUTCから変更できない関係で、けっこう時間の扱いに悩みます。

大抵の場合、application.rbに

config.time_zone = 'Tokyo'

とかやってお茶を濁すわけですが。

config.time_zoneはあくまでアプリケーション側の時間の扱いなので、DBのタイムゾーンを帰るわけではありません。

なので、date関数とか使うとUTC扱いになって困っちゃったりするわけです。

そういう場合はCONVERT_TZで無理くりJSTにします。

User.where("DATE(CONVERT_TZ(regist_day,\"+00:00\",\"+09:00\"))

MySQL5.6のSQLモード

ついに出ましたね、MySQL5.6!

my.cnfの位置が/usr/my.cnfに変わったとか標準動作がinnodb_per_tableになったとか色々ありますが、とりあえずデフォルトのmy.cnf内に

sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

という記述があって、詳細はこちらを見ていただくとして、今まではsql_mode=TRADITIONALだったのですが、このまま起動するとSTRICT_TRANS_TABLESモードで起動してしまいます。

TRADITIONALはカラムレングスよりも長い文字列のレコードがインサートされた場合、カラムに入らない部分を切り捨てて格納し、エラーメッセージを返すんですが、STRICT_TRANS_TABLESだとインサートエラーを返します。

WEBアプリでレングスチェックをしているのでMySQL側でチェックする必要ないよ、という人はTRADITIONALモードに変更しましょう。

TRADITIONALモードで起動した場合、余計チェックが厳しくなりました><
5.6はTRADITIONALモードも変更が入ってるらしい。。。

mysql> select @@global.sql_mode;

  1. ------------------------------------------------------------------------------------------------------------------------------------------------------+
@@global.sql_mode
  1. ------------------------------------------------------------------------------------------------------------------------------------------------------+
STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
  1. ------------------------------------------------------------------------------------------------------------------------------------------------------+

5.5時代と同じように使う場合はsql_mode自体をコメントアウトして起動しましょう

nginxでDOCOMOのguidを取得する

ガラケーのサービスでnginxをあまり使わないからなのか情報が見つからなかったのでメモ

nginx.confで

# アンダースコアを有効に
underscores_in_headers on;
# ログフォーマットに追記
log_format custom '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_time "$http_x_dcmguid"';

HTTP_X_DCMGUIDはアンダースコアを含むのでunderscores_in_headersをONしないと取れませんでした。

Backbone.jsで外部ファイルをテンプレートに指定する方法

このエントリはBackbone.js Advent Calendar11日目です。

皆様素晴らしいエントリを書かれている中、何か初歩的なエントリでスミマセン。

Backbone.jsで作られたサイトを見ると、よく_.templateを使ってHTMLを呼び出す記述を見かけますね。

よく知られているように、_.templateには文字列を渡す方法とtext/templateセクションをセレクタで渡す方法があります。

文字列で渡す
_.template('<div><%= name %></div>',name)

テンプレートセクションをセレクタで指定する
_.template($('#item-template').html(),name)

item-templateのテンプレートセクション
<script type="text/template" id="item-template">
    <div class="view">
<input class="edit" type="text" value="<%= name %>" />
    </div>
</script>

テンプレートセクションの記述はunderscore.jsの呼び出し元HTMLである必要があります。

ところでサーバサイドのMVCフレームワークに慣れ親しんだ人はこれがちょっと違和感で、できればテンプレートを分離して別ファイルにしてviewsフォルダとかにまとめたい欲望に駆れたことはないでしょうか。
しかしながら御存知の通りクライアントサイドJavascriptは表示しているHTMLのDOMに対する操作を主に行うためのもので、別ディレクトリの要素については全く関与しません(当たり前)。

でもデザイナーと分業したり、テンプレートセクションが増えすぎてソースが汚くなるのは嫌だから分離させたら何かと便利じゃない?、という人のために、viewsフォルダを分けて管理する方法について、敢えて考えてみたいと思います。

まずテンプレートを格納するためのフォルダを作成します。フォルダはAJAXでJSからアクセスできる必要があり、今回は/viewsとします。

index.htmlはbackbone.js, underscore.jsの呼び出し元
index.html
views/ここにテンプレートファイルを入れる

次に指定されたファイル名でviewsフォルダからHTMLを取得する関数を作成します。名前は何でもいいので今回は_renderという名前にしました。
2度めからのアクセスはキャッシュから参照させるため、tmpl_cacheというキャッシュ格納用変数も用意します。

var tmpl_cache;
function _render(tmpl_name) {
    if ( !tmpl_cache ) {
        tmpl_cache = {};
    }
    if ( ! tmpl_cache[tmpl_name] ) {
        var tmpl_url = 'views/' + tmpl_name + '.html';
        var tmpl_string;
        $.ajax({
            url: tmpl_url,
            method: 'GET',
            async: false,
            dataType: "html",
            success: function(data) {
                tmpl_string = data;
            }
        });
        tmpl_cache[tmpl_name] = tmpl_string;
    }
    return tmpl_cache[tmpl_name];
}

_renderは拡張子を省略したファイル名を渡すと、viewsディレクトリからHTMLを取得して文字列を返す関数です。
一度呼び出したテンプレートはキャッシュ変数に保持して二回目のアクセスはAJAX通信が発生しないようにします。またacyncをfalseにして強制的に同期処理させます。

View側からは_.templateの第一引数に_renderを指定します。

_.template(_render("item-template"),name)

viewsにitem-template.htmlを作成します。

views/item-template.html
<div class="view">
    <input class="edit" type="text" value="<%= name %>" />
</div>

_renderがAJAX通信して取得した結果を、HTMLとして_.templateに渡す感じになります。これで別ディレクトリに作成したテンプレートを_.templateに渡すことができるようになりました。

しかしながらこの方法、テンプレート取得のたびに無駄なAJAX通信が発生するため、WEBサイトでやる意味はほとんどないと思います。
ですのでPhoneGap+Backbone.jsでアプリを作る時などローカル限定での呼び出しに留めておくのが良いと思います。

というわけで、Backbone.jsほとんど関係ないエントリになってしまいましたが、以上よろしくお願い致します。

therubyracerでインストールエラー

Railsでプロジェクト作ろうと思ったらなぜかbundle installでエラーが発生する。

/usr/local/rvm/rubies/ruby-1.9.3-p194/bin/ruby extconf.rb
checking for main() in -lpthread... yes
checking for v8.h... no

therubyracerインストール中にlibv8が見つからずエラーになってるみたい。
とりあえずlibv8を入れなおす。

gem uninstall libv8
gem install libv8

単体でtherubyracerをインストールする

gem install therubyracer

linking shared-object v8/init.so
/usr/bin/ld:/usr/local/rvm/gems/ruby-1.9.3-p194/gems/libv8-3.11.8.3-x86_64-linux/vendor/v8/out/x64.release/obj.target/tools/gyp/libv8_base.a: file format not recognized; treating as linker script
/usr/bin/ld:/usr/local/rvm/gems/ruby-1.9.3-p194/gems/libv8-3.11.8.3-x86_64-linux/vendor/v8/out/x64.release/obj.target/tools/gyp/libv8_base.a:1: syntax error

なぜかsyntax errorで怒られる。

この間までは動いていたので、どうやらtherubyracerのバージョンが上がったのが原因くさい。
rubygems.orgで確認。


all versions of therubyracer

101 versions since December 21, 2009

0.11.0 December 4, 2012
0.11.0beta8 August 13, 2012 x86_64-linux

日付的に0.11.0のGAと相性が悪いっぽいので、beta8を指定してbundle install

gem 'therubyracer', '0.11.0beta8'

Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

うまくいった!

MySQL-MHAを試してみたよ。

新しく購入したDELLのサーバがネットワークエラーで不通になっちゃう事象が半年に1回くらい起きてて、それが運悪くマスタDBだったので、そろそろ本気で対策をしないといけないかしらんと思ってMySQL-MHAについて調べてみました。

MySQL-MHAについてはpublic keyの記事を参考に。

作業に入る前に本家のプロジェクトと、試した方々のブログを拝見しました。
・本家
http://code.google.com/p/mysql-master-ha/
・試した方々のブログ
http://myhome.munetika.mydns.jp/ossdbwiki/index.php/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8
http://6pongi.wordpress.com/2011/11/14/mysql-mha/
http://d.hatena.ne.jp/ke-16/20110912/1315824419
http://d.hatena.ne.jp/kaze-kaoru/20110830/1314677120

先ず最初に疑問に思ったのが、通常WEBアプリケーションというのはアプリケーションサーバとDBサーバが物理的に分かれていると思うんですが、スレーブがマスターに昇格した場合、アプリケーション側からどう見えるのか?ということでしたが、それについては本家にも書いてありますが、こちらがわかりやすくQAにまとめてくれていました。
簡単に言うと、MHAはマスタ昇格のタイミングで任意のスクリプトを走らせることができるので、IP付け替え(仮想NICだったりコールドスタンバイのNIC起動だったり)はそのタイミングでできるそうです。

次に疑問だったのが、フェイルオーバー後に旧マスタを復旧させた場合、旧マスタの扱いはどうなるのかということでした。

これについてあまり言及してるブログがなかったので、せっかくの機会なので自分で試してみることにしました。
ローカルのPC2台にCentOSを入れ、1台がマスタノード兼マネージャ、2台めをスレーブとしました。
MySQLのバージョンはマスタが若干古くて5.5.11、スレーブが5.5.25aでした。MySQLは1つ上のメジャーバージョンまでレプリケーションの互換性をみてくれるので問題無いと判断しました。

インストール方法などはこちらを参考に。特につまづくところもなくセットアップまで完了しました。

Checking slave recovery environment settings..
Opening /var/lib/mysql/relay-log.info ... ok.
Relay log found at /var/lib/mysql, up to localhost-relay-bin.000002
Temporary relay log file is /var/lib/mysql/localhost-relay-bin.000002
Testing mysql connection and privileges.. done.
Testing mysqlbinlog output.. done.
Cleaning up test file(s).. done.
Thu Nov 29 17:05:22 2012 - [info] Slaves settings check done.
Thu Nov 29 17:05:22 2012 - [info]

マスタ側のMySQLを落とす。

# /etc/rc.d/init.d/mysql stop

マネージャーがマスタの異常を検出

Thu Nov 29 17:07:47 2012 - [info] HealthCheck: SSH to 192.168.0.187 is reachable.
Thu Nov 29 17:07:49 2012 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.0.187' (111))
Thu Nov 29 17:07:49 2012 - [warning] Connection failed 1 time(s)..
Thu Nov 29 17:07:52 2012 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.0.187' (111))
Thu Nov 29 17:07:52 2012 - [warning] Connection failed 2 time(s)..
Thu Nov 29 17:07:55 2012 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.0.187' (111))
Thu Nov 29 17:07:55 2012 - [warning] Connection failed 3 time(s)..
Thu Nov 29 17:07:55 2012 - [warning] Master is not reachable from health checker!

落ちたかどうかの判定は設定ファイルで変更できるようです。

ヘルスチェックを行った後、設定ファイルにしたがってフェイルオーバーするよ!というログが出てフェイルオーバーが始まります。

Thu Nov 29 17:07:55 2012 - [warning] Master is not reachable from health checker!
Thu Nov 29 17:07:55 2012 - [warning] Master 192.168.0.187(192.168.0.187:3306) is not reachable!
Thu Nov 29 17:07:55 2012 - [warning] SSH is reachable.
Thu Nov 29 17:07:55 2012 - [info] Connecting to a master server failed. Reading configuration file /etc/masterha_default.cnf and /etc/app1.cnf again, and trying to connect to all servers to check server status..
Thu Nov 29 17:07:55 2012 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Thu Nov 29 17:07:55 2012 - [info] Reading application default configurations from /etc/app1.cnf..
Thu Nov 29 17:07:55 2012 - [info] Reading server configurations from /etc/app1.cnf..
Thu Nov 29 17:07:56 2012 - [info] Dead Servers:
Thu Nov 29 17:07:56 2012 - [info] 192.168.0.187(192.168.0.187:3306)
Thu Nov 29 17:07:56 2012 - [info] Alive Servers:
Thu Nov 29 17:07:56 2012 - [info] 192.168.0.232(192.168.0.232:3306)
Thu Nov 29 17:07:56 2012 - [info] Alive Slaves:
Thu Nov 29 17:07:56 2012 - [info] 192.168.0.232(192.168.0.232:3306) Version=5.5.25a-log (oldest major version between slaves) log-bin:enabled
Thu Nov 29 17:07:56 2012 - [info] Replicating from 192.168.0.187(192.168.0.187:3306)
Thu Nov 29 17:07:56 2012 - [info] Checking slave configurations..
Thu Nov 29 17:07:56 2012 - [info] read_only=1 is not set on slave 192.168.0.232(192.168.0.232:3306).
Thu Nov 29 17:07:56 2012 - [warning] relay_log_purge=0 is not set on slave 192.168.0.232(192.168.0.232:3306).
Thu Nov 29 17:07:56 2012 - [info] Checking replication filtering settings..
Thu Nov 29 17:07:56 2012 - [info] Replication filtering check ok.
Thu Nov 29 17:07:56 2012 - [info] Master is down!
Thu Nov 29 17:07:56 2012 - [info] Terminating monitoring script.
Thu Nov 29 17:07:56 2012 - [info] Got exit code 20 (Master dead).
Thu Nov 29 17:07:56 2012 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Thu Nov 29 17:07:56 2012 - [info] Reading application default configurations from /etc/app1.cnf..
Thu Nov 29 17:07:56 2012 - [info] Reading server configurations from /etc/app1.cnf..
Thu Nov 29 17:07:56 2012 - [info] MHA::MasterFailover version 0.53.
Thu Nov 29 17:07:56 2012 - [info] Starting master failover.

ぐりぐりと進んでいきます。

Thu Nov 29 17:07:56 2012 - [info] * Phase 1: Configuration Check Phase..
Thu Nov 29 17:07:56 2012 - [info]
[中略]
Thu Nov 29 17:07:56 2012 - [info] ** Phase 1: Configuration Check Phase completed.
Thu Nov 29 17:07:56 2012 - [info]
[中略]
Thu Nov 29 17:07:56 2012 - [info] * Phase 2: Dead Master Shutdown Phase..
Thu Nov 29 17:07:56 2012 - [info]
[中略]
Thu Nov 29 17:07:56 2012 - [info] * Phase 2: Dead Master Shutdown Phase completed.
Thu Nov 29 17:07:56 2012 - [info]
[中略]
Thu Nov 29 17:07:56 2012 - [info] * Phase 3: Master Recovery Phase..
Thu Nov 29 17:07:56 2012 - [info]
[中略]
Thu Nov 29 17:07:56 2012 - [info] * Phase 3.1: Getting Latest Slaves Phase..
Thu Nov 29 17:07:56 2012 - [info]
[中略]
Thu Nov 29 17:07:56 2012 - [info] * Phase 3.2: Saving Dead Master's Binlog Phase..
Thu Nov 29 17:07:56 2012 - [info]
[後略]

終わった!

----- Failover Report -----

app1: MySQL Master failover 192.168.0.187 to 192.168.0.232 succeeded

Master 192.168.0.187 is down!

Check MHA Manager logs at hogehoge for details.

Started automated(non-interactive) failover.
The latest slave 192.168.0.232(192.168.0.232:3306) has all relay logs for recovery.
Selected 192.168.0.232 as a new master.
192.168.0.232: OK: Applying all logs succeeded.
Generating relay diff files from the latest slave succeeded.
192.168.0.232: Resetting slave info succeeded.

全ての処理をやり終えるとマネージャーは停止します。。

スレーブが昇格したのを確認して、マスタのMySQLを再度起動します。

/etc/rc.d/init.d/mysql start

起動しましたが自動的にスレーブになったりするわけではありませんでした。
やってみて思ったんですが、異常で死んだスレーブが勝手にフェイルオーバーしてマスタに戻るとデータの欠損などのトラブルの元になりますからね。。。

とはいえ、調べてみて特に異常ないことがわかった旧マスタをまたスレーブとして登録するためにはデータのリストアが必要で、データ量が多ければ多いほどこの作業は時間がかかってしまいます。
そういうところも見越してか、マネージャーの実行結果には昇格時のバイナリログのポジション情報が出力されています。

Thu Nov 29 17:07:58 2012 - [info] All relay logs were successfully applied.
Thu Nov 29 17:07:58 2012 - [info] Getting new master's binlog name and position..
Thu Nov 29 17:07:58 2012 - [info] localhost-bin.000003:787
Thu Nov 29 17:07:58 2012 - [info] All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='192.168.0.232', MASTER_PORT=3306, MASTER_LOG_FILE='localhost-bin.000003', MASTER_LOG_POS=787, MASTER_USER='repl', MASTER_PASSWORD='xxx';
Thu Nov 29 17:07:58 2012 - [warning] master_ip_failover_script is not set. Skipping taking over new master ip address.

データを確認して問題無いと判断したら、旧マスタにchange masterしてスレーブとして加えることもできます。すごいなあ。

ちなみに起動中にマネージャーを強制停止し、再実行しても問題ありませんでした。どうやらマネージャーは起動した時点からのログの整合性を取っているようです。
なので仮にマネージャーが落ちても他のサーバを代用できそうです(あくまで推測なので実際に作業される方は十分にテストしてくだい)。

ざっと使ってみた感じですが、MySQLサーバ側にほとんど影響を与えないよう作られているように感じました。
マネージャーは、どのタイミングで登録してもサーバに影響しないので、稼働中のサービスにも導入できそうでした。
HA製品というと導入や運用が難しく、専用のサポートが必要なイメージがありますが、MySQL-MHAはシンプルに極力ダウンタイムを発生させずフェイルオーバーを実現させることに重点を置いている感じがし、とても魅力的なOSSですので、本番環境にも導入を進めたいと思います。