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

2016年11月18日金曜日

WindowsでもRabbitとRabbiterを使いたい

初めて成功したので忘れないうちにメモ。

0. CygwinのRubyにパスが通ってると、rabbitを起動した時にCygwinのRubyを掴もうとすることがあってダメぽになることがある。

1. RubyInstallerでRubyをインストール。取り敢えず2.3.1で上手く動いた。 Downloads

2. 対応したDevKitをダウンロードして適当な場所に展開(取り敢えず C:\Ruby23-x64\DevKit に展開して上手くいってる) Development Kit · oneclick/rubyinstaller Wiki によると "Download it, run it to extract it somewhere (permanent)." らしい。消しちゃダメなのか。

> ruby dk.rb init
> ruby dk.rb install


2-1. `ruby dk.rb install` で "Invalid configuration or no Rubies listed. Please fix 'config.yml'" と言われる。ruby.exeの場所が見付けられていないので、config.ymlを修正してもう一度。

3. これでやっと `gem install rabbit rabbiter` できるかと思いきや、SSLのエラーを食らう。

ERROR:  Could not find a valid gem 'rabbit' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (https://api.rubygems.org/specs.4.8.gz)
ERROR:  Could not find a valid gem 'rabbiter' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (https://api.rubygems.org/specs.4.8.gz)

bundle install がこけるようになった の "rubygems-updateをインストールする" で解決した。 bundle install がこけるようになった(改訂版) には "rubygems 2.4にはバグがあってこの方法はとってはいけない" みたいなことも書いてあるけれど、2016年10月現在の rubygems-update の最新版は2.6.7だったのでもういいかなと思ってやってみた。
取りあえず問題は出ていない。

> gem install --local rubygems-update-2.6.8.gem
> update_rubygems

4. ようやくRabbitとRabbiterが入った。後は楽しむだけ!

> gem install rabbit
> gem install rabbiter


( ´-`).oO(ところで、rabbitのsample.rdとかsample.mdとかを動かそうとするとrabbitがハングするの俺だけ…?