2012年11月28日水曜日

bashとPerlの真偽値的な何か

PerlスクリプトでDBI使わずにMySQL接続確認したい…と思ってたら、ハマった。



system("mysql --user=tpcc --password=xxxx") or die("MySQL connection failure.\nexit.\n");


……接続できなくてもdieしてくれないよ!? Σ(゚д゚lll)
というかむしろ、接続出来た時にdieしてるよ!? Σ(゚д゚lll)

↓はちゃんと動くのになぁ。

$ mysql --user=tpcc --password=xxxx || echo die
ERROR 1045 (28000): Access denied for user 'tpcc'@'localhost' (using password: YES)
die


と、思ったら。
system関数の戻り値は実行したプロセスの終了コードなので、接続成功すると正常終了で0なのですね。
ところでPerlの0は偽ですね。
つまり、mysqlコマンドが接続できた時が偽で、接続に失敗した時が真ですね?

...orz


気持ち悪いけど、

system("mysql --user=tpcc --password=xxxx") and die("MySQL connection failure.\nexit.\n");

と書いて事無きを得る。。

$HOME/.my.cnfで無理矢理遊ぶ

意外に知られていない$HOME/.my.cnfを使って遊んでみよう企画。

$HOME/.my.cnfはMySQL関連のプログラムを起動した時に最後に読み込まれるオプションファイルで、
オプションの優先順位が`後勝ち'なので直接渡したオプション以外は
ここに書いておいたものが/etc/my.cnfなんかと重複していても優先される。
かつ、環境変数$HOMEに依存するので、特定のUNIXアカウントに対してのみ悪戯設定出来る。

ではれっつごー。


$ cat .my.cnf
[mysql]
execute = "SHOW PROCESSLIST"

$ mysql
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host      | db   | Command | Time | State | Info             |
+----+------+-----------+------+---------+------+-------+------------------+
|  7 | root | localhost | NULL | Query   |    0 | NULL  | SHOW PROCESSLIST |
+----+------+-----------+------+---------+------+-------+------------------+


おおおお。
--executeオプションも利くんだねこれ。

ところで、--executeってコマンドラインオプションで打ち消せない気がする(少なくとも--skip-executeではダメだった)ので、
これを誰かさんの$HOME/.my.cnfに書いておけば楽しい誰も幸せにならないこと請け合い。。



副産物的に思い付いたんだけれど、
5.6の$HOME/.mylogin.cnf(別に平文で書くのが嫌じゃなければ$HOME/.my.cnfでも良い)と
デフォルトシェルをmysqlクライアントにすると一発でログイン出来る上に。

$ mysql_config_editor print --all
[client]
user = root
password = *****

$ grep myadmin /etc/passwd
myadmin:x:502:503::/home/myadmin:/usr/bin/mysql

$ su - myadmin
Password: 

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 5.5.25-log MySQL Community Server (GPL)

Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

# tail /var/log/secure
..
Nov 28 10:06:02 cent01 su: pam_unix(su-l:session): session opened for user myadmin by root(uid=0)
Nov 28 10:07:09 cent01 su: pam_unix(su-l:session): session closed for user myadmin


suの履歴はsyslogに残るから、この経路以外でのmysqlのrootへのアクセスを塞ぐ
(.mylogin.cnf頼りrootのパスワードを配布しない)様にすれば、
MySQLにrootでアクセスした時のUNIXアカウントとアクセスしていた時間が綺麗にログに取れる。

んー。。これちょっと実装というか設定したいなぁ。。

2012年11月26日月曜日

Perlのprintfの謎4096バイト問題

/proc/net/devを定期的に読んで、テキトーに整形して
テキストファイルに吐き出すスクリプトを書いていた時に気付いた。

3秒に1回/proc/net/devを読んでprintfで書かせる様にしている割に、
なぜかtail -fで出力先ファイルを開いても何も表示されない。

かと思うと、しばらくしてからがばっと出力、またしばらく沈黙が続く。

コードはこんなの。


while() {

if (! -e $sPidFile) { exit 0; }

### open /proc/net/dev.
sysopen(IN, "/proc/net/dev", O_RDONLY);
printf(OUT "%s", time());

while ($sBuff = <IN>) {
if ($sBuff =~ /(.+):(.+)/) {
chomp($sBuff);
$sBuff =~ s/.+:\s+//;
printf(OUT "\t%s", $sBuff);
} else {
next;
}
}

printf(OUT "\n");
sleep($sInterval);
}

美しくないけど泣かない。

strace -t -e write,readで見てみたらこんなになってた。

14:59:12 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:12 read(5, "", 4096)              = 0
14:59:15 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:15 read(5, "", 4096)              = 0
14:59:18 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:18 read(5, "", 4096)              = 0
14:59:21 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:21 read(5, "", 4096)              = 0
14:59:24 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:24 read(5, "", 4096)              = 0
14:59:27 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:27 read(5, "", 4096)              = 0
14:59:30 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:30 read(5, "", 4096)              = 0
14:59:33 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:33 read(5, "", 4096)              = 0
14:59:36 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:36 write(4, "909543\t0       0    0    0    0 "..., 4096) = 4096
14:59:36 read(5, "", 4096)              = 0
14:59:39 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:39 read(5, "", 4096)              = 0
14:59:42 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:42 read(5, "", 4096)              = 0
14:59:45 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:45 read(5, "", 4096)              = 0
14:59:48 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:48 read(5, "", 4096)              = 0
14:59:51 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:51 read(5, "", 4096)              = 0
14:59:54 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:54 read(5, "", 4096)              = 0
14:59:57 read(5, "Inter-|   Receive               "..., 4096) = 569
14:59:57 read(5, "", 4096)              = 0
15:00:00 read(5, "Inter-|   Receive               "..., 4096) = 569
15:00:00 read(5, "", 4096)              = 0
15:00:03 read(5, "Inter-|   Receive               "..., 4096) = 569
15:00:03 read(5, "", 4096)              = 0
15:00:06 read(5, "Inter-|   Receive               "..., 4096) = 569
15:00:06 read(5, "", 4096)              = 0
15:00:09 read(5, "Inter-|   Receive               "..., 4096) = 569
15:00:09 read(5, "", 4096)              = 0
15:00:12 read(5, "Inter-|   Receive               "..., 4096) = 569
15:00:12 write(4, "   0    0    0     0       0    "..., 4096) = 4096

読んだものを加工してから書かせてるから、毎回569バイトずつ溜まってる訳じゃないはずなんだけど、
なんか4096バイトまで溜め込んでwrite()している雰囲気。

なんだろう。。

噂どおりのslave_parallel_workers

やっとparallelのRとLを間違えなくなった。。

http://dev.mysql.com/doc/refman/5.6/en/replication-options-slave.html#option_mysqld_slave-parallel-workers

個人的にかなり期待していた5.6の新機能ではあるけれど、実際試してみた感想をメモ。


1) 前提条件として、パラレルで動けるのはデータベース単位。
 ⇒全部同じデータベースに突っ込んである環境だと効果なし。
 ⇒USEステートメントで選んだデフォルトデータベースでなく、
  ちゃんと実効データベース(--replicate-wild-*-tableみたいな感じ)で判定してくれるっぽい。

2) データベース間をまたぐステートメント(INSERT INTO d1.t1 SELECT * FROM d2.t1とか)は
 2つのデータベースのparallel workを阻害する。
 ⇒d1に対する反映とd2に対する反映はそのステートメントが終わるまで待たされる。
  d3に対する反映は追い越してparallel workできる。

3) パラレルじゃないレプリケーションならリトライできるエラーでも、
 SQL_THREADが止まった上にその更新は適用されず失われる。
 ⇒スレーブ側でSELECT .. FOR UPDATEでロックしてlock wait timeout exceededにしたら、
  SQL_THREADは止まったけどSTART SLAVEしてもその更新は適用されないままさっくり続きのRelay Logを待つ。
  ⇒START SLAVEするたびにWarningで`こういう動作になるよ!'って教えてくれるけど。


うーん。。。
2)は良いとして、1)が問題にならないくらいデータベースがきちんと分かれてないとなぁ。。
レプリケーションの並列度を上げる為に水平分割とか1テーブル1データベース構成とか。。

あと、3)が地味に痛い。
クエリの中身とかはエラーログに吐いてくれるから、START SLAVEする前にそのクエリ手で叩けば良いのかな?

GTIDと並んでいきなり試すには鬼門な感じがする。。


【2012/02/06 10:18】
ちなみにこんなWarning。

slave_transaction_retries is not supported in multi-threaded slave mode. In the event of a transient failure, the slave will not retry the transaction and will stop.

2012年11月22日木曜日

mysqldumpの--flush-logsオプション

mysqldumpの--flush-logsオプション、mysqldumpを取ると同時にFLUSH LOGSしてくれるので、
バイナリログの管理が便利になったりするあのオプション。

ドキュメントには、
`--master-dataまたは--lock-all-tablesを一緒につけないと、複数データベースをダンプする時に
 各データベースのダンプごとにFLUSH LOGSするよ!'
とか書いてあるんだけど、一向に手元で再現しなかったのでソースを読んでみた。

結論、5.5.21以降(5.6も)では、--flush-logs --single-transactionならFLUSH LOGSは1回だけしか実行されない。
5.5.20以前(5.1も)では、--flush-logs --single-transactionではデータベースごとにFLUSH LOGSされる。



5.5.21以降のclients/mysqldump.cのmain関数の中で、

-------------------------------------------------------



  /*
    Flush logs before starting transaction since
    this causes implicit commit starting mysql-5.5.
  */
  if (opt_lock_all_tables || opt_master_data ||
      (opt_single_transaction && flush_logs) ||
      opt_delete_master_logs)
  {
    if (flush_logs || opt_delete_master_logs)
    {
      if (mysql_refresh(mysql, REFRESH_LOG))
        goto err;
      verbose_msg("-- main : logs flushed successfully!\n");
    }

    /* Not anymore! That would not be sensible. */
    flush_logs= 0;
  }

-------------------------------------------------------






となっているのを発見した。。

5.5.20の同じ部分は、

-------------------------------------------------------

  if (opt_delete_master_logs)
  {
    if (mysql_refresh(mysql, REFRESH_LOG) ||
        get_bin_log_name(mysql, bin_log_name, sizeof(bin_log_name)))
      goto err;
    flush_logs= 0;
  }
  if (opt_lock_all_tables || opt_master_data)
  {
    if (flush_logs && mysql_refresh(mysql, REFRESH_LOG))
      goto err;
    flush_logs= 0; /* not anymore; that would not be sensible */
  }
-------------------------------------------------------

と、ドキュメントどおりの動作。

ドキュメントのバグとして人生初Bugs投稿しました。。ドキがムネムネ。


ネタをくれた@studio3104さん、ありがとうございました(*-人-) 楽しかったです

2012年11月13日火曜日

my.cnfのパラメータ優先順位

複数のmy.cnfがデフォルトで読み込まれるパスに置いてあったり、
中で!includeしたり、直接オプションを渡したりしたとき。

同じパラメータが指定されている場合は、常に後勝ち(後から設定された方で上書き)です。


Linux系のMySQLでは、
/etc/my.cnf -> /etc/mysql/my.cnf -> SYSCONFDIR/my.cnf -> $MYSQL_HOME/my.cnf -> defaults-extra-fileで指定されたファイル -> $HOME/.my.cnf -> 直接渡したオプション
の順番で読み込まれる。

優先順位で考えると、後勝ちなので後ろが一番強い。

SYSCONFDIRはcmakeのオプションで指定するものの、デフォルトは/usr/local/mysql/etcになっていた(5.6.8-rcのsource)
$MYSQL_HOMEは、mysqld_safeがごにょごにょして/usrに置き換えられる場合が多い(CentOS)


さて、実験。

# cat /etc/my.cnf
[mysqld]
innodb_buffer_pool_size = 200M

# cat /usr/my.cnf   #MYSQL_HOME/my.cnf

[mysqld]
innodb_buffer_pool_size = 1G

mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+------------+
| Variable_name           | Value      |
+-------------------------+------------+
| innodb_buffer_pool_size | 1073741824 |
+-------------------------+------------+
1 row in set (0.00 sec)


 ⇒後勝ち。


# cat /etc/my.cnf
!include /etc/my2.cnf
[mysqld]
innodb_buffer_pool_size = 200M

# cat /etc/my2.cnf
[mysqld]
innodb_buffer_pool_size = 250M

mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';

+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 209715200 |
+-------------------------+-----------+
1 row in set (0.00 sec)

 ⇒includeして250Mの値を1行目で得た後、3行目で200Mで上書き。


cat /etc/my.cnf
[mysqld]
innodb_buffer_pool_size = 200M

!include /etc/my2.cnf


cat /etc/my2.cnf
[mysqld]
innodb_buffer_pool_size = 250M

mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 262144000 |
+-------------------------+-----------+
1 row in set (0.00 sec)

 ⇒200Mの値を3行目でincludeして250Mの値で上書き。


ただし、--defaults-fileを指定すると、そのファイル以外のデフォルトファイルは読まなくなる。
他のデフォルトファイルも読ませたい場合は、--defaults-extra-fileで指定すること。


【2012/11/13 14:08】
本家のリンク貼り忘れた。。

http://dev.mysql.com/doc/refman/5.5/en/option-files.html


【2013/05/30 00:10】
MariaDB 5.5.30のINSTALL_SYSCONFDIRは/etcになってた。
/etc/my.cnf -> /etc/mysql/my.cnf -> /etc/my.cnfの順番に読んだりしないのこれ?(調べてない

2012年11月9日金曜日

MySQL5.6.8ではInnoDBログファイルサイズの変更がお手軽に

今まではinnodb_log_file_sizeを変えると
テーブルスペースとログファイル両方の再作成が必要だったのが、すごくお手軽な感じになっていた!



121109 10:39:01 [Warning] InnoDB: Resizing redo log from 2*8192 to 2*16384 pages, LSN=1626134
121109 10:39:01 [Warning] InnoDB: Starting to delete and rewrite log files.
121109 10:39:01 [Note] InnoDB: Setting log file ./ib_logfile101 size to 256 MB
InnoDB: Progress in MB: 100 200
121109 10:39:02 [Note] InnoDB: Setting log file ./ib_logfile1 size to 256 MB
InnoDB: Progress in MB: 100 200
121109 10:39:02 [Note] InnoDB: Renaming log file ./ib_logfile101 to ./ib_logfile0
121109 10:39:02 [Warning] InnoDB: New log files created, LSN=1626134


テンポラリファイル作って内容を移してリネームしている様子。
ファイルサイズ大きめにすると、作ってる様子がちゃんと観察できた。
小さいログファイルサイズから大きいログファイルサイズに変更できるのは勿論、
大きいサイズから小さいサイズに変更するのも問題なさそう(ログ上は)

innodb_log_files_in_groupを増やしたり減らしたりするのも問題なく処理してくれる。

使う機会があるかどうかは置いておいて、地味に感動した!

MySQL5.6.8でrpmインストール時に変更になったこと

rpmでMySQL-serverをインストールすると、最後に自動でmysql_install_dbが流れるけれど、
mysql_install_dbに--random-passwordsオプションというのが出来て、
rpmでインストールした時にはそのオプションが有効な状態で流れるとのこと。
これによって、rootの初期パスワードが空っぽじゃなくなる。

【2016/03/14 09:22】
MySQL 5.7では更に変わって、mysqld --initializeになっていて.mysql_secretは作らなくなっています。こちらもどうぞ。 http://yoku0825.blogspot.jp/2015/03/mysql-576-mysqlinstalldbmysqld.html

 ⇒/root/.mysql_secretというファイルに書いてあった。
  カレントディレクトリじゃなくてホームディレクトリに作るっぽい。
  中身はこんなの。

# cat /root/.mysql_secret
# The random password set for the root user at Fri Nov  9 10:15:47 2012 (local time): EtZjQcMc

# mysql -pEtZjQcMc
mysql> SELECT user,host,password FROM mysql.user;
ERROR 1820 (HY000): You must SET PASSWORD before executing this statement
 ⇒mysql.user.password_expiredの値(これも5.6の新機能)が'Y'になってるので、
  先にパスワードを変えないと先に進めない。

mysql> SET PASSWORD = '';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT user,host,password,password_expired FROM mysql.user;
+------+-----------+-------------------------------------------+------------------+
| user | host      | password                                  | password_expired |
+------+-----------+-------------------------------------------+------------------+
| root | localhost |                                           | N                |
| root | cent24    | *80B1CF9111F7F255A9A5DC45068AFE299FF3CEB3 | Y                |
| root | 127.0.0.1 | *80B1CF9111F7F255A9A5DC45068AFE299FF3CEB3 | Y                |
| root | ::1       | *80B1CF9111F7F255A9A5DC45068AFE299FF3CEB3 | Y                |
+------+-----------+-------------------------------------------+------------------+
4 rows in set (0.00 sec)

確かに無名ユーザーも消えてるし、パスワードはさっきのが設定されてるし、
パスワードはExpireされてる。


5.6がGAになって新しく試した人が、
「rootでログインできません!」
とか路頭に迷う日が来たり?(しないか


【2012/11/09 10:34追記】
mysql_install_dbはついでに、/usr/my.cnf(←が存在すれば/usr/my-new.cnf)を勝手に作ってくれるんだけど、

# cat /usr/my.cnf

# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.6/en/server-configuration-defaults.html

[mysqld]

# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M

# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin

# These are commonly set, remove the # and set as required.
# basedir = .....
# datadir = .....
# port = .....
# server_id = .....
# socket = .....

# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M

sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

なんだかなぁ、な内容。。
せめて搭載メモリサイズ拾ってちゃちゃっと値を入れてくれれば良いのに(´・ω・`)

2012年11月2日金曜日

mysqlbinlogでバイナリログをバックアップするとか

mysqlbinlogは今まで(バックアップ用途で)リモートからバイナリログを読ませようとしても、

・SQLテキスト形式にエンコードした後のものを出力させるだけ。
・その時点の最後のエントリまでしか読み込めない。
・出力先ファイル名は1つしか指定できないので、
 読み込み元のバイナリログが複数でも出力は1つ。

だったのが、5.6のmysqlbinlogでは

・エンコードする前の状態のまま書き出せる。
・tail -fぽく待機して、更新があれば続けて書き出せる。
・ファイル名はマスターのものと同じものにできる。
 スイッチしたらmysqlbinlog側のファイル名も変わる。

と、スレーブに--log-slave-updatesを付けた様な動作が出来る様になった。
バックグラウンドにして常駐させておけば、暇そうなサーバにバイナリログをリアルタイムにバックアップ出来る。
マスターから見るとレプリケーションスレーブが1台増えるのと同じはずなので、
scpやftpで1時間に1回がりがり取るよりもI/O負荷的に優しい(たぶん)
バックアップ先のサーバから見た時に、一気に転送されるのとずっとちまちま転送されるのと
どっちが幸せなのかはちょっと考える余地があるかも。
運用してないので多数派がワカラナイ。。

mysqlbinlogさえ入っていればそれだけで済むので
バックアップ先のサーバにMySQL-clientパッケージだけ入れておけば良いし、
試してみる価値はありそうかなぁと。


使い方。

$ mysqlbinlog --read-from-remote-server --host=cent01 --user=repl --password=repl --stop-never --raw --result-file=cent01- bin.000001 &

--userにはREPLICATION SLAVE権限を持つユーザーを指定。
--stop-neverでtail -fっぽい動作になる。
--rawで、バイナリログをデコードせずにバイナリのまま、かつ、マスターのファイル名を踏襲してコピー。
--result-fileは--rawと一緒に指定した時は、保存先ファイル名のプレフィックスになるので、
cent01にはlog-bin=binと指定してあって大本のバイナリログファイル名がbin.000001~になる場合、
コピー先のバイナリログファイル名はcent01-bin.000001~になる。
最後の引数は、コピーを開始する最初のバイナリログファイル名。これはマスター上の名前で。

上手く使えばちょっとしたバックアップには使える予感。
--raw抜きで--stop-neverだけでもちょこちょこしたものに使えそうかな?

Perl用のBinlog APIとかあったなぁとふと思い出した。。


【2013/03/15 12:25】
資料を作ってふと気付いたんですが、mysqlbinlog --stop-neverしていると、
mysqlbinlogをCtrl+Cで切ってもコネクションが残存してしまう。
Bugs上げてみた。。

http://bugs.mysql.com/bug.php?id=68681