2014年だけど Q4M の紹介をしてみるよ

先日 [MySQL Casual Talks vol.7](http://mysql-casual.connpass.com/event/9767/) で LT をしてきました。
LT では [Ruby(Rails) からカジュアルに Q4M を使える何かを作ったお話](https://speakerdeck.com/ryopeko/mysql-casual-talks-vol7) として Q4M 周りのお話をしてきたのですが、そこで冒頭 "Q4M 使ってる人ー" と挙手を求めたら誰一人として手が挙がらなかったことに絶望してこのエントリーをしたためています。
(まーカジュアルだからなーカジュアルだから Q4M 使ってなくてもしょうがないからなー)
(使ってないだけで知っている人はいたかもしれません、あと恥ずかしがり屋さん)

このエントリーでは簡単な Q4M の使い方と特徴について説明していきます。

## Q4M って:question: 
Q4M は簡単に言うと DeNA の奥一穂さんが開発されている MySQL を使ったキューストレージです。
Q4M 自体は MySQL のストレージエンジンとして実装されているので、テーブルにレコードを insert することで enqueue とし、dequeue する場合は select を行うというとてもシンプルなものです。
普通に見ると MySQL 内の1テーブルなので、select でキューの状況を見ることや、詰まっているキューを delete で簡単に除去できるなどの特徴もあります。

## dequeue について :dash: 
実際の dequeue 処理では `select queue_wait('table_name')` とクエリを発行するとキューを待ち受ける状態になり、キューが存在すると(insert されてくると)、そのレコードをロックする形で結果が返ってきます。
ポイントとしては `select queue_wait('table_name')` を実行するとキューが得られるまでずっと待ち状態になるというところです。
といってもタイムアウトも設定でき、 `select queue_wait('table_name', 10)` とすると10秒待ってもキューが入ってこない場合はタイムアウトとなります。
queue_wait によってキューが得られた場合には queue_wait の結果に 1 が返ってきて、キューを取得できたことが通知されます。

```shell-session
mysql> select * from my_queues;
+--------------------------------------+-------+--------+---------------------+
| job_id                               | title | desc   | enqueued_at         |
+--------------------------------------+-------+--------+---------------------+
| 7073bd72-d34f-4f9d-b38a-b47a8331b9ad | foo   | barfoo | 2014-12-13 21:56:57 |
+--------------------------------------+-------+--------+---------------------+
1 row in set (0.00 sec)

mysql> select queue_wait('my_queues');
+-------------------------+
| queue_wait('my_queues') |
+-------------------------+
|                       1 |
+-------------------------+
1 row in set (0.00 sec)
```

この状態で `select * from my_queues` とするとキュー自体を取得することができます。

```shell-session
mysql> select * from my_queues;
+--------------------------------------+-------+--------+---------------------+
| job_id                               | title | desc   | enqueued_at         |
+--------------------------------------+-------+--------+---------------------+
| 7073bd72-d34f-4f9d-b38a-b47a8331b9ad | foo   | barfoo | 2014-12-13 21:56:57 |
+--------------------------------------+-------+--------+---------------------+
1 row in set (0.00 sec)
```

### dequeue されたキュー :innocent: 
dequeue されたキューは queue_wait を発行したコネクション以外からは見えなくなります。
つまりレコードが1件しかない状態で queue_wait を実行し、dequeue を行った場合、他のコネクションで `select * from table_name` とするとゼロ件となります。
先ほどのキューを取得した状態で別のコネクションからテーブルを select して確認してみると、先ほど select で取得できたキューが見えない状態になっていることがわかります。

```shell-session
mysql> select * from my_queues;
Empty set (0.00 sec)
```

この様にキューの排他制御がとてもシンプルに行えることも Q4M の特徴のひとつです。

## 非同期処理で失敗したキューの取り扱い :scream: 
キューを取得した後は、非同期に実行したかった処理を実行することになりますが、もしここの処理が予期せぬ例外などで失敗した場合にキューは破棄されずにテーブルに自動で戻されます。
また、例外をキャッチするなどした場合に明示的にキューを戻したいという場合も簡単に行うことが可能です。
その場合は `select queue_abort()` とクエリを実行するとキューはテーブルに戻ります。

```shell-session
mysql> select queue_abort();
+---------------+
| queue_abort() |
+---------------+
|             1 |
+---------------+
1 row in set (0.00 sec)

mysql> select * from my_queues;
+--------------------------------------+-------+--------+---------------------+
| job_id                               | title | desc   | enqueued_at         |
+--------------------------------------+-------+--------+---------------------+
| 7073bd72-d34f-4f9d-b38a-b47a8331b9ad | foo   | barfoo | 2014-12-13 21:56:57 |
+--------------------------------------+-------+--------+---------------------+
1 row in set (0.00 sec)
```

この様に明示的に queue_abort を呼ばない場合でも自動でキューがテーブルに戻ることから、非同期処理内でのバグや意図していない動作によるキューロストを簡単に防ぐことができます。
これは Q4M の数ある利便性の中でも特に際立つ部分だと思います。

## キューの破棄 :clap: 
前述のとおり非同期処理で正しく処理されたキューはそのままにしておくと自動的にテーブル戻ってしまいます。
キューを正しく破棄する場合には `select queue_end()` を実行します。

```shell-session
mysql> select queue_end();
+-------------+
| queue_end() |
+-------------+
|           1 |
+-------------+
1 row in set (0.01 sec)

mysql> select * from my_queues;
Empty set (0.00 sec)
```

## キューテーブルの統計情報 :chart_with_upwards_trend: 
Q4M ではキューテーブルの統計情報を出力する独自の function を利用することができます。
実はこれは Q4M のチュートリアルページにも書いていない機能なので、もしかしたら知らない人も多いかもしれません :information_desk_person: 

統計情報を出力するには `select queue_stats('table_name')` を実行します。

```shell-session
mysql> select queue_stats('my_queues');
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| queue_stats('my_queues')                                                                                                                                        |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| rows_written: 2
rows_removed: 1
wait_immediate: 2
wait_delayed: 1
wait_timeout: 0
restored_by_abort: 1
restored_by_close: 1
bytes_total: 122
bytes_removed: 61
 |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```

詳細な見方はこちらの [gist](https://gist.github.com/kazuho/4122754) を参照してください。

## まとめ :sushi: 
* Q4M は MySQL を使ったキューストレージです
* キューの排他制御が簡単にできます
* select, delete もそのまま使えるので状況参照や削除も簡単にできます
* Q4M 難しくないよ、むしろ簡単だよ

## おまけ :trollface: 
Rails から Q4M を使いたい場合は sidekiq からの移行も簡単な [shinq](http://rubygems.org/gems/shinq) というものがありますので是非ご活用ください。