managed じゃないRDBで replicationするのは、できれば避けて過ごしたい人生ですが、https://qiita.com/masudakz/items/1afafcca1c01d8b9c276 のようなシステム構成もありうるのです。そして replicationを組んでいると、「同期できなくなって障害」というのにいつかは出くわすので、早期警戒体制が欲しくなります。
Mackerel公式の mackerel-plugin-mysql
にはmysql.seconds_behind_master.Seconds_Behind_Master
があってよさげな感じです。
スレーブ SQL スレッドとスレーブ I/O スレッドの間の時間差 (秒単位)スレーブ SQL スレッドとスレーブ I/O スレッドの間の時間差 (秒単位)
この plugin を入れて Monitors でAlert設定入れて検証してみると、検出できない障害パターンもありました。Master側がAWS RDS でSecurityGroupで、Slaveからの接続を遮断したパターンです。Master側のbinlogが見えないので、遅延を計測できない感じです。MySQLの show slave status
にも異常所見が見えない(少なくともAlert出したい時間感覚の中では)。
SecurityGroupの誤設定とか、Master側でのGRANTの誤設定とか、Replication用ユーザを間違って無効化するとか、遅延以外のオペレーションミスでの通信途絶リスクがあるので、これではちょっと足りない。足りないなら自作すればいいのです。
手動の同期トラブルシューティング
すでに開発環境で2度の同期事故経験があるので、どこを見てどう判断したかを思い出してみます。
-
show slave status
のLast_Error
- いつ止まったか、高頻度INSERTのTABLEの最終レコードを見る
SELECT id, ts_created FROM ... ORDER BY id DESC LIMIT 1;
- replication用userで Slave - Master の疎通テスト
plugin概要
show slave status
の Last Error
はどんなパターンが出るか事前には網羅できないし、通信途絶だとそもそも Error 認識できないので custom plugin のスコープからは外します。
高頻度INSERTのTABLEの最終レコードを Slaveからlocalhost と Master(remote)で比較すれば、疎通テストにもなります。疎通失敗で比較不可能だったらその時点でAlert出るようにすればいいでしょう。
plugin骨格
SELECT MAX(${keycolumn}) FROM ${table};
Primary Keyの MAX()
で瞬時レスポンスで毎分実行しても負荷にはならない想定です。
sqloption="--silent --skip-column-names --connect-timeout=2"
mysql -h ${host1} -u ${user1} -D ${database} -e "${sql}" ${sqloption}
SQL出力は --silent --skip-column-names
で最もシンプルな数値だけにします。
通信障害のときにはさっさと(2秒で)あきらめてほしいので --connect-timeout=2
です。
plugin metric
Alert目的なので SELECT MAX...
の結果の差分 max1 - max2
が必須です。
0 が正常、WARN, CRITICAL は1でも10でも1000でもINSERT頻度に応じて。
でもこれだけだと平常時はずっと 0 が続くので、 custom pluginがちゃんと動いているかどうかが不安になります。元の max1, max2 も出力します。ただし、必須の diff とは値域も重要度も違うので、namespace を分割して別グラフにします。
echo replica.key.max1 ${max1} ${EPOCH}
echo replica.key.max2 ${max2} ${EPOCH}
echo replica.diff.diff ${keydiff} ${EPOCH}
replica.key の方を Mackrelで差分グラフにすると Insert数/min のグラフになります。Insert密度があがっていくとどこかでSlaveが追いつけなくなって diff が非0になるでしょう。それが障害か、利用頻度上昇によるものか、判断の一助になることを期待しています。
pluginコード
...
sql="SELECT MAX(${keycolumn}) FROM ${table};"
sqloption="--silent --skip-column-names --connect-timeout=2"
EPOCH=$(date +'%s')
max2=`mysql -h ${host2} -u ${user2} -D ${database} -e "${sql}" ${sqloption}`
max1=`mysql -h ${host1} -u ${user1} -D ${database} -e "${sql}" ${sqloption}`
if [ $? != 0 ]; then
keydiff=1000
else
keydiff=`expr $max1 - $max2`
fi
echo replica.key.max1 ${max1} ${EPOCH}
echo replica.key.max2 ${max2} ${EPOCH}
echo replica.diff.diff ${keydiff} ${EPOCH}
全容は https://github.com/masudakz/mackerel-plugin-replica で開示しています。
余談
2018年06月のMy first mackerel pluginの記事が「自助」になりました。面白みの薄い pluginだなあと思いながら当時書いたものですが、自分が読むには自分の記事が一番読みやすい。2つ目を作るのに大いに助けになってくれました。
https://qiita.com/masudakz/items/654d65041a7507e59ee3