MySQLのv4.0からv5.6へダンプファイルをインポートする際にTIMESTAMPがずれる場合の対処

MySQLをバージョン4.0からバージョン5.6へ移行するため、mysqldumpを使ってダンプファイルをv4.0から取得し、v5.6に投入した際、timestamp型のカラムの時間がズレる事象が発生した。

タイムゾーンの設定はどちらもJSTである。

v4.0

mysql> show variables like '%time%zone%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| timezone      | JST   |
+---------------+-------+
1 row in set (0.00 sec)

v5.6

mysql> show variables like '%time%zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.00 sec)

原因はv4.0からダンプファイルを取得する際、v5.6のmysqldumpを使用したため、"tz-utc"という

異なるタイムゾーンのサーバ間でTIMESTAMPカラムをダンプしてリロードできる

https://dev.mysql.com/doc/refman/5.6/ja/mysqldump.html#option_mysqldump_tz-utc

機能が有効に働いたのにもかかわらず、
v4.0側はこの概念がないため、

/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;

ダンプファイル内に上記のようなタイムゾーンの設定が入っているが、実際のTIMESTAMPカラムは生データのままになってしまい、v5.6側に投入すると時間がズレていたもよう。

どちらのサーバも時間設定は同じなので、mysqldumpのオプションである"tz-utc"を無効にする"skip−tz-utc"を付与することで、無事時間のズレなく投入できた。

pagerdutyで復旧通知メールによってインシデントを発生しないようにしたり自動的にインシデントをResolved(リゾルブ)に変えたりする方法

pagerdutyとは?

言わずと知れたシステムアラートの通知管理サービスです。メールやAPIサードパーティの監視システムからの信号をきっかけに、担当者に電話やSMS、メール等で優しく、時に厳しく連絡を送ってくれるものです。連絡する担当者をローテーションや順番等を細かく設定できるのも大きな特徴です。pagerduty側に送られた信号はインシデントという単位で、それぞれの状態を、"Triggered"(発砲)、"Acknowledged"(認知)、"Resolved"(解消)などのステータスで管理できます。

www.pagerduty.com

復旧通知メールを無視する方法

システムのメトリクス監視の中には、ロードアベレージやセッション数など、閾値を超えた場合発砲通知メールを送信し、その後は発砲せず、閾値を下回ったら復旧通知メールを送信して、その後は通常通り発砲するタイプのものがあります。この場合、まず復旧通知メールをpagerduty側でインシデントとして認識させないようにする必要があります。pagerdutyにはメールやAPI等で送られた信号を条件に合致した場合にインシデントを生成する、またはインシデントを解消するルール設定が可能です。Edit Serviceページにあります。

f:id:kazukiyunoue:20160115170946p:plain
f:id:kazukiyunoue:20160115171004p:plain
f:id:kazukiyunoue:20160115171013p:plain

例えば、復旧通知メールに必ず"[Resolved]"というキーワードが入る場合は、以下のような条件を設定すると、復旧通知メールを無視できます。

f:id:kazukiyunoue:20160115172819p:plain

厳密には無視をする機能ではありません。"[Resolved]"のキーワードが入った件名の文言全てと合致する発生済みインシデントをResolved状態にさせようとしていて、当然そのようなインシデントはありえないので、実質無視する形になります。

復旧通知メールでインシデントをResolvedにする方法

こうすると、監視システム側では解消となっているのにもかかわらず、pagerduty側ではまだResolvedになっていません。pagerdutyには、親切にも設定された時間を経過すると改めて通知する機能があって、深夜に障害は解消しているのに、電話でまた起こされるなんていう羽目になってしまいます。そこで、先ほど説明したように、条件に合致した発生済みインシデントをResolvedにする機能を使って、自動的にResolvedに変えたいと思います。合致の条件は以下のような種類が選べます。

f:id:kazukiyunoue:20160115171116p:plain

例えば発砲通知メールを、

[Alert] Xxxxxx is Xxxxxx

として、復旧通知メールを、

[Resolved][Alert] Xxxxxx is Xxxxxx

とします。この場合、以下のようにルールを設定すると、

f:id:kazukiyunoue:20160115172840p:plain

あら不思議、復旧通知メールをきっかけにpagerduty側のインシデントもResolved状態になりました。

まとめ

これでpagerdutyの更新忘れで無駄に起こされることがなくなりましたね。サードパーティ製の監視ツールの場合、pagerduty側での受け入れ体制が整っているので、もともと対応している場合もあります。独自の監視システムをお持ちの方のご参考になれば幸いです。

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、、!