読者です 読者をやめる 読者になる 読者になる

まめ畑

ゆるゆると書いていきます

気軽なMySQLバージョンアップ

MySQL

このエントリーはMySQL Casual Advent Calendar 2013 10日目の記事です。カジュアル!
このへんでそろっとカジュアル詐欺と言われるのを防止するために、カジュアルな話を書いてみました。

MySQL5.6も正式リリースされてもうすぐ1年経ち、5.7の足音も聞こえてきている今日このごろですが皆様のMySQLのご機嫌はいかがでしょうか。
新機能や性能向上/bugfixに対応するためにMySQLのバージョンアップを行う機会や性能や不具合調査を行うことも多いかと思います。データベースのバージョンアップは特にメジャーバージョンアップの場合、パラメータのデフォルト値などの変更や仕様変更の影響(オプティマイザの変更)をアプリケーションが受けないか、性能の変化などを検証すると思います。

検証

実際に検証を行う場合、本番環境で流れているクエリをバージョンアップ先のDBに実際に流してみるのが確実かつ影響を確認しやすいと思います。しかし、これは結構難しかったりします。実際に発行されるクエリを集めるのが特に面倒。。
アプリケーション規模が小さい間は、少しアプリケーションを改修して発行されるSQLをログにだすとか、 general logを有効にしてDB側で記録してしまうというのも手なのですがパフォーマンスにも影響が出てしまう可能性があるのと、アプリケーション規模やアクセス量が大きくなるとログ量がすさまじいことになってしまいかねません。(収集するサーバへのリクエスト振り分け率を落としても。。)
また、せっかく集めてもアプリケーションの機能追加などは検証中も随時行われているため、新規発行クエリをまた集めないといけません。
これはカジュアルじゃないですね。

実際に移行してみた

先日とある大きなサービスの大きめのDBバージョンアップ(メジャーバージョン2アップ)とパラメータ調整を行った際に実際に使用した方法をご紹介します。(5.6へじゃないですよ…)

まず、DBのバージョンアップを行う場合以下の様な方法で行うと思います。

左側が旧環境、右が移行先です。
f:id:con_mame:20131208133507p:plain
現在稼働している旧Slaveの下にバージョンアップ後に使う新DB masterをSlaveとして、更にその下にSlaveをぶら下げます。(新Master達はバックアップやSnapshotから複製していきます。dumpからimportした場合でない場合は、mysql_upgradeを忘れずに)

切替時は、一旦メンテナンスモードなどに入れて、レプリケーションを切り、アプリケーションの接続先を変更します。
f:id:con_mame:20131208135921p:plain

これで完了となります。

Kage

メンテナンス前の検証やパフォーマンス・チューニングではKageを使用しました。
Kageについては
cookpad/kage · GitHub
Kageを使う時にやっておくと便利なこと - まめ畑
move to mysql5.6 // Speaker Deck
を見ていただくとわかりやすいですが、リクエストを複製して、2つのバックエンドサーバにリクエストを送り、レスポンスのdiffを取ったり、新アプリケーションのパフォーマンス計測を気軽に行うことが出来ます。ユーザさんへのレスポンスはオリジナルサーバからのものが返却されるため、カジュアルにテストを行うことが出来ます。

今回は、以下の様な構成でテストを行いました(赤線はバックアップ用の設定経路です)
f:id:con_mame:20131212211354p:plain

本番環境のproxyの下にKageサーバを配置し、その下に旧DB群につながっているアプリケーションサーバと、新DB群に繋がるアプリケーションサーバを配置しました。
新旧DBはレプリケーションされていますが、書き込みテスト時にレプリケーションエラーが発生することもあるので、最初はSELECTが殆どのページを先にテストしてしまい、その後全ページをテストするという方針で行いました。また、新Masterへ書き込みが起こるのでレプリケーションエラーも発生しますが、このテストでは、アプリケーションへの影響・パフォーマンス・オプティマイザまわりを見たいため、こちらは基本的に無視をしてレプリケーションを行うようにしました。

このテストでは、Kageサーバのパフォーマンスによっては全てのリクエストをさばけないため、Kageサーバへのリクエスト振り分け率は下げてあります。

そのためDBへの負荷は既存の本番サーバよりも低くなるため、負荷テストなどは別途行います。
そのためのSQLリストを取得するためにもこの構成は有用で、テスト用のサーバからはレスポンスがユーザさんへ返却されないため、SQLのログをアプリケーションサーバで出すように気軽に出来ます。DBのパラメータ・チューニングもカジュアルに出来ます。
この構成では検証用アプリケーションサーバもデプロイターゲットにしておくことで、新機能などで新規発行されるSQLもリアルタイムに受け付けることが出来るため、常に最新の状態を検証でき、サイトの動線やユーザさんの行動もシュミレーションではなく実際のものが取れるため、推測ではない実際のトランザクションをで検証が行えます。

ご存知の方も多いと思いますが、もし、新DBが5.6でGTIDをONにするぜ!!というナウいヤング方は、この構成ではONに出来ません。(レプリケーションクラスタ全体でONにする必要があるため)そのため、アプリケーションサーバの設定を切り替えるタイミングでレプリケーションを切るので、ここでGTIDをONにしてmysqlを再起動し有効後、CHANGE MASTERでauto positionをONにします。

Kageの設定

Kageの設定はすごく簡単で、ざっくりと

require 'kage'
require 'diff/lcs'

def compare(a, b)
  # 超カンタンにdiffとってますが、もう少し詳細にとって、fluentdなどで飛ばしてためたりする
  diffs = Diff::LCS.diff(a.split(/\n/), b.split(/\n/))
  diffs.each do |diff|
    diff.each do |line|
      p line
    end
  end
end

Kage::ProxyServer.start do |server|
  server.port = 8090
  server.host = '0.0.0.0'
  server.debug = false

  server.add_master_backend(:production, 'production-app', 80)
  server.add_backend(:newdb, 'newdb-app', 80)

  server.client_timeout = 15
  server.backend_timeout = 10

  server.on_select_backends do |request, headers|
    # GETかつpathが/adsで無いものだけ比較
    # ここではads決め打ちだが、リクエスト毎に内容が変わる可能性のあるpathは正規表現で除外
    if request[:method] == 'GET' && request[:path] != '/ads'
      [:production, :newdb]
    else
      [:production]
    end
  end

  # [:production, :newdb]が選ばれた際にここでdiffとったりログったり
  server.on_backends_finished do |backends, requests, responses|
    compare(responses[:production][:data], responses[:newdb][:data])
  end
end

この様な感じで、2つのバックエンドサーバのレスポンスを比較したりしています。
また、newdb-appではログレベルも変えてあり、負荷試験用にSQLログを出力させています。
compareのところで、bodyだけ取り出しレスポンスを比較したり、速度を見たりします。
その辺はお好みで色々いじれます。

もちろん、MySQLサーバのエラーログも忘れずに見ましょうね!

まとめ

MySQLのバージョンアップは色々と気を使う部分が多く、アプリケーションの開発速度が速いとさらに影響範囲を調べるのが手間になってしまいます。
しかし、Kageを使うことで、リアルタイムにクエリ状況を確認出来るため少しは手間が減るんじゃないでしょうか?
バージョンアップ以外にも、チューニングなどにも活用できます。

もちろんバージョンアップの際は
MySQL :: MySQL 5.5 Reference Manual :: 2.12.1.1 Upgrading from MySQL 5.1 to 5.5

MySQL Server 5.6 defaults changes (Supporting MySQL)
など、Changelogもちゃんと見ましょう!


明日は @kazeburo さんです!