「NoSQLはRDBMSより高速」は本当か?
NoSQLが登場したころ(あるいは、今でも)、「NoSQLはRDBMSに比べて20倍高速」などというホワイトペーパーが出たりすることがあり、「20倍」という表現が独り歩きすることがよくありました。この倍率については、レコード長やページサイズ、その他もろもろのチューニング項目をどうするかで、いかようにも変化しますからほぼ意味がないうえ、実際のシステムでそれほどの差が出ることはありません。
今回は、memcached(Key-Value Store型のNoSQL)と、MySQLをNoSQL(Key-Value Store)として利用できるプラグインのHandlerSocketと、MySQLを、もう少し現実的な実験を通して比較し、処理速度の違いと、NoSQLを選ぶポイントについて解説します。
本稿では、HandlerSocketの産みの親である松信嘉範氏のブログ(http://yoshinorimatsunobu.blogspot.com/2010/10/using-mysql-as-nosql-story-for.html)を参考にさせていただきます。
松信氏が行った実験
MySQLの参照データはすべてメモリにキャッシュされている状態で、1GbpsのNIC 4ポート搭載のサーバに対して、リスト1(テーブル構造はリスト2)のSQLをmysqlslap注1を用いて1,000万回処理し、1秒間に何件処理できるかを測定しました。
同様の条件で、C/C++で書いたクライアントソフトから、memcached、HandlerSocketのそれぞれで1,000万回処理し、1秒あたりの処理件数を測定しました。
結果は図1のようになりました。SQLに対して、memcachedは4倍速く、HandlerSocketは7倍速いという結果になりました。
速度差を処理を分解して考えてみる
松信氏の実験は1,000万回処理したときの差ですが、1回の処理をさらに、
- リクエスト(ネットワーク処理)
- SQLのオーバーヘッド(SQLだけにある)
- データの読み取り処理
- レスポンス(ネットワーク処理)
に分解して、どこの処理に時間がかかっているかを推測してみましょう。
HandlerSocketはMySQLのAPIを利用しSQLのオーバーヘッド(第3回で挙げた「DBエンジンがSQL実行前に行う内部処理」)をショートカットしています注2。そのため、データの読み取り処理の部分だけを比べればSQLと同じ処理時間になります。memcachedもほぼ同じと仮定します。そこから、多くのホワイトペーパーなどで報告されているとおり、SQLのオーバーヘッドとデータの読み取り処理の時間が20倍以上になるように調整します。
次に、リクエストとレスポンスを加え、松信氏の実験結果のようにSQLの全体の処理時間がmemcashedの4倍程度、HandlerSocketの7倍程度に調整すると、図2のようになります。
それぞれの倍率はレコード長やページサイズ、そのほかのパラメータを調節することで簡単に変わりますから、倍率(数値)には意味がありませんが、大きな傾向は変わりません。
ここで筆者が言いたいことは、処理時間のほとんどが「ネットワーク処理とSQLのオーバーヘッドで費やされている」ということです。たった1レコードを取得するだけでも、第3回で挙げた「DBエンジンがSQL実行前に行う内部処理」が行われているということを思い出してもらえると、図2のSQLのオーバーヘッドのサイズは大げさではないと理解できるのではないでしょうか。また、ネットワークという遅いデバイスを使うことで内部処理を高速化した効果が大幅に打ち消されてしまっていることもわかります。
ぐるぐる系SQL
ORMを利用していると、知らず知らずのうちに大量のSQLを発行するということが起こりがちです。「ぐるぐる系」というのは、「N+1問題」とも言われる問題で、何重にもSQL文のループをまわすような処理のことを言います。たとえば、cityテーブルのCountryCodeに該当する国名をcountryテーブルから取得する際に、まずcityテーブルを全件取得するSQLを1回発行し、その後countryテーブルから国名を取得するSQLをcityテーブルのレコード件数回発行するのが「ぐるぐる系」SQLです。
それに対して、同じ結果を得るのにテーブルをJOINするなどして、1つのSQLで全データを取得する方式を本稿では「一発系」SQLと呼びます。
一発系と比較すべき
memcachedを含むNoSQL(Key-Value Store)を使うと、集計処理などをDBサーバ側で処理できないため、ぐるぐる系にならざるを得ません。NoSQLを使うことでSQLのオーバーヘッドはなくなりますが、ネットワークに関する遅いデバイスを何度も繰り返し利用することになります。
松信氏の実験では、1件だけを繰り返し取得する「SQLのぐるぐる系」と「NoSQLのぐるぐる系」の比較になっています。松信氏の仕事では、プライマリーキーを指定して1件のデータを取得する処理が多いそうです。しかし、そのケースに当てはまらないシステムやゲームもあるでしょう。筆者は「SQLのぐるぐる系」を「一発系SQL」に直すというチューニングの仕事を何度も行っていますが、数十倍〜数千倍のパフォーマンスになることはまったく珍しくありません。つまり、7倍差があっても、「一発系」にできるなら簡単に逆転してしまいます。
もちろん、ユーザアクションに対して1件のレコードしか必要がない要件の場合には、一括で処理することはできませんからNoSQLのほうが速いということが言えます。また、どの程度の処理をまとめることができるかは、ゲーム(システム)の要件によってまちまちですから、どちらが速いと決めることはできず「個々に判断が必要」ということになります。傾向としては、処理が複雑になるほどSQLのほうが速くなると言えます。
ユーザアクションに対する処理件数が選択の決め手
残念なことに筆者の経験上、「ぐるぐる系」と「一発系」を比較対象にしている事例は見たことがありません。そのような検証をまったくせずに、「NoSQLは○○倍速い」という独り歩きした謳い文句を信じてNoSQLを選択しているプロジェクトも数多くありました。SQLか、NoSQLかを決めるには、「ユーザアクションに対して1件のレコードだけを処理する」機能がどれぐらいあるかを考えなければいけません。そのような要件が多い場合には、NoSQLに適したゲーム(システム)ということになります。「ユーザアクションに対して複数のレコードを処理する」機能が多いのであればSQLのほうが有利です。
多くの場合、「どちらもそれなりにある」ということになり、とくにゲーム系では、「ユーザアクションに対して1件のレコードだけを処理する」ということが業務系に比べて多くなりがちです。そのような場合はSQLと、HandlerSocketやMySQL ClusterにあるNoSQL APIを併用することが望ましいでしょう注3。