2013年9月8日日曜日

MySQL 5.6 の GTID のしくみ

MySQL 5.6 で GTID (Global Transaction ID)が導入されました。どうも使うと便利そうだ、というものの実際どんなものかイマイチ分からなかったので、調べてみました。

サーバーがUUIDを持つ

私は調べるまで誤解していたのですが、UUIDを持つのはサーバーであって、トランザクションではありません。それぞれのサーバーにUUIDが割り当てられ、トランザクションは連番が振られます。
例えば、4台のMySQLインスタンスで1台をマスターにした場合は以下のようににUUIDが振られます。
そして、トランザクションは次のようにIDが振られます。
UUID:トランザクション番号
例えば"40bbcf9b-e556-45b0-bdb3-e528f0041652"で実行された123番目のトランザクションは
40bbcf9b-e556-45b0-bdb3-e528f0041652:123
という感じになります。
そして、それぞれのサーバーで実行されたトランザクションは "GTID_Executed" に "uuid:最初-最後" というように記録されます。
この図の場合では、右端のスレーブのみ最後のトランザクションが実行されず、遅れていることがわかります。
ちなみに、途中のトランザクションだけ抜けた場合は、
40bbcf9b-e556-45b0-bdb3-e528f0041652:1-20:22-1021
という感じで、 : でつなげて記録されます。

マスターを切り替える

ではマスターが落ちた場合の動作を見てみましょう。
マスターが落ちました。右端のスレーブが遅れているので除外し、今回は左端のスレーブをマスターに昇格させます。
この場合、左側のスレーブは新マスターに追いついているので、新マスターの最新のログポジションに合わせれば良いことがわかります。右側のスレーブはひとつ遅れているので、新マスターの該当のログポジションに合わせます。これらが自動計算され CHANGE MASTER TO に MASTER_AUTO_POSITION=1 を指定するだけで良いのが GTID の売りです。

新マスターでのトランザクション

新マスターでトランザクションが実行された場合は、新マスターのUUID:1 から新しくトランザクションが数えられます。
GTID_Executed にはこれまで実行されたトランザクションがすべて記録されます。マスターが変更された場合は複数行記録されます。

新しくスレーブを追加

旧マスターに対するスナップショットから新しくスレーブを追加することもできます
右端に新しくスレーブを追加しました。旧マスターのトランザクションのみ実行され、新マスターのトランザクションが実行されていないことが GTID_Executed からわかりますので、新マスターに接続した時にポジションを新マスターの場所に自動的に設定します。

バイナリログの中身

GTIDを有効にした状態では、バイナリログに
SET @@SESSION.GTID_NEXT= 'a8679745-1864-11e3-a1ec-28924a2bea1c:14'
という行が現れます。これは、次に実行するトランザクションのIDを指定しています。
GTID_NEXT は通常であれば "AUTOMATIC" に設定されていて、その状態では "自分のuuid:次の番号" のIDを次のトランザクションに設定しますが、設定し直すと任意のIDのトランザクションを作成することができます。
# at 2621
#130908 21:05:04 server id 10  end_log_pos 2669 CRC32 0x20b2bc89        GTID [commit=yes]
SET @@SESSION.GTID_NEXT= 'a8679745-1864-11e3-a1ec-28924a2bea1c:14'/*!*/;
# at 2669
#130908 21:05:04 server id 10  end_log_pos 2737 CRC32 0x5e5fa3bb        Query   thread_id=10    exec_time=0     error_code=0
SET TIMESTAMP=1378641904/*!*/;
BEGIN
/*!*/;
# at 2737
#130908 21:05:04 server id 10  end_log_pos 2785 CRC32 0x757ca2c9        Table_map: `yuryu`.`test` mapped to number 70
# at 2785
#130908 21:05:04 server id 10  end_log_pos 2829 CRC32 0x4e043fe7        Write_rows: table id 70 flags: STMT_END_F

BINLOG '
8GcsUhMKAAAAMAAAAOEKAAAAAEYAAAAAAAEABXl1cnl1AAR0ZXN0AAEIAADJonx1
8GcsUh4KAAAALAAAAA0LAAAAAEYAAAAAAAEAAgAB//4RAAAAAAAAAOc/BE4=
'/*!*/;
# at 2829
#130908 21:05:04 server id 10  end_log_pos 2860 CRC32 0x3fbac367        Xid = 226
COMMIT/*!*/;
つまり、バイナリログがスレーブに転送されて実行されるのは
  1. GTID_NEXT を次に実行されるトランザクションID(マスターと同じもの)にセット
  2. BEGIN ~ COMMIT でトランザクションを実行
ということになります。

まとめ

  • サーバーごとにUUIDを付与
  • トランザクションIDは サーバーUUID:番号 として連番で生成
  • スレーブで実行するときは GTID_NEXT を指定して同じトランザクションを再生
  • 実行したトランザクションは GTID_EXECUTED に記録される
以上のような仕組みで GTID は自分がどこまでのトランザクションを実行したのか追跡しています。

参考資料

PerconaのMySQL 5.6 GTID in a nutshellというスライドが大変参考になりました。

2013年9月6日金曜日

CentOS / Debian 両方で使える memcached 複数インスタンス対応の init スクリプト

CentOS に標準で添付される memcached 用の init スクリプトは、単一のインスタンスを起動することしかできません。探していると memcached.conf を複数配置して実行するもの は存在するんですが、ほしいのは /etc/sysconfig/memcached を使う CentOS のやりかたと互換性を保ちながら複数インスタンスに拡張するものでした。

というわけで Debian 用の memcached.conf を複数配置するものをベースにして、CentOS / Debian 両対応するとともに環境変数を /etc/sysconfig/memcached から読み込んでインスタンスを起動するものを作成しました。

こちらです: https://github.com/yuryu/memcached-better-init-script/

使い方


もし1インスタンスしか使わない場合は、従来の init スクリプトと変わらず、 /etc/sysconfig/memcached がそのまま使えます。以下は複数インスタンスを起動するときのやりかたです(READMEを日本語にしたものです)。

/etc/sysconfig/memcached-server1
/etc/sysconfig/memcached-server2
        .
        .
        .
/etc/sysconfig/memcached-serverN
というふうに必要なだけサーバーを並べます。 server1 の部分は好きな名前が使えます。
service memcached start
とすると、すべてのサーバーを起動します。
service memcached start server3
とすると、指定したサーバーのみ起動します。 stop も同様です。 それぞれのファイルの中身は
PORT="11212"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""
という感じで指定します。
CentOS 6.4 で動作確認しています。 LSB 準拠なので Debian でも動くはずですが確認はしていません。 CentOS では redhat-lsb-core パッケージが必要です。

2013年9月4日水曜日

MySQL 5.6.11でGTIDを有効にしFLUSH LOGSするとレプリケーションが止まる

タイトルのとおりです。 MySQL 5.6.11 (5.6.10までと5.6.12以降は大丈夫) において GTID を有効にした状態で、マスターで FLUSH LOGS すると、レプリケーションが停止します。

スレーブで SHOW SLAVE STATUS すると下記のようなエラーが出ています。

Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Found a Gtid_log_event or Previous_gtids_log_event when @@GLOBAL.GTID_MODE = OFF.; the first event 'mysql-bin.000013' at 404, the last event read from './mysql-bin.000013' at 452, the last byte read from './mysql-bin.000013' at 452.'
Last_SQL_Errno: 1778
Last_SQL_Error: Error 'Cannot execute statements with implicit commit inside a transaction when @@SESSION.GTID_NEXT != AUTOMATIC or @@SESSION.GTID_NEXT_LIST != NULL.' on query. Default database: ''. Query: 'FLUSH ERROR LOGS'

FLUSH ERROR LOGS でレプリケーションが止まってしまいます。これは logrotate するときに発行されていて、1日経つと(深夜に)レプリケーションが止まるという、とても困った状態になりました。

調べたところこれは 5.6.11 固有のバグでした。公式に MySQL Bugs: #69045: replication was broken while executing flush tables としてレポートされています。

gtid_next が automatic ではないときに、暗黙にトランザクションを発生させてコミットするようなステートメントを禁止した副作用で、FLUSH LOGSなどのステートメントがうまく動かなくなってしまったらしいのです。

5.6.12 以降ではこのパッチは差し戻されて、正常に戻っています。

tar は圧縮形式を指定しなくても展開できる

tar は最もよく使われるコマンドの一つですが、意外と知られていないことがあります。それは、展開するときに圧縮形式を指定しなくても展開できるということです。

よくtarを使ってファイルを展開するときに紹介されるコマンドは

tar xzvf archive.tar.gz
tar xjvf archive.tar.bz2
tar xJvf archive.tar.xz

というふうに、圧縮形式を指定しているものだと思います。ところが広く使われている GNU tar (Linux)も、BSD tar (OSX)も、展開するときは圧縮形式を自動判別します。つまり、

tar xvf archive.tar.gz
tar xvf archive.tar.bz2
tar xvf archive.tar.xz

とするだけで、すべての形式を自動判別して展開します。ちなみにファイルの一覧を出力してほしくない時は

tar xf archive.tar.gz
tar xf archive.tar.bz2
tar xf archive.tar.xz

とすると画面上には何も出力せず展開してくれます。

このことは、GNU tar であれば公式マニュアル、BSD tar であればmanにかかれています。

GNU tar の場合
Reading compressed archive is even simpler: you don't need to specify any additional options as GNU tar recognizes its format automatically.
圧縮されたアーカイブを読むのはより簡単です。GNU tar が自動的に形式を認識するので、追加のオプションを指定する必要はありません。

BSD tar の場合
代表例として -z を抜粋しますが、他の形式のオプションのところにも同様のことが書かれています。
-z, --gunzip, --gzip
(c mode only) Compress the resulting archive with gzip(1). In extract or list modes, this option is ignored. Note that, unlike other tar implementations, this implementation recognizes gzip compression automatically when reading archives.
-z
 (cモードのみ) 生成されるアーカイブをgzip(1)で圧縮します。展開やリストモードでは、このオプションは無視されます。他のtarの実装と異なり、この実装ではアーカイブを読むときにgzip圧縮を自動的に認識します。

いかがでしょうか。特にBSD tarの場合は明確に「無視する」と書いています。

ちょっとしたトリビアですが、知ってるとだいぶ得した気分になれます。

2013年9月2日月曜日

各種圧縮ソフト 圧縮速度と圧縮率の比較(並列版含む)

Unix-like 環境でよく使われるアーカイバの圧縮速度、圧縮率を比較しました。

比較したツール

すべて CentOS 6.4 / EPEL のレポジトリのビルドを使用しました。

比較した環境

  • CPU: Intel Core i7 2600
  • SSD: Crucial m4
  • Memory: DDR3-1600 16GB

ベンチマーク内容

圧縮に使ったファイルは、数GBあるMySQL のダンプです。

cat original | command > original.compressed

という形でベンチマークしました。

下記の表は gzip の標準状態を 1 とした相対的な数値を示しています。 Software 欄の -1 や -9 は、コマンドに指定できる圧縮率です。 -1 が最低、 -9 が最高になっているコマンドが多いです。

並列に実行できるものに関してはすべて 8 スレッドで実行しています。

ベンチマーク結果

time real
実際の経過時間
time user
CPU user 時間
compression ratio
圧縮率の比。大きいほどファイルが大きい。
compression ratio (single/parallel)
並列版について、シングルスレッドに対する圧縮率の比
parallel overhead
シングルスレッド版と比較した CPU user時間の比


Softwaretime realtime usercompression ratiocompression ratio (single/parallel)parallel overhead
lzo0.220.191.55
lzo -94.914.931.25
gzip -10.500.491.15
gzip1.001.001.00
bzip2 -14.414.460.86
bzip25.875.940.79
xz -11.961.980.94
xz17.8618.140.72
pigz -10.100.771.151.001.56
pigz0.201.551.001.001.55
pbzip2 -10.957.600.861.001.70
pbzip21.5712.530.791.002.11
pxz -10.463.610.961.031.83
pxz4.1128.350.731.011.56

棒グラフが相対的な経過時間、緑の点が相対的な圧縮率です。


xz が飛び出てわかりづらいので、xz以外のシングルスレッド系圧縮ソフトを並べました。

lzo が極めて高速ですがgzipに比べ1.5倍程度のサイズになりました。 lzo の最大圧縮はとても速度が遅いにもかかわらず gzip -1 より圧縮率が悪くなりました。
bzip2 は -1..-9 は厳密には圧縮率ではないので、他のソフトに比べて差が小さかったです。圧縮速度と圧縮率のバランスが良いように思います。
xz はさすがの遅さでした。ただ、 xz -1 や(今回は測っていませんが) xz -2 であれば、bzip2 より高速にもかかわらず肉薄する圧縮率を出すので、かなり優秀だと思います。
gzip は圧縮速度と圧縮率のバランスが取れています。

次にマルチスレッド系圧縮ソフトです。棒グラフが相対的な経過時間、緑の点が並列化のオーバーヘッド(シングルスレッド版に対するCPU user timeの比)、赤い点がシングルスレッド版に対する圧縮率の比です。


ご覧のように、シングルスレッド版に対して圧縮率はほとんど変わりません。シングルスレッド版に対する実行時間は bzip2 が若干高く2倍を超えましたが、その他は1.5倍~1.8倍程度でした。

まとめ

圧縮速度と圧縮率のバランスが取れているのは gzip で、並列版の性能も優秀でした。速度をある程度要求される環境下においても、 lzo には並列版がないので、CPUをたくさん使えるのであればこれで決まりな気がします。

xz は最高の圧縮率でした。 ただ、時間をかけて圧縮する用途にしても、さすがに時間がかかりすぎの気もします... 今回は測っていませんが、bzip2 より展開は早い(らしい)ので、ファイルを配布するなどの用途には向いているのでしょう。

bzip2 は圧縮率こそ xz に叶いませんが、実行速度とのバランスは優れていると思います。展開に時間が掛かるのがデメリットですが、一応保存しておく系のログファイルなどには使えるのではないでしょうか。

lzo の高圧縮モードは今回の測定だと出番がないように見えますが、展開が gzip に比べてメモリや時間が少なくて済むので、組込み用途では活躍しそうです。

補足

xz のマルチスレッド版には pixz というのもありますが、これは圧縮アルゴリズムにも少し手を加えており、 xz とファイルの互換性がありません。 環境によってはビルドが失敗するなど、これからに期待です。 ファイルの互換性はあるようです。また、私の環境でビルドが失敗したのは単にSDKが古いから説が浮上したので、今度追試します。

pxz は xz を単純にマルチスレッドにしたもので、圧縮が終わってからファイルを連結するので、メモリとディスクの使用量がかなり大きくなります。

今回は展開速度に関しては測っていませんので、展開を頻繁に行う場合はそちらも考慮する必要があります。

2013年9月1日日曜日

Linux Kernel Updates の Kindle 版が出ました!

おまたせしました!

コミックマーケット83で頒布した Linux Kernel Updates 2012.12版と、コミックマーケット84で頒布した Linux Kernel Updates 2013.08版のKindle版を出版しました。

Linux Kernel Updates 2012.12


目次

  • What's New in Linux 3.7?
  • TCP Fast Open
  • Load Average の落とし穴
  • Completely Fair Scheduler
本文試し読み(冊子版のキャプチャです)。

Linux Kernel Updates 2013.08版

目次

  • What's New in Linux 3.8?
  • What's New in Linux 3.9?
  • What's New in Linux 3.10?
  • カーネルを更新するには
  • Modern Socket Progarmming
  • カーネルソース読書ガイド
  • マルチコアを使いこなす

本文試し読み(冊子版のキャプチャです)。

2013年8月22日木曜日

/etc/hosts はホスト数が増えるとDNSより遅くなる

/etc/hosts にホスト名を書いて配るというのは、数台のマシンを管理する状況では誰しもやったことがあると思います。DNSクエリが発生しないのでとても早く、また単一障害点が発生しないメリットがあります。その反面、台数が増えてくると全部を更新するのがとても大変になるだけでなく、致命的な速度低下をもたらします。

今回は /etc/hosts と dnsmasq でどの程度速度が変わるのかをベンチマークしました。

テスト環境は

  • OS: CentOS 6.4 64bit, Linux 3.10.2
  • CPU: Intel Core i7-2600 @ 3.4GHz
です。

テストにはひたすら getaddrinfo(3) し続けるプログラムを作成し、名前を解決しました。 /etc/hosts には

10.234.130.1 host1301
10.234.130.2 host1302
10.234.130.3 host1303

のように適当なアドレスとホスト名を並べたものを作り、利用しました。なお端数の2件はIPv4とv6のlocalhostです。

比較対象に dnsmasq をローカルに立てて同じ内容の hosts ファイルを読ませたものを使いました。

結果は次のとおりです。単位は秒で、10万クエリあたりの所要時間です。

#hostshostsdnsmasq
21.5164.8068
1022.65984.9224
2524.44744.9286
100213.23764.831


結果は一目瞭然で、ホストのエントリ数が増加するに従って、hosts を引く時間はほぼ線形に増加しています。今回の構成では損益分岐点はおよそ250エントリ程度となりました。なお hosts に見つからなかった場合は hosts の検索時間に加えて DNS を引く時間がかかります。

こうなる理由は、hosts の中身はライブラリ(libc)によって処理されていて、毎回 fopen(3) して線形に検索しているからです。dnsmasqをはじめとする多くのネームサーバーはファイルから読み込んだあと、ハッシュなどを使って高速に検索を行います。

実際には1リクエストあたりの時間に換算すると0.05マイクロ秒程度で、hostsに1000エントリ書いた場合でも0.14マイクロ秒なので大した時間ではありません。ですが、場合によってはhostsを引くよりきちんとDNSを使ったほうが早くなる場合があるということは覚えておいて損はありません。

測定に用いたプログラムは下記のとおりです。
2013/08/23 追記: タイトルが「急激に遅くなる」というのは煽っていると指摘を受けたので変更しました。 測定ポイント数が少ないのは単なる手抜きです...
「そんな多数のホストがある状態で /etc/hosts にすべて書くのはありえない」的な意見も見受けられましたが、実際にあるんですよ... そういう状態に対してきちんとエビデンスを示す目的で書かれました。 DNSで実際に速度や冗長性を担保するには別の工夫が必要になりますが、それはまた別の記事で。

あと nscd 使えばっていう話があったので参考までに1002エントリhostsで走らせたところ
$ ./a.out 100000 host127183
Resolved 100000 times. Elapsed time = 0.740 seconds, 0.007 us per host.

こんくらいの速度だったので、何も考えずに200倍早くなります。