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

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権限があるので可能な気がするんだけど。。

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

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