データベースの負荷分散(多謝)
貴重な資料なので以下参照させて頂きました。2006年なので古いのかなと思ったりしてますが。
著者プロフィール
株式会社はてな 伊藤 直也
取締役最高技術責任者
――以下参照文章
Webサーバーも順調に増えた、となると次はデータベースが悲鳴を上げる頃です。データベースの増設と行きましょう。
はてなではデータベースにはMySQLを利用しています。MySQLは組み込みでレプリケーションをサポートしているので、これを使わない手はありません。レプリケーションを行い、マスターDBのコピーであるスレーブDBサーバーを作り2台構成にします。
レプリケーションは、データベースを複数台に増やし、且つその複数のデータベースが保持するデータを同期させるための仕組みです。レプリケーションされたデータベースのうち、元々あったデータベースが親、それ以外が子という親子関係になります。
親はマスター、子はスレーブと呼ばれ、マスターへの更新処理と同じ処理をスレーブに伝播させることでデータの同期が行われます。実際にはマスターからスレーブへ処理が伝播するのではなく、スレーブがポーリングを行ってマスターと同期を取ることになります。
MySQLによるレプリケーションの設定方法の詳細はここでは割愛しますが、設定はそれほど難しくはありません。マニュアルなどを参考にされても良いでしょうし、多くのMySQL関連の書籍などにも詳しい手順が掲載されているかと思います。
Perl側のCRUD処理に手を加える
単純にレプリケーションを行って台数を増やしただけでは負荷分散は行えません。2つのサーバーにデータベースクエリーが分散されるよう、アプリケーション側に手を入れる必要があります。このときの振り分け方ですが、マスター/スレーブによるレプリケーションを行った場合データベースのCRUD(CREATE(INSERT)/READ(SELECT)/UPDATE/DELETE)処理を、
R(参照系処理)はスレーブへ
CUD(更新系処理)はマスターへ
というようにクエリーが発行されるようにする必要があります。
MySQLのレプリケーションでは、マスターが更新されるとスレーブにその更新内容が伝わり常に同期が取られるようになっています。この同期はマスターからスレーブへの一方通行です。スレーブ側に更新処理を発行してしまった場合、両者の同期は行われず矛盾が発生してしまいますので注意が必要です。
MVCフレームワークを利用している場合、通常データベースへの処理はモデルクラスに集中しているので、ここに手を入れてCRUDの振り分けを実装するのがセオリーです。はてなではモデルクラスの実装として自前のO/Rマッパーを持っており、ここにその処理を実装しています。
O/Rマッパーのクラス階層の中で、データベースへの接続を管理しているクラスにおいて、マスターDBへの接続とスレーブへの接続それぞれのデータベースハンドラを生成し保持しておきます。参照系処理、つまりSQLのSELECTコールはスレーブ用ハンドラへ、CUD(INESRT/UPDATE/DELETE)はマスター用ハンドラへクエリーが発行されるようにしています。
モデルの実装にCPANモジュール、例えばClass::DBIを利用している場合はClass::DBI::Replicationなどを利用すると良いでしょう。データベース接続が抽象化されたO/Rマッパーなどを使っていれば、プログラマはマスター/スレーブどちらのサーバーにクエリーを発行しているかを意識せずにコードを記述できます。はてなのO/Rマッパーもそうですし、Class::DBIやDBIx::ClassなどのCPANモジュールを利用している場合も同じくです。
SQLをあちこちに書いてはいけない、というのはもはやすべてのWebアプリケーションプログラマの知るところですが、その理由の一つに、こういった複数のデータベースに対する処理をプログラマに意識させないための実装を行えるようにする、という点が含まれているわけですね。
アプリケーションの特性を考えたデータベース設計を
ところで、はてなブックマークではMySQLのレプリケーション機能を素直に使った負荷分散を行っているわけですが、これはソーシャルブックマークというアプリケーションの特性を考慮してのことです。
ソーシャルブックマークサービスはコンテンツとして閲覧されるページが非常に多く、発行されるSQLのクエリーの多くは参照系のクエリーになります。参照系と更新系のクエリーの割合は、だいたい80%〜90%が参照系という具合です。ソーシャルブックマークに限らず、掲示板やブログなど多くのWEB+DBアプリケーションでは参照系クエリーが多くなるかと思います。
こういった場合は、レプリケーションによる負荷分散が効果的です。レプリケーションではマスター1台に対して複数のスレーブを持たせることができ、理論的にはスレーブは何台でも追加できます。先に述べたとおり、参照系クエリーはスレーブが担当するので、参照系クエリーを分散させるにはスレーブを追加すれば良く、スレーブの追加が容易なレプリケーションシステム下では分散にはそれほど困らない、というわけです。
一方のマスターは、スレーブに比較すると台数を増やすことが難しく、データベースに保存されているデータを複数のサーバーで分割保持するなど、クラスタリングのノウハウを導入する必要があります。したがって、更新系処理が多いアプリケーションでの負荷分散は、そうでない場合に比べてはるかに難易度が高くなるでしょう。
Webアプリケーションを構築するにあたって、いきあたりばったりで進めてはいけないポイントの一つが、このデータベースのクエリーの種類による負荷分散の計画です。更新系処理が多くなりがちなアプリケーションでは、あらかじめクラスタリングしやすいようにテーブルを設計しておくなどの注意が必要になります。アプリケーションの特性上、データベースにどのようなクエリーが集中するのかを考慮してデータベース設計を行っておきましょう。
tmpfsにMySQLのデータを展開してディスクI/O レスデータベース
マスターとスレーブの2台にデータベースを増やしたおかげでクエリーの振り分けができました。しかし、参照系のクエリーが多いというサービスの特性もあり、このままではまだその参照系クエリーがスレーブサーバーに集中しているため、たいした分散はできていないことになります。
そこで負荷を下げるために更に手を打ちます。スレーブをもっと増やす、という手段ももちろん使いますが、はてなではその前にもう一つ特殊なテクニックを利用しています。
データベースサーバーで処理の大部分を占めるのがディスクへのアクセスです。データはディスクに保存されているため、MySQLはそこからデータを読み出します。ディスクI/Oはメモリへのアクセスなどに比べると遥かに遅いため、ディスクI/Oが頻発するとサーバーのパフォーマンスは劣化します。そこで高速なディスクを用意するのもいいのですが、保持しているデータがそれほど大きくなければ、そのデータをすべてメモリ上に展開してしまうという少々強引なテクニックを使うことができます。
Linuxでは、メモリ上の一定の空間をファイルシステムとして利用することができる、tmpfsというファイルシステムが利用できます。いわゆるRAMディスクのようなものです。
tmpfsで数GBのファイルシステムを作成し、その上にMySQLのデータを移動させます。tmpfsで作られたファイルシステムの実体はメモリですので、こうすることによりデータベースサーバーでのディスクI/Oを完全にカットし高速化できるのです。
しかし、メモリ上にデータを展開するのは高速な反面、リスクも伴います。その主たるものはサーバークラッシュ時のデータ損失です。tmpfsはメモリなので、サーバーの電源が切れたところでそこに保存されたデータはすべて失われてしまいます。データベースに保存されるような貴重なデータが不意のハードの故障や停電などで消失してしまっては困ります。
ただし、レプリケーション環境であれば、たとえスレーブのデータがすべて消失してしまったとしても、同期しているマスターにすべて残っているというところがミソです。マスターのデータは通常どおりディスク上で運用しつつ、スレーブはtmpfsによるメモリファイルシステムで運用するのです。万が一スレーブが故障してデータがなくなっても、マスターからまたスレーブを構築すればOK、というわけです。
tmpfsにMySQLデータを移動する
このオンメモリデータによる高速データベースの構築方法ですが、手順はリスト5の通りです。
リスト5:tmpfsをマウント
$ sudo mkdir -p /shm/mysql/bookmark
$ sudo mount -t tmpfs -o size=2048m tmpfs /dev/shm
$ sudo mount -t tmpfs -o size=2048m /dev/shm /shm/mysql/bookmark
まず、tmpfsを作成し、適当なマウントポイントにマウントします。ここでは/shm/mysql/bookmarkというディレクトリを作り、そこに2048MBのtmpfsをマウントしています。
次に、MySQLのデータを元々ある場所からコピーして移動します(リスト6)。
リスト6:データベースをtmpfsにコピー
$ cd /var/lib/mysql/bookmark
$ sudo cp *.* /shm/mysql/bookmark
/var/lib/mysql/bookmarkにあるものとします。なお、はてなブックマークでは各テーブルのストレージエンジンに、参照系クエリーが高速なMyISAMを採用しています。MyISAMテーブルのデータはコピーするだけで移動させることができます。データのコピーを行う最中に更新処理がかからないよう、MySQLは停止しておきましょう。
これで準備は整いました。あとはMySQLの設定ファイルであるmy.cnfで、新しいデータの格納ディレクトリを設定してやればOKです。/etc/my.cnfを編集し、以下の一文を加えます。
datadir=/shm/mysql/bookmark
設定が済んだところでMySQLを起動してください。
以降・・・ひたすらサーバーを追加する
はてなブックマークでのWebサーバー、データベースサーバーの増設の過程を解説してきました。ここまでで解説した時点では、Webサーバーが3台、データベースサーバーが2台とまだ小さな構成です。が、ここから先の負荷分散は、しばらくの間は難しいことはありません。ここまで増設したのと同じ方法で、適宜サーバーを追加していけばよいのです。
負荷分散のコツは、いかにサーバーを追加するだけで分散できるようなシステムを構築するかにかかっています。負荷を分散する必要が出るたびにアプリケーションを作り変えたり、大きな作業が発生するようでは、メンテナンスにコストがかかりすぎます。サーバーを増やすだけでよい環境であれば、チープなサーバーが簡単に手に入る昨今ですから、コストは最小限に抑えられます。
まとめ
簡単になりますが、はてなブックマークを題材にしたLAMPでの中規模アプリケーションの運用ノウハウを解説してきました。本稿で触れた方法論は非常にオーソドックスなもので、いろいろな環境で応用が利くのではないかと思います。PerlでのWebアプリケーション開発の知恵として参考にしていただければ幸いです。
なお、冒頭で紹介したYAPC::Asiaでの発表資料には、本稿で触れたトピック以外にもmod_perl2.0の話題、botプログラムからのアクセスを分散するノウハウ、データベースのコネクションプーリングに関する是非といった、今回は触れなかったトピックについても記述してあります。興味のある方は是非ご一読いただければと思います。
No tags for this post.