BizStationブログ

ビズステーション株式会社の公式ブログです。

Transactd 2.1はさらに高速で低負荷に

まもなくリリースされるTransactd 2.1は、2.0に比べてクエリーの速度が大幅に高速になります。

SQLより高速なクエリー

2.1ではJoinなどに関する処理を徹底的に見直し、SQLを凌ぐクエリーレスポンスを実現しました。

TransactdのJoinを含むクエリーは、テーブルごとにクライアントからリクエストを送信するため、サーバー側ですべてを処理して結果を返すSQLに比べて通信回数が多くなり、レスポンスにおいては不利です。特に結果レコード数が少ない場合は、処理時間に比べて通信時間の占める割合が大きく、高速に処理しても差が出にくくなります。にもかかわらず、Transactd2.1はSQLより高速にJoin処理ができます。

下図は、LinuxUbuntu 14.04.1)上のPHP5.6クライアントでのレスポンスを、SQLMySQL 5.6.21, PDO)と比較したグラフです。Readは単純な1レコードの読み取り、Join_xRowはx行の結果を返すクエリーです。すべて、5クライアントで数千回の連続実行をして、1回あたりの平均を取っています。(ネットワークは1Gbps NIC

図1 (縦軸は秒)
f:id:bizstation:20141128132100p:plain

//クエリーのSQL文 
select `user`.`id`,`user`.`名前`,`user`.`group`,`groups`.`name` as `group_name`
from `user` left join `groups` on `user`.`group`  = `groups`.`code`
where `user`.`id` > ? and `user`.`id` <= ?
order by `group_name`;
//Transactdのコード
$q->select('id', '名前', 'group')->where('id', '<', '?');
$rs = $activeTable1->keyValue($id)->read($q);
$activeTable2->alias('name', 'group_name');
$q->reset()->select('group_name');
$activeTable2->join($rs, $q, 'group');
$rs->orderBy('group_name');

高速化のポイント

プリペアードクエリー

プリペアードクエリー機能が新しく追加されました。これによりクエリーの実行速度の向上、CPU負荷、ネットワーク消費の低減をしました。

TransactdのアクセスはAPIベースです。SQLのような言語構文解析はありません。しかし、クライアント側ではシリアライズ、サーバー側ではデシリアライズや比較順の決定などの負荷があります。
プリペアードクエリーは、(サーバー、クライアントともに)クエリー処理時に行われる準備処理を事前にキャッシュします。クエリー実行時は、プレースホルダーの値のみやり取りして処理を行います。

下図は、プリペアードクエリーを使用したときとそうでない時のレスポンス比較の図です。約12%レスポンス速度が向上しています。

15 Client同時実行 Joinを含む1行を返すクエリー実行速度の平均値(縦軸は秒)
f:id:bizstation:20141128165121p:plain

recordsetクラスの実装見直し

図1のJoinを含むテストでは、Transactd、SQLともに結果セットを[行][列]の連想配列の形式で値を読み出せるようにするところまで実行しています。サーバーから受け取ったデータを如何に効率よく連想配列のように取り出せるかについて、メモリのアロケート回数、コピー回数の低減を徹底的に進め、実行速度の向上とメモリの利用効率を高めました。

SWIGバインディングコードの最適化

PHPRubyの言語バインディングSWIGを使用して生成しています。このSWIGによって生成されるバインディングコードを見直し、PHPRubyでの処理速度の向上と省メモリーを実現しました。
SWIGは関数のオーバーロードやデフォルト引数があると非常に冗長なコードを生成してしまいます。また、メソッドチェーンのためのthisを返す場合も新しいリーソースを確保してしまいます。2.1はこれらを取り除き最適なコードにすることで、バインディングのオーバーヘッドを大幅に小さくしています。

ガベージコレクタの影響を受けない結果セットメモリ管理

PHPRubyActiveXなどの言語バインディングでも、結果セット(recordset)のメモリ確保と管理はC++のTransactdクライアント内部で行われます。「recordsetクラスの実装見直し」と「SWIGバインディングコードの最適化」によって、ガベージコレクタによる「引っかかるような」もたつきがほとんどなくなりました。常にスムーズに処理が行われます。

下の動画はPHPにて3つのテーブルをJoinし、500行の結果を返すクエリーを2000回連続して実行したときの、PDOとTransactdの比較動画です。20回ごとに1つのプログレスが進んでいきます。上がPDO、下がTransactdです。ともに同じ結果を返すクエリーです。

Transactdはスムーズに処理が進んでいく様子をご覧ください。


Transactd 2.1 vs PDO(MySQL) - YouTube

このクエリーでは、MySQL+PDOが32.4秒、Transactdが9.4秒で3倍以上の速度差が出ています。

SQLの数分の1のサーバーCPU負荷

一連のbenchmark実行時のサーバーCPU負荷を計測しました。 図1のJoin 100レコードのReadテスト時のCPU負荷の変化を示します。
(図1では5クライアントの時のデータを載せていますが、実際の計測は1~50クライアントまで連続的に計測しています。)

図を見るとTransactdはCPU負荷が小さいことがわかります。(面積で約 1/2)さらにこのとき約1.6倍のスループットが出ていましたので、実質MySQLの約1/3のCPU負荷になっています。
データの転送量の少なさ、SQL文の解析、Joinの結合処理、OrderByのソート処理がないことが要因かと思います。サーバーは、SQLに比べ約3倍のスケーリングを実現できます。
f:id:bizstation:20141128181255p:plain

まとめ

2.0のリリース以降、クエリーパフォーマンスの改善を図ってきました。この改善でサーバー、クライアントとも処理時間は限りなく少なくなりました。

ここで提示したデータはありのままのデータです。出来る限り実際の使用状況に近くするため、1GbpsのNICでケーブルで接続して計測しています。ローカルや同一ホスト内の仮想サーバー間通信ではありません。

今回示したデータは、SQLとの差が少し少な目に見えるかも知れません。これは、処理時間において、1Gbps NICによるTCP/IP通信のレイテンシの占める割合が大きく、この部分はSQLもTransactdもほぼ同様にかかるためです。他のNoSQL(Radisやmemcachedなど)でも全く同様です。

参考までに、ローカルでのサーバーとクライアントの通信に、共有メモリによるプロセス間通信とTCP/IPを使った場合の差を示します。共有メモリによる方法は通信時間は限りなくゼロに近いものです。点線で囲まれた部分が通信時間に相当します。通信時間の比率の高さがよくわかると思います。

処理時間に占める通信時間(縦軸はミリ秒)
f:id:bizstation:20141128205552p:plain

サーバー間のネットワークもいよいよ10Gbpsの時代になりつつあります。サーバーとクライアントを10Gbps NICで接続すればSQLとの差はさらに大きなものになると思います。

先日、ブログのコメントに、更新処理における排他制御に関する質問をいただきましたので、詳しく回答させていただきました。その他、使い方など何か不明なことがありましたら是非コメントください。お待ちしています。
(いただいた質問は下記記事にあります。)

Transactd 2.0 その3 データベーススケーリング - BizStationブログ