2016年12月10日土曜日

MySQLのビルド環境にConoHaを選んでいる理由

この記事は ConoHa Advent Calendar 2016MySQL Casual Advent Calendar 2016 の10個目の窓です。

俺は とある企業のDBA なので、基本的にコードは書きません。Bot書いたりとか運用ツール書いたりとかMySQLにパッチ当てたりだとかそのくらいですね。意外と書いてた。

Botはついでに動かしているだけで、もともとBotのためにConoHaを使っているわけでもなかったし(当時はCPU2コア, メモリー1GB, HDD 100GBが最小プランだったけど、Botに使うにはちょっとオーバースペック)、じゃあ何に使っているのかというと

MySQLのビルド なわけですが。これがConoHaだとかなり良い。とても良い。
というか最近、ソースコードも量が増えて、へたれな仮想サーバーとかPCのVirtualBoxだと まともな時間でビルドが終わらない ようになってきました。特に5.7から先。10時間かかっても終わらないとか挙句OOM Killerに殺されちゃったりとかなってきました。つらい。

その点ConoHaだと8.0.0でも1時間あればお釣りがくるくらいなので助かっています…というか本当に助かったこれは。、

まずMySQLおじさんたるもの、取り敢えず新しいマイナーバージョンが出たらそれをビルドします。pre 5.0は捨てるにしても、5.0.96, 5.1.73, 5.5.53, 5.6.34, 5.7.16, 8.0.0の5つのバージョンのバイナリーとソースコードを保管しないといけない訳です。

あとたまに触りたくなると(触ったあとに消すけど)lab版とかPercona ServerやMariaDB、MySQL Clusterもビルドすることになるので、常時6~8個のmysqldは常に置いておかないといけません。


$ du -sch /usr/mysql/*
104M    /usr/mysql/5.0.96
306M    /usr/mysql/5.1.73
385M    /usr/mysql/5.5.53
676M    /usr/mysql/5.6.34
1.2G    /usr/mysql/5.7.16
1.2G    /usr/mysql/8.0.0
1.6G    /usr/mysql/http
1.2G    /usr/mysql/labs
6.5G    total


あとはもちろんソースコードと、コンパイルしたバイナリーは残しておかないといけません。デバッグとか、動作を理解するためにgdb刺してステップ実行とかは結構します。

$ du -shc mysql-*[0-9]
208M    mysql-5.0.96
503M    mysql-5.1.73
806M    mysql-5.5.53
1.4G    mysql-5.6.34
4.4G    mysql-5.7.16
15M     mysql-connector-java-5.1.40
2.3M    mysql-connector-python-2.1.3
8.9M    mysql-router-2.0.3
7.3G    total


複数台まとめて検証したりするにはDockerが便利ですよね。

$ docker images
REPOSITORY                                TAG                    IMAGE ID            CREATED             SIZE
docker.io/groonga/mroonga                 latest                 a0b64d145f97        5 weeks ago         1.236 GB
docker.io/groonga/mroonga                 mysql5634_mroonga610   6b31bf4e0d48        5 weeks ago         1.236 GB
docker.io/groonga/mroonga                 mysql5716_mroonga610   5c849df969e4        5 weeks ago         1.778 GB
docker.io/groonga/mroonga                 mysql5634_mroonga609   1a7d7e2ba454        6 weeks ago         1.234 GB
docker.io/centos                          centos6.6              d03626170061        3 months ago        202.6 MB
docker.io/centos                          centos6.8              0cd976dc0a98        3 months ago        194.5 MB
docker.io/centos                          centos5                1ae98b2c895d        3 months ago        284.7 MB
docker.io/yoku0825/mysql_router           latest                 4e14f7e09d50        3 months ago        519.6 MB
docker.io/yoku0825/mysql_fabric_command   latest                 6a4304d6b8a2        6 months ago        556.8 MB
docker.io/yoku0825/mysql_fabric_aware     latest                 2fac9fb7cc7e        6 months ago        1.664 GB
docker.io/yoku0825/mysql_fabric_server    latest                 e45808b5e20b        6 months ago        1.673 GB
docker.io/groonga/mroonga                 mysql5623_mroonga410   37f49ca977bc        9 months ago        1.132 GB


とするとこうなりますよね。

$ df -h .
Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   48G   44G  3.6G  93% /


CentOS 7.2でDocker最新版使おうと思って旧のHDD 100GBから乗り換えなければよかった。。追加SSD、流石に200GBは要らないんだよなあ。。50GB単位とかになったら嬉しい。


取り敢えず、MySQLのビルドしたりgtagsでタグ付けしてソースコード読んだり、空いたリソースでbotを起動したりする分には、お値段以上に楽しんでいる方だと思います。


明日の ConoHa Advent Calendar 2016 は AtnanasiさんMySQL Casual Advent Calendar 2016 はウッ

2016年12月8日木曜日

最近のMroongaさんの構成について

このエントリーは Groonga Advent Calendar 2016MySQL Casual Advent Calendar 2016 の8日目です。

Groonga + MySQLと言えばMroongaです。
GroongaとMroongaを正確に聞き分けてもらうテクニックとして、「じーるんが」と「えむるんが」というと大体通じます。「あーるるんが」もたまに言います。NroongaとDroongaの存在を忘れることにすれば、いい言い分け方じゃないかなーと勝手に思っていますが。

そんなウチのMroongaの構成に少し異変(?)があったのでメモ。


1年半前は ↓ こんな構成をしていた(らしい)Mroongaさん (See also MySQLの全文検索に関するあれやこれや)、何が悲しくて同じ文書を2回InnoDB用とMroonga用にINSERT/UPDATEするんだ…という状態だった(らしい)のですが、



晴れてこうなった(らしい)
全文検索を必要とするテーブルに限った話ではあるけれど、 replicate-do-db/replicate-do-wild-tableあたりを駆使して「マスターはInnoDB、スレーブはInnoDBのものとMroongaのもの」という構成になり、二重書き込みはしなくていいしマスターが倒れてもInnoDBのクラッシュリカバリーに頼れる状態。



ついでに、s3とs4 (Mroongaストレージエンジンのスレーブ)にgroonga-httpd を起動させて、「シンプルな全文検索だけのクエリーはHTTPで、DISTNCTとかしてるクエリーはMroongaでSELECT」とか参照を分けるようにもなりました(DISTINCTっぽいことをgroonga-httpdにやらせてみたけれどMroongaのDISTINCTと速度変わらなかった。。)

「更新処理の伝搬はMySQLのレプリケーション任せ」、「バックアップもMySQLのレプリケーション任せ」、「groonga-httpdでウマウマできるところだけアプリ改修」と実際結構良いことづくめでした。


ウチではGroongaさんは毎回ソースからコンパイルしているので、`make install` した後には `sbin/grroonga-httpd` が出来上がっています( `./configure --enable-groonga-httpd=no` しない限り、一緒にコンパイルされる)

吊るしで立ち上げる時のコンフィグは `etc/groonga/httpd/groonga-httpd.conf` です。
userをmysqlに、groonga_databaseをMroongaのデータファイル($datadir/$schema.mrn) にセットしました。access_logは捨てています(このあたり、rpm版を使うとログローテーションも一緒に入って便利だよって同僚氏は言ってました)


5c5
< user mysql mysql;
---
> user groonga groonga;
18c18
<   groonga_database /data/mroonga_datadir/database_name.mrn;
---
>   groonga_database /usr/local/groonga503/var/lib/groonga/db/db;
27,30c27
<   groonga_database_auto_create off;
<   groonga_log_level NONE;
<   access_log /dev/null;
<
---
>   groonga_database_auto_create on;

あとは `$ sbin/groonga-httpd` と叩くだけで勝手にデーモンになります。
複数台あってロードバランスされているので、死活監視だけして自動再起動とかは特に仕込んでません。


某氏の喜びの声。

一部の全文検索クエリをMroongaからGroongaへ切りかえ

下記のワードは最も効果がある例だが、おおむね0.4秒ほどクエリあたりのレスポンスは改善される。
また、リスト後半に行くにしたがってMySQLのOFFSET,LIMITはパフォーマンスが線形に悪化していくが、Groongaは大きく性能劣化しない。
クローラーがpager=4000 とかリクエストしてきても性能劣化なく応答できる可能性が高い。


某ワードの場合(160万件)
MySQL > カウント = 0.9 sec
MySQL > リスト = 1.2 sec

Groonga > カウント = 0.45 sec
Groonga > リスト = 0.73 sec

オフセット 100万
MySQL > リスト = 6.14 sec
Groonga > 0.75 sec

ちなみにですが…、
現状、見ているページの検索リスト取得と検索結果の総件数取得で2回クエリを発行していますが、groongaは見ているページの検索リスト取得時にデフォルトで総件数も一緒に返ってくるのでクエリ発行回数が1回で済みます。

大成功だったようです。

俺も彼に触発されて groonga-httpd を取り敢えずスレーブに入れてみたクチなんですが、簡単な割に効果が高い(こともある)ので、気になったら試してみることをオススメしております。
(参照だけと割り切れば、効果がなければ放ってMroongaに切り戻せば良いだけだし)

明日のMySQL Casualは @meijik さん、Groongaは…おっと、まだ決まっていないようですね?
参加をお待ちしております :)

2016年12月5日月曜日

2年越しの #ChugokuDB in 中国地方



これが2年前。




これが1年前。




そしてついに今年。

第18回 中国地方DB勉強会 in 広島 に逝ってきました!

( ´-`).oO(リアルに逝ってしまって本当に申し訳ない。。


スライドはこちらになります。
クエリーチューニングのおともに便利なMySQLer (都内30代・DBA 1.00000人に聞きました) 御用達のツールの紹介です。





恒例(?)の、MySQLおじさんとPostgreSQLおじさんが知らないことを聞きあうセッションもやってきました。 soudai1025_vs_yoku0825/03_hiroshima.md








坂井さん とその夜話したんですが、まあつまり1年半前と同じような感じで


MySQL を好きな人が、MySQLのちょっと変なところを、少しばかり強調しておもしろおかしく伝えている面はあるので、その変な部分だけが一人歩きして多くの人に理解されてしまうことを危惧はしていますが、

10回目の最後のYAPCに2回目の参加をしてきた - sakaikの日々雑感~(T)編


今週末の YAPC::Hokkaido ではちゃんと 最新版の魅力 をお伝えできるようにがんばります。


2016年12月3日土曜日

mysqlコマンドラインクライアントのコマンド集

この記事は MySQL Casual Advent Calendar 2016 の3つ目の窓です。
昨日は kakuka4430 さんの CentOS6.8にtpcc-mysqlを入れようとして失敗した話 でした。

$ mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 8.0.0-labs-opt-log Source distribution

Copyright (c) 2000, 2016, 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>

mysqlコマンドラインクライアントにはその中に更にコマンドを持っています。
helpを叩くと出てくる、\で始まるやつら(とそのロング形式)


mysql> help

For information about MySQL products and services, visit:
   http://www.mysql.com/
For developer information, including the MySQL Reference Manual, visit:
   http://dev.mysql.com/
To buy MySQL Enterprise support, training, or other products, visit:
   https://shop.mysql.com/

List of all MySQL commands:
Note that all text commands must be first on line and end with ';'
?         (\?) Synonym for `help'.
clear     (\c) Clear the current input statement.
connect   (\r) Reconnect to the server. Optional arguments are db and host.
delimiter (\d) Set statement delimiter.
edit      (\e) Edit command with $EDITOR.
ego       (\G) Send command to mysql server, display result vertically.
exit      (\q) Exit mysql. Same as quit.
go        (\g) Send command to mysql server.
help      (\h) Display this help.
nopager   (\n) Disable pager, print to stdout.
notee     (\t) Don't write into outfile.
pager     (\P) Set PAGER [to_pager]. Print the query results via PAGER.
print     (\p) Print current command.
prompt    (\R) Change your mysql prompt.
quit      (\q) Quit mysql.
rehash    (\#) Rebuild completion hash.
source    (\.) Execute an SQL script file. Takes a file name as an argument.
status    (\s) Get status information from the server.
system    (\!) Execute a system shell command.
tee       (\T) Set outfile [to_outfile]. Append everything into given outfile.
use       (\u) Use another database. Takes database name as argument.
charset   (\C) Switch to another charset. Might be needed for processing binlog with multi-byte charsets.
warnings  (\W) Show warnings after every statement.
nowarning (\w) Don't show warnings after every statement.
resetconnection(\x) Clean session context.

For server side help, type 'help contents'

…これは8.0.0でCTEが使える(labsの)コマンドラインクライアントなんですけど、なんか前に比べて増えてない?;


- \c なんかtypoした時とかよく使う。セミコロンが来る前であれば、入力中のSQLをなかったことに出来る。クォート閉じるの忘れた時とかは、閉じクォートを書いて\c(セミコロンがクォートの中に閉じ込められるから、セミコロンは認識されない)

mysql> INSERT INTO t1 VALUES (1, 'one'), (2, 'two), (3, 'three');
    '> '\c
mysql> 


- \G これ実はmysqlコマンドラインクライアントのコマンドなので、他のクライアントではできない(か、互換性のために実装してるものはあるかも)


MySQL Workbenchはそのまま\Gまでサーバーに投げつけちゃって、シンタックスエラーをもらってる(mysqlコマンドラインクライアントではサーバーに送り付ける前にこの\Gをゴニョってから投げてる)

- pagerの使い方は 第6回 mysqlコマンドラインクライアントにページャーを指定する:MySQL道普請便り|gihyo.jp … 技術評論社 に最近書いた

- useコマンドはコマンドで、USEステートメントもある話は前に 日々の覚書: mysqlコマンドラインクライアントでuseの代わりにcdを使う この辺でちょっとだけ

- editは(まだ空きがあるので)そのうちに。


こいつらを上手く使うとmysqlコマンドラインライフが豊かになるので機会があれば是非。

明日は zurazurataicho さんです。

2016年12月2日金曜日

ペパボの中の人ではありませんがペパボとの2016年を振り返って

このエントリーは  pepabo Advent Calendar 2016 の2日目です。
ちなみに GMOペパボ の人間ではありません。

俺は2016/12/02現在 GMOペパボのものすごく近くの会社 に勤めているので、去年の 論理削除Casua Talks #1 (おや…? #1から1年たつのに#2がないぞ…?) でしゃべらせてもらったのを切っ掛けに、今年は2回、まとまった時間を作ってもらってペパボに(内部の勉強会で)遊びにいきました。






1回目の勉強会の 5.7 + 雑な方は「へぇー、その機能、2年くらい前に言ってたよね。2年くらい前に見たわ」的なマサカリで俺が死ぬんじゃないかとドキドキしていたんですが、

すっげえ楽しそうに話してて、「この人、ほんと MySQL のこと好きなんだなー」と思って。
今回 MySQL 5.7 導入しようとしたときにも顔が浮かんだし。つまり背中を押してもらえたということだ。


この記事を読んだ時にすごくうれしかったです。今もたまに読み返しています。



2回目のGaleraの方は資料無し、ホワイトボードにごりごり思いついたことを書くスタイルでやらせてもらいました。というか参加してくれた方みんなフツーのMySQLのレプリケーション詳しくてすんごい話がしやすかった。Dockerでサクッと上げたPXCに「つまりRBRなのでこういうことすると死にます、というか自殺してフル同期かかります」って言ってサクッと落としてみるとか。


Galera Cluster勉強会@ペパボ – inamuu.com

楽しんでいただけたようで何よりです。



物理的に近く に位置しているものの、「ペパボの人ってすげーなー」「あんちぽさんよくやるなー」「ペパボの常様が常様の中で一番好きだわー」とか思っていただけのパンピーなので、遊びに行かせてもらってすごく楽しかったです。

あんちぽさん と「俺の知ってるMySQLのことは何でも伝えられるので、ウチのWEBサーバー周りの人にバーターで色々教えてください」なんて話をして、来年はもっと行き来ができればいいなと思っています。







以上、GMOペパボの福利厚生からでした。来年もまたよろしくお願いします ;)

2016年12月1日木曜日

MySQLのNOW関数はどのようにして安全にスレーブでリプレイされるのか

このエントリーは MySQL Casual Advent Calendar 2016 の1日目の記事です!

日々の覚書: 複数のテーブルのON UPDATE current_timestampなカラムの値を揃える方法を考える の派生形なんですが、 "CURRENT_TIMESTAMP および CURRENT_TIMESTAMP() は NOW() のシノニムです。" なので、ちょっとだけ篠田さんの「使い慣れたSQLに潜む実装依存」に対する補足でもあったりします。




NOW関数は デフォルトでは 「そのステートメントの開始時刻」を返します。
この動作の検証としては以下のステートメントが有名でしょう。

mysql57> SELECT NOW(6), SLEEP(1), NOW(6);
+----------------------------+----------+----------------------------+
| NOW(6)                     | SLEEP(1) | NOW(6)                     |
+----------------------------+----------+----------------------------+
| 2016-12-01 10:16:16.596803 |        0 | 2016-12-01 10:16:16.596803 |
+----------------------------+----------+----------------------------+
1 row in set (1.00 sec)

mysql57> SELECT SYSDATE(6), SLEEP(1), SYSDATE(6);
+----------------------------+----------+----------------------------+
| SYSDATE(6)                 | SLEEP(1) | SYSDATE(6)                 |
+----------------------------+----------+----------------------------+
| 2016-12-01 10:16:29.699001 |        0 | 2016-12-01 10:16:30.699117 |
+----------------------------+----------+----------------------------+
1 row in set (1.00 sec)

NOW関数はステートメントの中で2回呼ばれていますが、同じ値を返します。
それに対しSYSDATE関数は「関数が実行された時点の時刻」を返すため、SLEEP(1) をはさんでもう一度実行された時には違う結果を返します。

さてさて、これがレプリケーションをはさむとどうなるか。


master [localhost] {msandbox} (d1) > SELECT @@binlog_format;
+-----------------+
| @@binlog_format |
+-----------------+
| STATEMENT       |
+-----------------+
1 row in set (0.00 sec)

master [localhost] {msandbox} (d1) > INSERT INTO sbr VALUES ('NOW', NOW(6)), ('SYSDATE', SYSDATE(6));
Query OK, 2 rows affected, 1 warning (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 1

master [localhost] {msandbox} (d1) > SHOW WARNINGS;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                  |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1592 | Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it uses a system function that may return a different value on the slave. |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

master [localhost] {msandbox} (d1) > SELECT * FROM sbr;
+---------+----------------------------+
| func    | dt                         |
+---------+----------------------------+
| NOW     | 2016-12-01 11:35:23.865477 |
| SYSDATE | 2016-12-01 11:35:23.871343 |
+---------+----------------------------+
2 rows in set (0.00 sec)

slave1 [localhost] {msandbox} (d1) > SELECT * FROM sbr;
+---------+----------------------------+
| func    | dt                         |
+---------+----------------------------+
| NOW     | 2016-12-01 11:35:23.865477 |
| SYSDATE | 2016-12-01 11:35:23.874431 |
+---------+----------------------------+
2 rows in set (0.00 sec)

SBRの場合、NOW関数はマスターと同じ値が記録されるけれど、SYSDATE関数は同じ値が記録されない。「スレーブでSQLをリプレイした時の現在時刻」になってしまう。


master [localhost] {msandbox} (d1) > SELECT @@binlog_format;
+-----------------+
| @@binlog_format |
+-----------------+
| ROW             |
+-----------------+
1 row in set (0.00 sec)

master [localhost] {msandbox} (d1) > INSERT INTO rbr VALUES ('NOW', NOW(6)), ('SYSDATE', SYSDATE(6));
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

master [localhost] {msandbox} (d1) > SELECT * FROM rbr;
+---------+----------------------------+
| func    | dt                         |
+---------+----------------------------+
| NOW     | 2016-12-01 11:36:06.626105 |
| SYSDATE | 2016-12-01 11:36:06.626309 |
+---------+----------------------------+
2 rows in set (0.00 sec)

slave1 [localhost] {msandbox} (d1) > SELECT * FROM rbr;
+---------+----------------------------+
| func    | dt                         |
+---------+----------------------------+
| NOW     | 2016-12-01 11:36:06.626105 |
| SYSDATE | 2016-12-01 11:36:06.626309 |
+---------+----------------------------+
2 rows in set (0.00 sec)

それに対してRBRは関数をリプレイしないのでどちらでも問題なく動く(そもそも「関数の結果」をバイナリーログに書き込むため、スレーブではリプレイされない)
MIXEDの場合、SYSDATE関数がレプリケーションアンセーフな関数(非決定性関数)のためRBRにフォールバックする。


NOW関数の仕掛けはこうだ。

- クエリー開始時にTIMESTAMPセッション変数の中に値が入っているかどうかを探す
  - ちなみにTIMESTAMP変数は32ビット符号つきかつ負値をバリデーションではじいている、つまりunixtime
  - TIMESTAMP変数が0ならば、thd->query_start() を読んでTIMESTAMP変数に入れる
- TIMESTAMP変数をunixtimeからDATETIMEに変換して返す
  - クエリー終了まで、一度セットされたTIMESTAMP変数を使い続ける
- クエリー終了後、もとのTIMESTAMP変数が0だった場合は0に戻す


mysqlbinlogを出すと、しょっちゅう SET TIMESTAMP= .. で指定されているのを見ることができる。これは、そういう(NOW関数をスレーブでも決定性にするための)意味だったのだ。

# at 6439
#161201 11:35:23 server id 1  end_log_pos 6526 CRC32 0x2e37a667         Query   thread_id=9     exec_time=0     error_code=0
SET TIMESTAMP=1480559723.865477/*!*/;
BEGIN
/*!*/;
# at 6526
#161201 11:35:23 server id 1  end_log_pos 6671 CRC32 0xab51a6f0         Query   thread_id=9     exec_time=0     error_code=0
SET TIMESTAMP=1480559723.865477/*!*/;
INSERT INTO sbr VALUES ('NOW', NOW(6)), ('SYSDATE', SYSDATE(6))
/*!*/;
# at 6671
#161201 11:35:23 server id 1  end_log_pos 6702 CRC32 0xa975b625         Xid = 73
COMMIT/*!*/;

ちなみに、RAND関数も引数なしだとTIMESTAMP変数をシードにするので、RAND関数もレプリケーションセーフな関数(スレーブでリプレイしても結果がズレない)として扱われます。

豆知識でした。


明日 12/2 は @kakuka4430 さんです!

2016年11月22日火曜日

MySQL 5.7.16, 8.0.0現在、slave_skip_errorsはエラーコードの3000番台をスキップできない

日々の覚書: MySQL 5.7.6でエラーコードが変わった件 の時からMySQLのエラーコードに3000番台が加わった。

それまで1000番台はサーバーサイド、2000番台はクライアントサイドだけだったものが、3000番台もサーバーサイドのエラーコードとして設定されている(エラー番号はMySQL 5.7.16現在)



エラー番号 マクロ 備考
1000~1884 ER_*, WARN_* サーバーサイドエラー
2000~2062 CR_* クライアントサイドエラー(libmysqlclientの場合)
3000~3193 ER_* サーバーサイドエラー(5.7.6から)
例えばレプリケーションのI/OスレッドはMySQLサーバーの中にいるけれど実際はクライアントなので2000番台のエラーもハンドルする必要があったりして、ちょこちょこと"2000より小さければサーバーサイドエラー", "2000以上ならクライアントサイドエラー" みたいな判定があったりする。


もうお気付きだろう。3000番台のエラー番号は2000より大きい。

MAX_SLAVE_ERROR マクロは2000で、

https://github.com/mysql/mysql-server/blob/mysql-5.7.16/sql/rpl_slave.h#L66

ここ とか ここ とか ここ とかで err_code < MAX_SLAVE_ERROR で判定されている。


というわけでバグレポートしたのでしたん。

MySQL Bugs: #83184: Can't set slave_skip_errors > 3000