yukiです。そろそろクリスマスですね。みんな浮かれていればいいと思います!最近急に目が悪くなって、ツリーの赤色電球と居酒屋の赤提灯の色が判別出来なくなってきました。嘘です。
今回は、みんな大好きメッセージキュー、Q4Mを触ってみた感想を今更ながらレポートします。
Q4M
公式ページはこちらhttp://q4m.31tools.com/
Q4Mはサイボウズラボの奥 一穂氏が開発されており、MySQLの5.1以上でストレージエンジンとして利用できるメッセージキューで、MySQLプラグインとしてGPLライセンスで配布されております。
特長
MySQLのストレージエンジンとして利用できるので、テーブル作成時にストレージエンジンを指定するだけで利用できます。
CREATE TABLE hoge (
...
) ENGINE = QUEUE
キューの作成(enqueue)は通常のレコード操作と同様にINSERTで行い、キューの取り出し(dequeue)も同様にSELECTで行えるので、シンプルに扱う事が出来ます。
導入事例
公式ページには導入事例として以下の2つが載っています。
インストール
最新版は0.8.9となっています。こちらからバイナリ・ソース版と用意されているので、適切なものを選んでインストールします。
GCCのバージョンによってはコンパイルに失敗するエラーがありましたが、現バージョンでは解消されて手軽にインストールできるようになりました。
インストールが終わると、MySQLにプラグインとして認識されているのがわかります。
mysql> show plugins;
+------------+----------+----------------+--------------------+---------+
| Name | Status | Type | Library | License |
+------------+----------+----------------+--------------------+---------+
| binlog | ACTIVE | STORAGE ENGINE | NULL | GPL |
| partition | ACTIVE | STORAGE ENGINE | NULL | GPL |
| ARCHIVE | ACTIVE | STORAGE ENGINE | NULL | GPL |
| BLACKHOLE | ACTIVE | STORAGE ENGINE | NULL | GPL |
| CSV | ACTIVE | STORAGE ENGINE | NULL | GPL |
| FEDERATED | DISABLED | STORAGE ENGINE | NULL | GPL |
| MEMORY | ACTIVE | STORAGE ENGINE | NULL | GPL |
| InnoDB | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MyISAM | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MRG_MYISAM | ACTIVE | STORAGE ENGINE | NULL | GPL |
| QUEUE | ACTIVE | STORAGE ENGINE | libqueue_engine.so | GPL |
+------------+----------+----------------+--------------------+---------+
実際に動かしてみる
まずはテーブルを作成します。
mysql> CREATE TABLE queue_table (
message TEXT NOT NULL,
priority TINYINT UNSIGNED NOT NULL DEFAULT 10
)
ENGINE = QUEUE
次に、実際に処理するキューを作るため、キューをINSERTします。
mysql> INSERT INTO queue_table (message) VALUES ('ms1'), ('ms2') ,('ms3');
dequeueしてみます。dequeueにはqueue_waitというfunctionを利用します。
mysql> SELECT queue_wait('queue_table');
+---------------------------+
| queue_wait('queue_table') |
+---------------------------+
| 1 |
+---------------------------+
1 row in set (0.00 sec)
成功すると、結果として1が返って来ます。
キューを取り出しているこの状態をオーナーモードと呼び、この状態だと、該当のテーブルからは1レコードだけ引けるようになります。
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms1 | 10 |
+---------+----------+
1 row in set (0.00 sec)
取り出して処理が完了した場合、queue_endを呼ぶと、取り出したキューが削除されます。同時にオーナーモードが解除され、非オーナーモードに戻ります。
mysql> SELECT queue_end();
+-------------+
| queue_end() |
+-------------+
| 1 |
+-------------+
1 row in set (0.00 sec)
非オーナーモードに戻ったので、通常のSELECTを行うと、残りの全キューを引くことができます。
ただし、別のコネクションでオーナーモードになっている場合、そのコネクションで得たキューは表示されません。(後述。)
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms2 | 10 |
| ms3 | 10 |
+---------+----------+
2 rows in set (0.00 sec)
またこのとき、処理中に例外エラーなどでキューを戻したい場合は、queue_abort()を利用すると直前に取り出したキューを戻します。queue_wait()→queue_abort()した際の流れです。
mysql> SELECT queue_wait('queue_table');
+---------------------------+
| queue_wait('queue_table') |
+---------------------------+
| 1 |
+---------------------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms2 | 10 |
+---------+----------+
1 row in set (0.00 sec)
mysql> SELECT queue_abort();
+---------------+
| queue_abort() |
+---------------+
| 1 |
+---------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms2 | 10 |
| ms3 | 10 |
+---------+----------+
2 rows in set (0.00 sec)
オーナーモードと非オーナーモード
queue_wait()を呼ぶとオーナーモードに入り、先述したように、そのコネクション内では1行のみ取得できるようになります。
また、別のコネクションでは、他コネクションでオーナーモードで取得した行以外を取得できるようになります。
コネクション1
% mysql -u test test
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms2 | 10 |
| ms3 | 10 |
+---------+----------+
2 rows in set (0.01 sec)
コネクション2
% mysql -u test test
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms2 | 10 |
| ms3 | 10 |
+---------+----------+
2 rows in set (0.01 sec)
コネクション1でオーナーモードに入る
mysql> SELECT queue_wait('queue_table');
+---------------------------+
| queue_wait('queue_table') |
+---------------------------+
| 1 |
+---------------------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms2 | 10 |
+---------+----------+
1 row in set (0.00 sec)
コネクション2でキューを全件表示すると、コネクション1で引いたキューは表示されない。
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms3 | 10 |
+---------+----------+
1 row in set (0.00 sec)
優先度別キュー取出
優先度低めのキューテーブルを作り、優先して取得したい順にqueue_waitの引数に加えます。
mysql> CREATE TABLE low_priority_queue (
message TEXT NOT NULL,
priority TINYINT UNSIGNED NOT NULL DEFAULT 10
)
ENGINE = QUEUE;
mysql> INSERT INTO low_priority_queue (message) VALUES ('lm1'), ('lm2'), ('lm3');
mysql> SELECT queue_wait('queue_table', 'low_priority_queue');
+-------------------------------------------------+
| queue_wait('queue_table', 'low_priority_queue') |
+-------------------------------------------------+
| 1 |
+-------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
成功すると、取得できるテーブルの順の番号が返って来ます。(上記は1なのでqueue_table、2であればlow_priority_queueになります)あとは通常通り取得します。
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms2 | 10 |
+---------+----------+
1 row in set (0.00 sec)
ここの挙動がよくわかりにくく、どなたか分かる方がいたら教えて頂ければ大変嬉しいのですが、優先度が高いテーブル(queue_table)の中身が空の時、タイムアウトを指定しないでqueue_waitすると「ゼロ」(データ無し)と返って来ます。
2009/12/21追記: 複数指定する場合は、必ずタイムアウト引数を付けなくてはいけないので、この用法は間違いでした。お詫びいたします。
mysql> TRUNCATE queue_table;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT queue_wait('queue_table', 'low_priority_queue');
+-------------------------------------------------+
| queue_wait('queue_table', 'low_priority_queue') |
+-------------------------------------------------+
| 0 |
+-------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
タイムアウトを与えてやると、正しく取得できます。下記の場合はqueue_tableにレコードがないので、low_priority_queueから取得するため2番が返り、期待通りの動作となります。
mysql> SELECT queue_wait('queue_table', 'low_priority_queue', 10);
+-----------------------------------------------------+
| queue_wait('queue_table', 'low_priority_queue', 10) |
+-----------------------------------------------------+
| 2 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM low_priority_queue;
+---------+----------+
| message | priority |
+---------+----------+
| lm1 | 10 |
+---------+----------+
1 row in set (0.00 sec)
条件付き取り出し
簡易なWHERE句のようにして取得することが可能です。queue_waitのテーブルを指定する引数に、条件を付加して取得します。
下記の例は優先度2で新たなキューを発行し、優先度3以下で取得するようにしてみます。
mysql> INSERT INTO queue_table(message, priority) values ('blocker', 2);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| ms1 | 10 |
| ms2 | 10 |
| ms3 | 10 |
| blocker | 2 |
+---------+----------+
4 rows in set (0.00 sec)
mysql> SELECT queue_wait('queue_table:priority<3');
mysql> SELECT * FROM queue_table;
+---------+----------+
| message | priority |
+---------+----------+
| blocker | 2 |
+---------+----------+
1 row in set (0.00 sec)
リレー
別のサーバにキューをリレーする事が出来ます。同梱されているq4m-forwardというperlスクリプトを起動すると、無限ループでキューを待ち、別のサーバへ転送するような処理になっています。
同様の処理であれば、自作のスクリプトで同様の処理が可能です。
daemontoolsなどを使えば、割とお手軽に転送させることが出来るかと思います。
まとめ
いかがでしたでしょうか。メッセージキューイングというとActiveMQやTheSchwartzなど、いろいろなソリューションがあると思いますが、キューイング方法などかなり手軽に扱えるので、1度試してみてはいかがでしょうか。
試している最中、queue_wait()してオーナーモード中にtruncateしてしまい、drop databaseすら効かなくなってrm -r /var/lib/mysql/testしたりしましたが、ご愛敬ということで。