handlersocket 20140218
TRANSCRIPT
HandlerSocket 2.0
2014/02/18 @ 渋谷ヒカリエ DeNA セミナールーム
株式会社ディー・エヌ・エー樋口 証
3 行でまとめると
• HandlerSocket は、 MariaDB ・ MySQL の「非 SQL 」フロントエンド
• 性能向上、通信量削減、メモリ使用量削減等の効果がある
• version 2 でクエリ解釈ロジックを外部モジュールで定義できるようになり、従来よりも複雑なクエリをサポート
0
200000
400000
600000
800000
1000000
1200000
1 2 4 8 16 32 64 128
256
512
1024
2048
4096
8192
1638
4
3276
8
# of concurrent connections
quer
ies
per
seco
nd
SELECT querySELECT query (thread pool)HandlerSocket
HandlerSocket 開発のねらい
• DB キャッシュ用サーバを置くことによるシステムの複雑化や不整合問題を回避すること
• 予測可能で安定した性能を出すことで、性能問題起因のシステム障害を起こさないようにすること
• 同時接続数や通信量などのネックを回避し、システム全体のスケーラビリティを確保すること
基本的な情報
HandlerSocket を使うには
• MariaDB 5.3 以降に含まれている– MariaDB のサイトにドキュメントもある
https://mariadb.com/kb/en/handlersocket/
• インストール手順概略 :– my.cnf にいくつか設定を書く– 以下のクエリを実行し plugin をロード
install plugin handlersocket soname ‘handlersocket.so’
– telnet でポート 9998 に繋ぎ、改行を打つと反応が返ってくれば ok
構成
Handler Interface
Innodb MyISAM Other storage engines …
SQL Layer HandlerSocket Plugin
Listener for libmysqlclient
libmysqlclient libhsclient
Applications
mysqld
client app
port 3306port 9998, 9999
HandlerSocket クライアント
• 各言語のクライアントライブラリがあるhttps://github.com/DeNA/HandlerSocket-Plugin-for-MySQL
• プロトコルが単純なので、クライアントライブラリを使わずに実装することも可能
実行例
• create table db1.table1 (k int key, v char(20))• insert into db1.table1 values (234, 'foo'), (678, ‘bar’)
$ telnet localhost 9998Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.P 0 db1 table1 PRIMARY k,v0 10 = 1 2340 2 234 foo0 = 1 6780 2 678 bar
db1.table1 の PK を開く
k = 234 を検索
k = 678 を検索
HandlerSocket の機能
HandlerSocket の主な機能 ( 参照系 )
• Primary Key や Unique Key を使った行取得
• 範囲取得– 比較条件に使える演算子は =, >=, >, <=, <
HandlerSocket の主な機能 ( 更新系 )
• 参照クエリで得た行の UPDATE と DELETE• 行の INSERT• Atomic な Increment/Decrement
• 更新系クエリは row-based の形式でバイナリログに記録される– MySQL のレプリケーション機能を使える
• ACID特性は MySQL と同じ– 書き込みは同期的、 ( 設定によるが )durable
MySQL と HandlerSocket の通信プロトコル比較
MySQL で通信内容が冗長になるケース
write(3, "L\0\0\0\3select column0,column1,column2,column3,column4 from db_1.table_1 where k=15", 80) = 80
read(3, "\1\0\0\1\0056\0\0\2\3def\4db_1\7table_1\7table_1\7column0\7column0\f\r\0<\0\0\0\375\200\0\0\0\0006\0\0\3\3def\4db_1\7table_1\7table_1\7column1\7column1\f\r\0<\0\0\0\375\200\0\0\0\0006\0\0\4\3def\4db_1\7table_1\7table_1\7column2\7column2\f\r\0<\0\0\0\375\200\0\0\0\0006\0\0\5\3def\4db_1\7table_1\7table_1\7column3\7column3\f\r\0<\0\0\0\375\200\0\0\0\0006\0\0\6\3def\4db_1\7table_1\7table_1\7column4\7column4\f\r\0<\0\0\0\375\200\0\0\0\0\5\0\0\7\376\0\0\"\0\n\0\0\10\0010\0011\0012\0013\0014\5\0\0\t\376\0\0\"\0", 16384) = 327
libmysqlclient/mysqld でこのクエリを実行すると…
SELECT column0, column1, column2, column3, column4 FROM db_1.table_1 where k = 15
MySQL で通信内容が冗長になるケース
write(3, "1\t=\t1\t15\n", 9) = 9read(3, "0\t5\t0\t1\t2\t3\t4\n", 8192) = 14
HandlerSocket で同等のクエリを実行すると…
libmysqlclient HandlerSocket
request 80 bytes 9 bytes
response 327 bytes 14 bytes
MySQL で通信内容が冗長になるケース
• 結果セットメタデータが大きい– 各列について、 DB名、テーブル名、テーブル別名、列名、列の別名がメタデータに含まれる
http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Field_Packet
– mysql 4.0 までのプロトコルではメタデータが少し小さい。 4.1以降で大きくなった。
• 列が多く行が少ないとき相対的に大きい– メタデータは結果セットに一つだけ付くから
• HANDLER クエリや SSPS を使っても減らすことができない
MySQL の性能を引き出すためのその他の機能
MariaDB の thread pool について
• MariaDB には thread pool の実装がある (既定では off)thread-handling=pool-of-threads
• 有効にすると SQL クエリについても同時接続数を増やしたときの性能劣化が小さくなる– HandlerSocket を使った場合と似た特性になる
• 但し HandlerSocket を使うときは thread pool無効にしておいたほうがよいかもしれない– sleep しうる処理は one-thread-per-connection のほ
うが向いている
0
20000
40000
60000
80000
100000
120000
140000
160000
180000
200000
1 2 4 8 16 32 64 128
256
512
1024
2048
4096
8192
1638
4
3276
8
# of concurrent connections
quer
ies
per
seco
nd
SELECT query
SELECT query (thread pool)
HANDLER クエリ
• 索引に対する低レベルアクセスを行うクエリ
• 参照クエリのみサポート• SELECT クエリよりも速いことが多い• ただし取得する列を指定することができ
ず、常に全フィールド取得になってしまう
Server-Side Prepared Statement
• 少しだけ性能が上がることがある
HandlerSocket リクエストの pipelining
• HandlerSocket プロトコルは pipeline 化可能– HandlerSocket の実装は pipeline 化されたリ
クエストに対して最適化されている• プロトコルは単純な行ベースなので、接
続を集約して pipeline 化するプロキシを簡単に作れる
0
500000
1000000
1500000
2000000
2500000
1 2 4 8 16 32 64 128
256
512
1024
2048
4096
8192
1638
4
3276
8
# of concurrent connections
quer
ies
per
seco
nd
SELECT querySELECT query (thread pool)HandlerSocketHandlerSocket (pipelining x5)HANDLER query (thread pool)HANDLER query SSPS (thread pool)
ベンチマークについて• Xeon E5-2670 2.6GHz, 16core 32論理 CPU の半分を mysqld, 半分をクライアントに割り当て
• internet domain socket で通信• innodb テーブル• innodb_adaptive_hash_index = 1• handlersocket_threads = 16
HandlerSocket 2.0 について
version 2.0 の変更点
• クエリ解釈ロジックを外部モジュールで定義できるようになる– 従来サポートしていなかった種類のクエリを
外部モジュールで実装可能• 外部モジュールは、サービスを停止せず
に更新可能–古いバージョンの外部モジュールは参照が無
くなった時点でアンロードされる
ねらい• Stored procedure のように手軽に HandlerSock
etへ機能追加できるようにしたい• SQL に代わるクエリ言語を外部モジュールとし
て実装する– 従来の HandlerSocket クエリは拡張性に乏しいので抜本的に直したい
• 将来的には SQL も HandlerSocket 上に実装するかもしれない– SQL であっても現在の HandlerSocket と同等の性能
を出すことは可能
構成
Handler Interface
Storage engines
Transaction management etc.hs module
HandlerSocket Listener
API for hs modules
clients
prepare, execute response
find, update, etc. result
port 9998, 9999
hs module
hsmod_module
hsmod_worker
hsmod_conn
hsmod_stmt
モジュールがロードされたときに 1 つだけ作成される
ワーカースレッドにつき 1 つ作成される
クライアント接続につき 1 つ作成される
ステートメントが prepare されるごとに作成されるクエリが実行されると execute メソッドが呼ばれる
prepare
create
create
API for hs modules
関数 コンテキスト 動作
hs_open_index prepare テーブルと索引を開く
hs_get_field_num prepare, execute 指定された名前のフィールドを捜し、その番号を返す
hs_find_first
hs_find_next
hs_find_finish
execute 索引から条件にマッチするレコードを探す
hs_field_get execute 現在選択されている行の指定された列の値を取得
hs_update_row execute 現在選択されている行を指定された値で更新
hs_delete_row execute 現在選択されている行を削除
hs_insert_row execute 行を作成
hs_push_response prepare, execute クライアントへレスポンスを返す
hsmod_pxc
• HS2.0 クエリ (仮 ) を実行する hs module 実装
• コンパイルに pxc が必要https://github.com/ahiguti/pxc
HS2.0 クエリ (仮 ) – 単純なクエリ
(do (open hstestdb hstesttbl PRIMARY idx0) (foreach idx0 = (?0) 1 (response (idx0.v))))
テーブル hstestdb.hstesttbl の PK を開き、 idx0 という名前をつける。 idx0 のキーが実行時引数の 0番に一致する行に対し、その行のフィールド v の値を返す。
これは以下の SQL クエリと同等の結果を返す
SELECT v from hstestdb.hstesttbl where k = ?
HS2.0 クエリ (仮 ) – inner join
(do (open hstestdb hstesttbl PRIMARY idx0)
(open hstestdb hst2 PRIMARY idx1)
(foreach idx0 = (?0) 1
(foreach idx1 = (idx0.v) 1
(response (idx0.k idx0.v idx1.k idx1.v)))))
これは以下の SQL クエリと同等の結果を返す
SELECT t0.k, t0.v, t1.k, t1.v
FROM hstestdb.hstesttbl t0
INNER JOIN hstestdb.hst2 t1
ON t0.v = t1.k
WHERE t0.k = ?
0
200000
400000
600000
800000
1000000
1200000
1 2 4 8 16 32 64 128
256
512
1024
2048
4096
8192
1638
4
# of concurrent connections
quer
ies
per
seco
nd
HS2.0, simple queryHS2.0, inner join
version 2.0 でもできないこと
• 自動コミットを抑制はできない– HandlerSocket ではレスポンスが返った時点
で必ずコミット済み– lock を保持したまま sleep することが無いよ
うにしている• rollback はできない
– 複数リクエストを一つのトランザクションの中で実行しているから
SQL をサポート?
• SQL をパースし、実行戦略を決めて HS2.0 クエリ (仮 ) を組み立てればよい
• 変換は prepare段階で行えるので、 execute の性能には影響しない– したがって SQL であっても現在の HandlerS
ocket と同等の性能を出すことができる• 実装するかどうか未定
• 以下のブランチで開発中https://code.launchpad.net/~maria-captains/maria/10.0-hs-devel
• 移動するかも。その場合は以下のアドレスから辿れるようにする。https://launchpad.net/~ahiguti100