NUMA
最近のx86_64系のCPUはメモリコントローラを搭載している。2ソケット以上の構成になると、それぞれのCPUがローカルのメモリを管理するようになる。プログラムから見ると同じ「メモリ」なのだが、どのCPUが管理しているメモリなのかによって、メモリのアクセス速度は異なって来る。スレッドが実行されているCPU自身が管理しているメモリへのアクセスは高速だが、他のCPUが管理しているメモリへアクセスするにはそのCPUを経由しなければならないからだ。このようにメモリアクセスが不均一になるアーキテクチャをNUMAという。
MySQLおよびInnoDBは、NUMAであろうがなかろうが別段問題なく動く。ただし、安定性および性能を考えると、管理者がひと手間かけておくことをおすすめする。
NUMAアーキテクチャにおいて、システム全体の性能を活かしきる最適な方法は、ソフトウェアがNUMAを意識したつくりになっていることだ。だが、それは抜本的なアーキテクチャの変更になるため難しい。当然ながらMySQLもInnoDBもそのようなつくりにはなっていない。
Linuxの場合、OS側にはNUMAを意識する点があって、メモリ割り当てを行うと、メモリ割り当ての要求を行ったスレッドと同じCPUが管理する(いわゆるローカルの)メモリを優先的に割り当てる。そのような割り当ては小さな規模のプログラムでは理にかなっているのだが、MySQLのようにシステムメモリのほとんどを消費するようなプログラムでは問題がある。LinuxではCPUとそのCPUが管理するメモリのことをNUMAノードというが、ローカルのメモリを優先的に割り当てると特定のNUMAノードにメモリの割り当てが集中してしまう。そうしてメモリの割り当てが不均一になるのが問題なのだ。
まず、メモリの割り当てが不均一になるとそのNUMAノードだけビジーになり、他のNUMAノードは遊んでしまうことになる。その結果、全体的なパフォーマンスは最適なものではなくなってしまうだろう。
さらに厄介なのはSwap Insanityという現象だ。ローカルのメモリを優先的に割り当てると、そのNUMAノードのメモリを消費し尽くしてしまうことになる。すると、他のNUMAノードには空きメモリが存在するにも関わらず、プロセスがスワップアウトしてしまうという問題が生じてしまう。そのような現象がSwap Insanityと呼ばれている。そのような現象が生じる原因は、カーネルの処理には同じNUMAノード上のメモリを必要とするものがあるからだ。
回りくどくなってしまったが、NUMAに対する上記のようなメモリ割り当ての挙動を変更しておくと良い。Linuxならばnumactlコマンドを使うか、カーネルの起動オプションでnuma=offとすれば良いだろう。
CPUスケーラビリティ
InnoDBのCPUスケーラビリティを考える上で鍵となるパラメーターは、従来はinnodb_thread_concurrencyであった。今でもinnodb_thread_concurrencyオプションは有効だが、数千接続レベルになってくると厳しいものがある。MySQLはセッションとスレッドの対応が1:1であるため、InnoDBで同時実行可能な処理の数を絞っても、それ以外の部分では処理の数を絞れずに、スラッシングを起こしてしまうからだ。そこで、MySQL 5.5より商用限定ではあるが、スレッドプールプラグインなるものが登場した。これは、ストレージエンジンではなくMySQL自身が同時実行するスレッド数の調整を行うための機能だ。詳細については、スレッドプールプラグインのマニュアルや、開発者であるMikael Ronstrom氏のブログエントリを参照して頂きたい。
ところで、MySQL 5.5からInnoDBはバッファプールのインスタンス数およびロールバックセグメント数の調整が出来るようになっている。これはいずれもロックの競合を緩和するための措置である。バッファプールのインスタンス数は従来はひとつであった。増やすと必ず性能が向上するというわけではないが、CPUコア数が多い場合には増やしてみるといいだろう。ロールバックセグメント数は1~128で設定可能だが、バッファプールのインスタンス数とは違い、最大値がデフォルトとなっている。減らしたほうが性能が上がるようなら減らしてみるといいだろう。
innodb_buffer_pool_instances=8
innodb_rollback_segments=32
InnoDB自身は、Plugin以降のバージョンなら、適切に設定されていれば32コアぐらいは軽く使いきってしまうとだけ言っておこう。
クエリキャッシュをOFF
参照中心の場合にはクエリキャッシュが有効な場合があるが、テーブルへの更新が多い場合にはクエリキャッシュはあまり効かない。テーブルが更新されるとそのテーブルを参照するクエリキャッシュのエントリは無効になるからだ。(クエリキャッシュが正しい結果を返すには、クエリがキャッシュに追加された時点から、テーブルが一切更新されていない必要がある。)特に、InnoDBは行レベルロックであるため、同時に複数のトランザクションがテーブルを更新することが日常的に起きるので、クエリキャッシュが効く場面というのはなかなかないのではなかろうか。
更新が多い場合、クエリキャッシュはオーバーヘッドにしかならないので、InnoDBを利用している場合には特にクエリキャッシュをOFFにすることを検討しよう。クエリキャッシュをOFFにすることで性能が向上したという例はたくさんある。
ハードウェア選定
InnoDBにとって最適なハードウェアとはどのようなものだろうか。物理レイヤーたるInnoDBの実行速度は、ハードウェアの速度によって決まると言っても過言ではない。だからといって、単にハードウェアにお金をかければいいのか?というと、そういうわけでもない。要件以上にハードウェアを豪華にし過ぎても無駄が生じることになるし、多くの場合「限られた予算の中でどれだけ性能を出せるか」という課題に直面しているはずだ。だいたい、バランスを逸してしまってはお金をかけても性能が出ない。ディスク、メモリ、CPUにバランスよく投資することが重要である。
まず重要なのは、ワーキングセットがどのぐらいのサイズなのかということである。ワーキングセットが全てバッファプールに収まるようになるまでは、メモリを搭載すればするほどI/Oが減るので性能が上がる。当然ながら、ワーキングセットが全てバッファプールに収まるような場合には、それ以上メモリを増やしても効果はない。バッファにワーキングセットが全て収まらなくても、システムへのアクセスが少なく、性能の要件を満たすようならそれ以上メモリを搭載する必要はない。
バッファプールが大きくなってくると、より多くの更新を処理できるようになるのでそれを支えるために高速なディスクが必要となる。ログが枯渇したり、ダーティページでバッファプールが埋まらないよう適切な速度のディスクを用いて欲しい。メモリをたくさん搭載するにはそれを支えるディスクが必要なのだ。ただし、負荷のタイプが参照メインであればディスクはそれほど高速でなくても良い。せいぜいキャッシュミス時のペナルティが大きくなるだけだからだ。それよりもバッファプールを増やすことにお金をかけたほうが良いだろう。参照がメインの場合であっても、ワーキングセットのサイズがどうしてもバッファプールよりもはるかに大きくなってしまうというような場合には、高速なディスクで逃げるというのも悪くはない。
I/Oがボトルネックになっている限り、CPUを増やしてもあまり意味はない。たまにCPUだけやけに豪華な構成を見かけるが、それは誤りだ。バッファプールが大きくメモリへのアクセスだけで処理が完結することが多くなると、ようやく多数のCPUで並列処理をすることによる性能向上の余地が出てくる。リクエスト数に応じてCPUを増やそう。
CPU、メモリ、ディスクはバランスの良い構成にしよう。どれかひとつだけ突出して豪華でも高い性能は得られない。