MyISAMとkey_buffer

Innodbが十分に安定している今敢えてMyISAMを選択する理由はあまり無いのだけど、ここしばらく苦しめられた思い出として記録する

MyISAMとキャッシュ

MyISAMではデータとインデックスがそれぞれ別の方式でキャッシュされる。データはOS自身が持っているページキャッシュで、インデックスはMySQLではkey_buffer_sizeと呼ばれる値で指定されるサイズの領域。Innodbではこれらが統合されているので、キャッシュ領域を主記憶の過半以上に確保してしまっても何ら問題ない(むしろ推奨値は主記憶の最大80%)。ところが、MyISAMでは前述の通りOSのページキャッシュを利用するためそれらのために十分に残りメモリを確保するため、key_bufferは1/4程度に抑えておく必要がある。

key_bufferの管理方式

key_bufferはmy.cnfでサイズを指定するが、内部ではこれをあるブロック単位で管理している。このパラメータが'key_cache_block_size'。初期値は1024、I/Oのブロックサイズと揃えることをマニュアルでは推奨している。
例えば
key_buffer = 32M
として32Mbytes分をkey_bufferとして確保すると、管理領域分として幾分か引いたあと残りを1024で割ってそのブロック単位で管理する。手元の環境では32Mのとき26792blockほどになった、管理領域の大きさはアーキテクチャと全体のサイズ依存。
管理は単純なLRU。使用された順でリンクリストとなっていて、足りなくなったときは後ろから捨てていく。

key_bufferとtable_cache

なんでこの二者が絡むのか隨分悩んだが、色々なスクリプトで挙動を観察する限りkey_bufferが使われるのは対象となるテーブルがtable_cacheに乗っている状態のみ。非常に多くのMyISAMテーブルを扱うときにはkey_bufferだけでなくtable_cacheも十分に大きく確保しておかないとせっかく多めにとったキャッシュ領域が全然使われない状態となることがある。

key_bufferの使用率

前項と絡んで、特定の条件下では確保したキャッシュが使い切られないことがある。そのとき見るべきはステータス変数の'Key_blocks_unused'と'Key_blocks_used'。
前者は現在未使用のブロック数であるのに対して、後者は起動してから使われたことのある最大値(いわゆるハイウォータマーク)。そのため合計するとkey_bufferの大きさを超えることがある。
通常に使ってから十分に経過しても'Key_blocks_unused'が0になっていないときにはkey_bufferを割り当てすぎか、table_cacheが足りなくてテーブルが掃き出されているなどが考えられる。
インデックスの物理ファイル(.MYI)の大きさと全体のメモリ容量と相談しつつ必要量を算出するとよい。

key_bufferと並列度

ごく素直にkey_bufferを使うとかなり並列度が低い。これはkey_bufferを触る関数のかなり初期にmutexロックをしてしまい(key_cache_read@mysys/mf_keycache.c)、ページ単位の細かいロックなどを行っていないため。
ただし、最近のMySQLではkey_bufferを複数の管理領域に分割することでこれらの競合を軽減できる。ただし、どのテーブルをどの領域でキャッシュするかという指定を逐一SQLで指定する必要があり、一つのテーブルについてのロック粒度を下げられるわけではない。
領域を分割することによって単純なLRUではなく、常時キャッシュに乗っていて欲しいデータと適宜入れ替って欲しいデータを明示的に分離することができる。ほかにも明示的なキャッシュのプリロードなど、色々できるようにはなっているけど、ちょっと煩雑すぎる印象。