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

カスタム検索

2011-12-13

MySQLにおけるレプリケーション遅延の傾向と対策

レプリケーションはMySQLで最もよく使われる機能のひとつだ。レプリケーションは基本的に非同期でデータの複製を行う仕組みになっているのだが、非同期故にどうしても逃れられない問題がある。そのひとつが今回のテーマ、遅延である。というと、MySQLのレプリケーションはすぐに遅延が生じてしまうように感じてしまうかも知れないが、そのようなことはない。ほとんどの場合は即座にスレーブの更新が行われる。

なぜ遅延は発生するのか、どのように遅延が起きていることを調べるのか、どのように回避するのかということを本エントリでは解説したい。うまく遅延と付き合って、MySQLのレプリケーションをより快適に運用してもらえればと思う。

そもそも遅延とは何か

MySQLのレプリケーションは非同期で行われる。これは準同期でも同じであり、スレーブにおいて更新が起きるのはマスターよりも一瞬遅れてしまう。これは非同期であるが故に逃れられない制約である。言ってみればMySQLのレプリケーションでは、(マスターに更新があれば)スレーブは常にマスターから遅れている状態であると言える。(全く更新がなければマスターとスレーブのデータは同じになる。)

MySQLのレプリケーションは、マスターに対して行われたのと同じ更新を、スレーブで同様に再現することで行われる。同じデータに対して同じ更新を行えば結果は同じになるというコンセプトの上に成り立っている。(もちろん、そうなるようにマスター上で更新がシリアライズされている。)

以下はレプリケーションの動作を模式的に表した図である。


この図における4-2以降の処理が非同期で行われるのである。

なぜ遅延が普段問題にならないのか。それは、レプリケーションが非同期と言えども非常に高速であり、遅延がとても短いからだ。とても短いというと抽象的な表現だが、具体的には「ネットワーク上をパケットが流れるのにかかる時間 + スレーブ上で更新を適用するのにかかる時間 + α」ということになる。ただしこれは非同期の場合で、スレーブへ更新が転送されるのを待つ準同期の場合には「スレーブ上で更新を適用するのにかかる時間 + α」が実質的な遅延となる。いずれにしても、人間にとってそれは「ほぼ同時」と言える短い時間における出来事なのである。

遅延が問題となるのは、マスターとスレーブのデータが同期するのにかかる時間が、前述のような「ほぼ同時」ではなくなってしまうような状況である。そして、一般的に「レプリケーションの遅延」という単語は、このように遅延時間が大きくなってしまった状況のことを指す。

なぜ遅延が発生するのか

レプリケーションの遅延には大分して2つのタイプがある。それは、ネットワーク上をパケットが流れるのに時間がかかる場合と、スレーブ上で更新を適用するのに時間がかかる場合である。後者は典型的に2つの場合に分けられるので、概ね3つのタイプがあると理解して欲しい。それぞれのタイプについて順次説明しよう。

ネットワークの遅延
小規模な構成では、ネットワークの遅延はほとんど問題になることはない。単位時間あたりの更新の量よりもネットワーク帯域の方が上回り、データの転送が滞りなく行われていればネットワークに起因する遅延は生じない。

だが、更新の量がネットワーク帯域よりも多くなると遅延が生じることになる。そのような状況は、次のような場合に起こりやすいだろう。

  • 遠隔地へレプリケーションを行うなど、回線が細い場合。
  • スレーブの数が多すぎる。(マスターが送信すべきデータ量はスレーブ数に比例して増加する。)
  • ネットワークの障害。

巨大なトランザクションを実行した
最もポピュラーな遅延の原因である。

もし、マスターでひとつのトランザクションに膨大な時間がかかってしまった場合、スレーブでも同じように膨大な時間がかかることになる。上記の図から分かるように、レプリケーションの実行順序はマスターでトランザクションがCOMMITされる→非同期でバイナリログの内容がスレーブへ転送される→スレーブ上で同じトランザクションが実行されるというものであるから、スレーブ上でトランザクションの再実行に時間がかかるとその分即座に遅延が生じることになる。特に、行ベースではなくステートメントベースのレプリケーションを行っていた場合には、スレーブ上で全く同じSQL文が実行されることになるので、SQL文の実行にかかる時間もマスターと同程度の時間がかかることになる。(マスターで1時間かかるようなトランザクションは、マスターでCOMMITされた後にスレーブで開始され、同じように1時間かかるので、スレーブが同じ状態に追いつくのは約1時間後になるというわけである。)

スレーブの負荷が高すぎる
スレーブが処理出来る量を超えた更新がマスターからやってくると、スレーブでは更新が滞ってしまう。ひとつの大きなトランザクションによる遅延は突如としてやってくるが、こちらの場合は徐々にスレーブが遅れていくことになる。どのリソースがボトルネックになるかは状況次第だが、概ね次の3パターンに分けて考えると良い。

  • CPU bound ... スレーブへの参照の負荷が高すぎて、スレーブSQLスレッドに割り当てるCPUリソースが枯渇してしまっている。
  • Memory bound ... ワーキングセットがInnoDBバッファプールよりもずっと大きい。UPDATEやDELETEを実行する場合には、対象となるレコードがバッファプール内に存在している必要があるが、バッファプールが小さすぎる場合には頻繁にディスクから読み込む必要が生じることになり、ディスクI/Oは時間がかかるので遅延の原因となる。
  • I/O bound ... マスターでは更新は並列に行われ、グループCOMMITによりログへの更新がまとめて行われるが、スレーブ上ではトランザクションはひとつずつしか実行できない。COMMITのたびにディスクへの同期が行われるが、その操作は時間がかかる。マスターとスレーブでの同期回数の差異により、スレーブが遅れてしまう。

どのように遅延を調査するのか

まず、スレーブの状態を見る上で最も基本となるのはSHOW SLAVE STATUSである。この出力をちゃんと読み取れるかどうかがまず最初の関門となる。以下はMySQL Sandboxを使ってレプリケーションの負荷テストを実施した例である。

slave1 [localhost] {msandbox} ((none)) > show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 127.0.0.1
                  Master_User: rsandbox
                  Master_Port: 19972
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000003
          Read_Master_Log_Pos: 31122248
               Relay_Log_File: mysql_sandbox19973-relay-bin.000008
                Relay_Log_Pos: 30512168
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 30512022
              Relay_Log_Space: 31122709
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 1
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
1 row in set (0.00 sec)

遅延が生じているかどうかは、基本的にSeconds_Behind_Masterで判断できる。上記の例では1と表示されているので、約1秒の遅れが生じていることになる。スレーブの遅延が突如として生じたのか、それとも徐々に遅延したのかは、Seconds_Behind_Masterを定期的に実行して、その値の履歴を保存しておくとわかりやすい。商用のものでよければ、MySQL Enterprise Monitorを使うとグラフ化までを自動で行ってくれるのでお手軽である。

遅延の状況を見る上で他に重要な箇所は、バイナリログのポジションである。Master_Log_FileおよびRead_Master_Log_Posが、スレーブI/Oスレッドによってどこまでバイナリログが転送されたかを示す。Relay_Master_Log_FileとExec_Master_Log_Posは、スレーブSQLスレッドがどこまでリレーログ内のSQL文を実行したかを示す。上記の例では、スレーブSQLスレッドが実行すべきログが600KB程度たまっていることが分かるので、遅延はSQLスレッドによるものであることが分かる。(そもそもMySQL Sandboxを使っているので、I/Oスレッドが遅れるとは考えにくいわけだが。)

残念ながら、SHOW SLAVE STATUSを使って、I/Oスレッドの遅延、すなわちネットワークに起因する遅延を効果的に調査する機能はない。Seconds_Behind_Masterは、バイナリログ内のタイムスタンプと、スレーブのクロックを比較することで時差を算出している。(マスターの時計とスレーブの時計の時差はレプリケーション開始時に算出されるが、もちろんNTPなどで時計を合わせておくことが望ましい。)そのため、Seconds_Behind_Masterの値はネットワークの遅延によって増加することもあるが、それがネットワークに起因するものかどうかを直感的に判断する機能は存在しないのである。マスターでSHOW MASTER STATUSを実行した際に表示されるバイナリログ位置と、SHOW SLAVE STATUSのMaster_Log_FileおよびRead_Master_Log_Posを比較するといいだろう。両者の間で乖離が生じていれば、I/Oスレッドが遅延していると判断できる。

SQLスレッドの遅延を調査する上で役立つのがスロークエリログである。特に「巨大なトランザクション」によって遅延が生じていたかどうかは、スロークエリを見れば即座に判断可能である。

スレーブ上で徐々にSQLスレッドが遅れている場合には、どこにボトルネックがあるかを判断するのが重要となる。mpstatやvmstat、iostatなどの各種システムリソースを監視したり、SHOW ENGINE INNODB STATUS(InnoDBモニター)の値を見てどこがボトルネックになっているかを判断するといいだろう。

商用のMySQL Enterprise Monitorに付属しているレプリケーションモニターは、レプリケーション監視の強い味方である。スレーブごとにSeconds_Behind_Masterやバイナリログポジションを一覧表示してくれるので、スレーブの稼働状況が一目瞭然である。また、Seconds_Behind_Masterの値も定期的に取得してグラフ化してくれる。

遅延対策のベストプラクティス

エントリのまとめとして、レプリケーションを運用する上で実施するべきことについて列挙しておこう。

まず、巨大なトランザクション(一度に大量の行を更新するもの)は実行しないというのが鉄則である。バッチ処理的なものであれば、可能であればコミットする行数を少しずつに絞り込むようにしよう。大きなテーブルのALTER TABLEのようにいかんともし難い処理は、メンテナンスウィンドウを設けて実施しよう。

スレーブでI/O boundなボトルネックが生じている場合には、innodb_flush_log_at_trx_commit=2の設定を検討しよう。こうすると、スレーブ上ではCOMMIT時にログがディスクへ同期されなくなるが、スレーブはいざとなればバックアップから再構築すれば良いので、特にスレーブが複数あってひとつぐらいダウンしても問題がない場合には、ログの同期をOFFにすることは悪い選択肢ではない。

ワーキングセットがInnoDBバッファプールよりずっと大きい場合、松信さんが紹介していたプリフェッチのテクニックが有効であろう。ただし、そのような状況ではバッファプールを大きくするのも重要である。バッファプールが足りないと、参照の性能にも影響するからだ。

CPUリソースが枯渇している場合には、CPUを増設する(スケールアップ)か、スレーブを追加する(スケールアウト)しかないが、一般的にはスケールアウトのほうが安くつくので好まれる傾向にある。スレーブ数が増えすぎるとNICの帯域が限界に達してネットワークの遅延が生じることになるが、その場合にはマスター上でNICを増設したり、スレーブを多段構成(孫スレーブ)にするなどの工夫が求められる。

というわけで、遅延を解消して快適なレプリケーション生活を送って頂きたいと思う。

0 コメント:

コメントを投稿