現場で仕事をしていると、既存サービスの保守開発というか、新しい機能ももちろん開発するのだけれど、ミドルウェアはローンチ当初のバージョンを使っていることが多い。たまにはこうしてある程度まとまった書籍で知識を詰めるのも大事だなと思ったり。
で、redisのリアルタイムランキングの実装の説明でRDBMSだと難しいって下記の説明があまりピンと来なかったので検証してみる。
50位から60位までの人をリストアップしようとすると、とたんに計算量が多くなってしまいます。というのも、ランキングテーブルに存在するレコード全件を対象としたデータ操作になるため、データ数によっては計算量が爆発してしまうことになるからです。
これはスコアにインデックスを貼れば爆発はしないような気がする。
とりあえず、実験のために下記テーブルを作り10万件程度のデータを入れてみる。
CREATE TABLE `ranking` (
`user_id` int(10) unsigned NOT NULL,
`score` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`user_id`),
KEY `score` (`score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
書籍のサンプルにあるようなhistory_versionやcreated_onなどは省略。で、50位から60位までの人をリストアップするには、普通にlimitでいい気がする。
mysql> select user_id, score
from ranking order by score desc, user_id limit 50, 10;
+---------+--------+
| user_id | score |
+---------+--------+
| 50029 | 999116 |
| 22555 | 999091 |
| 97860 | 999052 |
| 53493 | 999042 |
| 60475 | 999042 |
| 44599 | 999032 |
| 99891 | 999018 |
| 86485 | 998923 |
| 71413 | 998855 |
| 8096 | 998851 |
+---------+--------+
10 rows in set (0.07 sec)
若干遅い。でも、サービスとして耐えられないとも思わないけれど。何位かを表示するには自分のスコアより高いスコアを持っている人の数に1を足す。
mysql> select user_id, score,
(select count(1)+1 from ranking t0 where t0.score > ranking.score) rank
from ranking order by score desc, user_id limit 50, 10;
+---------+--------+------+
| user_id | score | rank |
+---------+--------+------+
| 50029 | 999116 | 51 |
| 22555 | 999091 | 52 |
| 97860 | 999052 | 53 |
| 53493 | 999042 | 54 |
| 60475 | 999042 | 54 |
| 44599 | 999032 | 56 |
| 99891 | 999018 | 57 |
| 86485 | 998923 | 58 |
| 71413 | 998855 | 59 |
| 8096 | 998851 | 60 |
+---------+--------+------+
10 rows in set (0.37 sec)
まぁ、計算増えるし遅くはなるわな。結論としては、自分がリアルタイムなランキングを作るときは、MySQLでもできるとは信じつつも、試してみたいのでRedisを採用する。