ちょっと硬派なコンピュータフリークのBlogです。

カスタム検索

2009-04-06

Distribution Awareness - MySQL Clusterにおけるスキーマチューニングの定石

MySQL Clusterはデータノードが増えると性能が低下する???

そのような噂を聞いたことがないだろうか。この噂は事実を含んでいる面もあるが、殆どの場合は適切にスキーマを設計していないことが原因で起きる。実はMySQL Clusterはその性能を遺憾なく発揮するためにはスキーマの設計が非常に大事なのである。

MySQL Clusterは複数のデータノード(ノードグループ)に対して主キーの値に基づいて行単位で分散されている。主キーに偏りがなければ各データノードに格納される行数は均等になる。つまり、MySQL ClusterはSharding(アプリケーションパーティショニング/Level2分散)を自ら行っていると言えるだろう。

MySQL Clusterでは主キーによるルックアップは、どのデータノードにデータが格納されているかが主キーから分かるため非常に高速である。逆に、主キー以外のキーで検索を行う場合には、どのデータノードに目的の行が存在するかが分からないので全てのデータノードへのアクセスが発生するため性能は劣化する。この違いは次の2つの図を比べればわかり易いだろう。

主キーによるルックアップ


主キーの指定がない場合

主キーによるルックアップでは一つのデータノードへ要求を送り、そのデータノードから行データが返されるだけである。それに比べ、主キーを用いない場合には、要求を受け取ったデータノードは他の全てのデータノードに要求を転送し、SQLノードはそれら全てのノードから結果を受け取ることになる。そのため、データノード間・SQLノード間の通信量は飛躍的に増えてしまう。

また、MySQL Clusterはトランザクションを開始する際に、一つのデータノードをトランザクション・コーディネータ(TC)として選択する。そのトランザクションに関しては、全てTCを通じてSQLノードとデータノードの通信が行われる。そのため、いくら主キーを用いたルックアップを行っていても、一つのトランザクション内で複数のテーブルへアクセスするような場合にはデータノード間の通信が発生してしまう。(下図参照)



上記2つの問題は、データノード数が増えれば増えるほど顕在化して、ネットワークの通信量が増大してしまう。そして、ネットワークがボトルネックになってしまうのである。特に前者の例ではデータノード数が通信量に直結してしまうので問題は甚大である。後者の例も複数のテーブルを一度に更新する場合などに、データノード間の通信によるネットワークへの負荷が増大するだろう。

MySQL Clusterにはこのような問題に対処するための仕組みが存在する。それが、Distribution Awarenessである。Distribution Awarenessとは、「分散を意識してデータを格納する」といった感じの意味である。平たくいうと、主キー以外のキーを用いて行を各データノードへ分散させるということである。これにより、データノードへのアクセス効率が格段に向上するわけである。

Distribution Awarenessの効果は、DBT-2などでも確認することができる。手元のマシンを使ってベンチマークを行った結果は次のようになった。



このベンチマークは一台のPCを利用して、単一OSイメージ上に10データノードのMySQL Clusterを構築して計測を行った。データノードを実際に分散しているわけではないので、このベンチマーク結果は実際の性能を反映しているものではないが、Distribution Awarenessの効率を理解するのには役にたつだろう。

このベンチマークで用いた主な設定は次の通り。
  • OS/Ubuntu 8.10
  • MySQL Cluster 6.3.23
  • CPU/AMD/2コア/2.3GHz
  • 物理メモリ/8GB
  • HDD/SATA 7200rpm
  • DataMemory=400MB
  • IndexMemory=64M
  • NoOfReplicas=2
  • データノード数10(5ノードグループ)
  • SQLノード数1
  • ベンチマークソフト/DBT-2、Warehouse=5

それぞれのベンチマークは次のような条件で行った。

InnoDB

こちらは比較実験である。InnoDB Buffer Pool=4GBにして、全てのデータがバッファプール内に収まるように配慮して行った。

NDB w/o DA

Distribution Awarenessを考慮しない場合である。データのローディングはいったんMyISAMを用いて行い、全てのテーブルをALTER TABLE ... ENGINE NDBで単純にMySQL Clusterへ変換してからベンチマークを行った。

ちなみに、「w/o」はwithoutの略。「w/」はwithの略。

NDB w/DA

Distribution Awarenessを考慮したベンチマークである。DBT-2は、customer、district、history、item、new_order、order_line、orders、warehouseという7つのテーブルを用いてベンチマークを実行する。このうち、itemテーブル以外のテーブルにはwarehouseの主キーを参照するカラムが定義されている。ほとんどのクエリがwarehouseの主キーを用いて参照されているので、warehouseの主キーに基づいてデータを各ノードに分散させるとクエリの効率が良くなる。

行データの分散に利用するキーを明示的に指定するには、次のようにテーブル定義でPARTITION BY KEY句を利用する。
mysql> alter table customer engine ndb partition by key (c_w_id);
mysql> alter table district engine ndb partition by key (d_w_id);
mysql> alter table history add h_rowid int unsigned not null auto_increment, add primary key (h_rowid, h_w_id);
mysql> alter table history engine ndb partition by key (h_w_id);
mysql> alter table item engine ndb;
mysql> alter table new_order engine ndb partition by key (no_w_id);
mysql> alter table order_line engine ndb partition by key (ol_w_id);
mysql> alter table orders engine ndb partition by key (o_w_id);

PARTITION BY KEYを用いるには明示的な主キーが必要なのだが、historyテーブルには明示的な主キーが存在しないので、新しいAUTO_INCREMENTカラムを追加することで対応している。また、PARTITION BY KEYを用いるには、キーが主キーに含まれている必要がある。DBT-2が利用する上記のテーブルでは、全てwarehouseのIDが主キーの一部になっているので問題はない。

NDB w/full DA

上記のスキーマ変更だけでは、itemテーブルと他のテーブルへのアクセスが同一トランザクション内で連続して発生した場合に、約80%の確率でDistribution Awarenessが効かなくなってしまう。itemテーブルは参照専用なので、次のようにMyISAMへ変換した。(ついでにwarehouseも。)
mysql> alter table item engine myisam;
mysql> alter table warehouse engine myisam;

NDB single node

こちらも比較実験である。
  • DataMemory=4GB
  • IndexMemory=640MB
  • NoOfReplica=1
という設定でベンチマークを行った。

考察

Distribution Awareness あり/なしの場合では、ベンチマーク結果に顕著な差が出ているのが分かるだろう。また、Distribution Awarenessを用いることが出来ないテーブルが存在する場合にはMyISAMテーブルに変換し、完全なDistribution Awarenessを利用することで、さらに効率化を図ることができる。itemテーブル=商品マスタであるが、そのような参照専門のテーブルならばMyISAMで運用するのも現実的だろう。

MySQL Cluster内におけるデータの分散は、いわゆるSharding(アプリケーションパーティショニング/Level2分散)と同じ原理で行っている。従って、アプリケーション側からはShardingと同じ要領でテーブルを定義し、クエリを発行するともっとも効率が良くなるわけである。例えば、Shardingではuser-idに基づいてユーザーごとに格納するデータベースを分けていたりする。その場合、user-idが分からないとどのデータベースにデータが存在するかが分からないので、データベースへのアクセスする際には必ずuser-idが指定されなければならない。それと同じ要領で、SQL文に必ず「AND userid = xxx」という検索条件をつけておけば、Distribution Awarenessが効くというわけである。

Distribution Awarenessが適切に考慮されていないと、データノード数が増加した場合に確実にネットワークがボトルネックになってしまう。そのため、データノード数が増えると返って性能が落ちるという事態が発生してしまう。Distribution Awarenessを適切に利用すると、データノード間の通信がボトルネックになってしまうことはない。データノードの数を増やせばそれに応じて性能がスケールする。従って、テーブルやクエリをDistribution Awarenessを用いて設計できるかどうかが、MySQL Clusterを利用するかどうかを判断する上で一つのポイントになるだろう。

実際にクエリにおいてDistribution Awarenessが効いているかどうかは、EXPLAIN PARTITIONS文を用いて調べることができる。パーティションが一つだけ選択されているようなら成功である。
mysql> EXPLAIN PARTITIONS SELECT no_o_id FROM new_order WHERE no_w_id = 2  AND  no_d_id = 7\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: new_order
partitions: p5  <--- パーティションがひとつだけ選択されていればOK
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const,const
rows: 10
Extra: 
1 row in set (0.00 sec)

今回のベンチマークは一台のマシンで計測を行った。従って、データノード間の通信はネットワークを経由することがないので非常に高速であり、現実的な構成における性能を正確には反映しない結果になっているだろう。複数のマシンを用いた場合にはノード間通信におけるオーバーヘッドが増すので、Distribution Awarenessあり/なしの場合の差はより顕著になると考えられる。また、今回のベンチマークではInnoDBば最も速いという結果に終わっているが、これは一台のマシンを用いているので当然の結果であると言える。MySQL Clusterの真価は、多数のマシンを用いてシステムを構築した場合に(なおかつDistribution Awarenessを適切に用いた場合に)発揮されるのである。

Distribution Awarenessを考慮しないといけないのでMySQL Clusterは色々と面倒だと思うかも知れない。自分でuseridを使って格納するデータベースを決めるShardingをしたほうがいいのでは?と。しかし、Shardingの場合は格納先を決めるロジックを自分で実装しなければいけないが、Distribution Awarenessを利用するとテーブルとSQL文に工夫を行うだけで、データベースへのアクセスは一本化できるというメリットがある。つまり、「自分でデータベースへのアクセスを振り分けるロジックを実装する vs テーブル定義とSQL文を工夫する」というトレードオフである。開発効率を考えるなら、Distribution Awarenessの方がお手軽だろう。


まとめ

データノード数が増えると・・・
  • Distribution Awarenessが効いていないと性能が劣化する。
  • Distribution Awarenessが効いていると性能がスケールする。

Distribution Awarenessを利用するには、テーブル定義とクエリの両方をチューニングする必要がある。
  • CREATE TABLE ... ENGINE NDB PARTITION BY KEY (userid);
  • SELECT ... WHERE ... AND userid = 12345;

Distribution Awarenessが効いているかどうかを調べるには
  • EXPLAIN PARTITIONS SELECT ...\G
を利用する。

そんなわけで、Distribution Awarenessを適切に利用すればMySQL Clusterはスケールする。アプリケーション側で分散のロジックを実装する必要がないので、分散手法としてはお手軽である。これからShardingを導入しようと考えているなら、MySQL Clusterによる負荷分散を検討してみてはいかがだろうか。

0 コメント:

コメントを投稿