tech.kayac.com

入社4年目にもなってtech.kayac初登場のせいです。

ブログ書けプレッシャーにとうとう屈する時がきました。 これで夢にkyo_agoが出てうなされなくてすみます。(彼はtech.kayacの尻たたき担当でした)

先々月「ぼくらの甲子園!熱闘編」というゲームをモバゲー内にてリリースしました。 これは去年リリースした「ぼくらの甲子園!」の続編です。 モバゲーユーザの方、是非遊んでみてください。

今回はこの「ぼくらの甲子園!熱闘編」がどういうインフラ構成になってるか紹介したいと思います。

注) 題名に「カヤック流」とはつけましたが、カヤックでは多様性を善としている風潮があり、 ゲームによってインフラの構成が違うどころか、利用しているプログラミング言語すら違います。 なので全てのゲームがこのような構成になってるわけではありません。

前提

今回のインフラ構成を決めるに至って考慮した点は「ラクに構築できるSPOFのない構成」です。

ソーシャルアプリはいまや群雄割拠の時代です。 どんなに面白いゲームを作ってもトラブルによりサービスが停止してしまったら、 ユーザーは不安を感じ、他のゲームに移ってしまいます。 反面、サーバーのハードウェア障害は避けられない運命にあるのでSPOFのない構成は必須です。

しかし、SPOFのない構成というと障害時の切り替えの仕組みを自動化するなど手間がかかるイメージがあります。 開発期間も潤沢にあるわけではないので、構築にそれほど時間も割けません。 そのため、できるだけラクに構築できてSPOFがない構成というのが理想でした。

また、ソーシャルアプリは、ヒットすれば膨大なリクエストがサーバーに押し寄せ、 逆に想定よりも格段にアクセス量が少なくなることもあります。 そのため、スケールアップ・ダウンが容易にとれることはもちろん必要です。

全体

Untitled.png

上図がインフラ構成図になります。各サーバーで利用しているミドルウェアは下記になります。

  • Webサーバー - nginx
  • アプリケーションサーバー - Starman
  • キャッシュサーバー - memcached
  • セッションストレージ ー - Kyoto Tycoon
  • DBサーバー - MySQL

基本的にどのサーバーも冗長構成をとっていますが、 キャッシュサーバに関しては今回のゲームがキャッシュをそれほど活用しておらず、 キャッシュが仮になくても動作に支障がでないため、冗長構成はとってません。

アプリケーションサーバーでhaproxyを使う

アプリケーションからはDB・キャッシュ・セッションストレージへとそれぞれのサーバーへアクセスしますが、 このアクセス先をどう管理するかが問題になります。

アクセス先決め打ちでアクセスするとそのサーバーがダウンしたときに当然障害になります。

各サーバーの間にLVSなどロードバランサー用のサーバーを用意するという方法もありますが、 ロードバランサー自体を冗長化させる必要があり、その切替にも手間がかかります。

あとはクライアントライブラリに負荷分散機能が備わっていれば別ですが、 なかなか要件を満たすライブラリはなく、自作する必要があります。

そこで今回はhaproxyを各アプリケーションサーバー全てにインストールし、 アプリケーションサーバーから各サーバーへのアクセスは全てhaproxyを経由するようにしました。

haproxyの設定は以下のようになります。

listen mysql-master
        bind 127.0.0.1:3306
        mode tcp
        option mysql-check user haproxy
        server master k2-db01:3306 check

listen mysql-slave
        bind 127.0.0.1:3307
        mode tcp
        option mysql-check user haproxy
        balance roundrobin
        server slave0 k2-db02:3306 check
        server slave1 k2-db03:3306 check
        ....

listen kyototycoon
bind 127.0.0.1:11222
mode tcp
server master k2-cache02:11222 check
server backup k2-cache03:11222 check backup

これで各アプリケーションサーバーからはlocalhostの3306ポートがDBのmaster、 3307ポートがDBのslaveとして利用できるので、アプリケーションにはアクセス先を決め打ちにすることができます。

DBサーバーを1台余計に用意する

DBサーバーはよくあるmaster/slave構成です。

insert/updateはmaster、selectはslaveへアクセスしますが、 slaveへのアクセスは各アプリケーションサーバーのhaproxyを経由して分散されます。

仮にslaveがダウンした場合はそれをhaproxyが検知してそれ以外のslaveでサービスを継続します。 そのため、仮に1台ダウンして他のサーバーだけでサービスを続行しても、 支障が出ない程度にあらかじめスケーリングしておくことが必要です。

また、アプリケーションサーバーからアクセスがないslaveサーバーを一つさらに余計に用意しています。 これは主にはデイリーのバックアップ用途、スレーブ増設の際のコピー元になるためですが、 データの詳細な調査が必要になった際にSQLを手動実行出来る環境にも利用できます。

手動でSQLを発行して調査を行う際は重いクエリーを発行してしまうことが往々にして有ります。 それをサービスに影響なく実行するためにあらかじめ用意していると安心です。

参考: MySQLで参照の負荷分散を行うslaveは3台から構成するのがよいのでは

セッションストレージにKyoto Tycoon

当初はセッションデータを保持するためにキャッシュサーバーと同居して、memcachedを使うことを考えていましたが、 memcachedでは他のデータの容量次第でセッションデータがロストする可能性があります。 またレプリケーション機能が標準ではないため、冗長構成が取りにくいという欠点があります。

いつ消えてもサービスに影響がないキャッシュデータであればこれでも問題ないのですが、 セッションデータのロストはサービスの致命的な障害にはならないものの、 ユーザの利便性を大きく損なう可能性があるため、セッションストレージはKyoto Tyconnを利用しました。

他のKVSではなく、Kyoto Tycoonを採用した理由は主に以下です。

・Memcachedプロトコルをサポートしてること ・デュアルマスター構成が取れること

内部的な事情を言うと、当初memcachedを使うことを想定していたので、 memcached用のクライアントライブラリで開発をしていました。 そのためmemcachedプロトコルを使えるのは助かりました。

また、Kyoto Tycoonはデュアルマスタ構成が可能なので冗長化が容易です。 セッションストレージもDBのslave同様、各アプリケーションサーバーからhaproxyを経由してアクセスされるので、 仮に一方のサーバーがダウンしたとしても、もう一方のサーバーを利用してサービスを継続することができます。

Kyoto Tycoonの起動オプションはは下記のように設定してます。

ktserver -plsv /usr/local/libexec/ktplugservmemc.so \
    -plex 'opts=f#port=11222' -ulog /var/lib/kyototycoon/master1-ulog -sid 1 -mhost k2-cache03 \
    -rts /var/lib/kyototycoon/master1.rts \
    '/var/lib/kyototycoon/master1.kch#opts=l#bnum=8000000#msiz=5g#dfunit=8'

最後に

以上で、ひと通りのサーバーに対して冗長構成がとれていますが、ひとつだけ冗長構成が取れてないサーバーがあります。

DBサーバーのmasterです。

DBサーバーの冗長化はまだこれといった解決策を見いだせていませんが、 仮に障害が起きた場合はslaveを昇格するなどの対応をとることにしています。 下記のようなツールの開発が行われていますので、要チェックです。

Announcing MySQL-MHA: "MySQL Master High Availability manager and tools"

また、実を言うと以上の構成は弊社の「こえ部」のインフラにインスパイア(!)されたものです。

そのインフラの立役者のfujiwaraがYAPC::Asiaに登壇してそのサービスの裏側についてお話しします。

Perlで構築された中規模サイトのDC引っ越し記録

興味のある方は是非足をお運びください!

カヤックではfujiwaraと呑みたい技術者を大募集しています!

このまえ登り坂の途中でロードバイクのタイヤが破裂しました。ながたです。 今回はバッチ処理について書いてみようと思います。

バッチ処理?

Webサービスの処理開始条件は、大まかに次の2つに分けることができます。

  • ユーザーのアクションに起因するもの
  • ユーザーのアクションに起因しないもの

このうち後者の処理をバッチ処理が担当することになります。

バッチ処理の担当分はさらに、

  • 特定の条件(時間やサービスの状態)で実行するもの
  • 手動で実行するもの

の2つに分けられます。 今回はこの「手動で実行するもの」について書きたいと思います。

バッチを手動実行するのはどんなとき?

バッチ処理を手動で実行するのは、十中八九イレギュラーな状況が発生したときです。 ルーチンワークや実行の条件が決まっているものは何らかの方法で自動化できるはずです。

そしてイレギュラーな状況のほとんどは不具合が発生したとき。 つまり 重ねてミスができない状況 です。

ミスができない状況で、できるだけミスの発生を抑えるバッチ処理の (主にヒューマンエラーを防ぐための)ノウハウを紹介していきます。

本番と同じ環境でテストする

大前提です。

「開発環境ではうまくいったんだけど・・・」なんていいわけは通用しません。 必ず 開発環境で本番と同じ環境を再現してテストしましょう。

バッチ実行前にデータのバックアップをとる

処理に間違いがあってもすぐに復元できるよう、 処理の実行で書き換えられる可能性があるデータはバックアップをとっておきましょう。

バッチファイルに処理の本体を書かない

これはテストをしやすくするための手段です。

バッチファイルに直接処理を書くと、テストが非常にやりにくくなります。 よほど単純な処理でない限り、モデルに移して(できれば既存の処理を再利用して) テストしやすい構成にしましょう。

コマンドライン引数はわかりやすく

バッチ処理にコマンドラインから引数を渡すときは、 よりわかりやすい名前付きの引数を使うようにしましょう。

$ perl bat.pl 10 1 "test"

$ perl send_message.pl --target=10 --category=1 --message="test"

デフォルトはdry-run

「うっかり実行しちゃった(テヘ」 なんてことがあっても焦らないように、 何も指定しなければ dry-run(実行はするがデータは書き換えない)になるようにしましょう。

dry-run

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!"

実際に実行

--prod を付けなければ実行されないようにしておく。

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!" --prod

実行前に確認ダイアログを出す

これもうっかり対策。 実際に処理を行う前に、どのような処理が行われるか表示して、 問題があればそこで中断できるようにしましょう。

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!" --prod
send message # 処理の内容を表示
target  : 10
category: 1
message : wryyy!!

continue? [y/N] # ここでnとすれば中断できるようにする

実行前に処理の内容を確認できるだけで、安心感が段違いです。

実行に必要な条件を組み込んでおく

状態によっては実行してはいけない処理がある場合は、 その状態の判断をバッチに組み込んでおきましょう。 「実行するときに気をつけていれば大丈夫!」 なんて思っていると後で泣くことになります。

  • 二重実行するとまずい処理
  • 特定の時間に実行するとまずい処理
  • 逆に、特定の時間以外に実行したくない処理

など、条件を洗い出しておきましょう。

ログを残す

バッチ処理でデータを変更したら、その内容をログとして残すようにしましょう。 標準出力をファイルに書き出してもいいですし、 バッチ内でファイルに書き出す仕組みを入れてもいいでしょう。

標準出力をログとして利用する場合は tee を使うといいでしょう。 標準出力・ファイル両方に出力されるので、状況確認しつつログを残すことができます。

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!" --prod | tee send_message.log

ログを残すことには、おもに以下の2つの理由があります。

  • あとで処理の内容が間違っているとわかったときに、データの復元ができるようにする
  • 途中でバッチ処理が停止したときに、途中からやり直せるようにする

もちろん後者は、途中で処理が止まる→途中から再実行 ができるように、 以下のようにつくっておく必要があります。

  • 一部分だけ完了しても問題がない
  • 途中から再実行するためのオプションを付ける、あるいは処理状況を見て自動で中断箇所から再開するようにする
  • 再実行してもすでに完了している処理・再実行時に行われる処理に影響がない

処理が完了している部分としていない部分が混在するとまずい場合は、 すべての処理が完了するまでデータを書き換えないようにしておきましょう。 このあたりは場合によりけりです。

実行状況の表示

バッチ処理の実行状況を出力するようにしておくと、

  • 実際に処理が行われているのか
  • どこまで処理が終わっているのか

がわかって安心できます。

バッチの完了を通知する

バッチ処理の間、常に張り付いているわけにはいかない。 でも処理が終わったらすぐに確認したい・・・。

というときはバッチ完了時にメールやIMで通知するようにしておきましょう。

バッチ処理の終了時刻が予想できるなら、 終了よりも少し前に通知を送るようにするとタイムロスを防げます。

手動バッチに限らず、 何かしらイレギュラーな状態(自動バッチが途中で止まった、データの処理数が想定より少ない、etc) になったら通知するようにしておくと、トラブルに迅速に対応できるようになります。

ネットワーク切断対策

サーバー上で長時間走らせるバッチの場合、 うっかり途中でネットワークが切断したりすると泣きをみることになります。

screentmux 上で実行したり、 nohup を付けて実行するなどの対策をとっておきましょう。

おわり

今回あげたものは、ちょっと手間をかければすぐに実行できるものばかりです。 保険の宣伝に出てきそうな 少しの投資で大きな安心 という言葉がそのまま当てはまります。

以上、batノウハウでした。

カヤックでは自動化が得意な技術者も募集しています!

Mac Book AirにWindows7を入れて常用しています。ago@kyo_ago)です。

先週、HTML5とか勉強会、Mozilla勉強会と発表を行って来たので、その時使った資料を紹介したいと思います。

HTML5とか勉強会

HTML5とか勉強会ではJSクイズゲームの作り方(HTML5実力テストの紹介)と題して15分の発表を行いました。
(ただ、最後は結構時間が余ってしまいました。。。)

ちなみにJSクイズゲームの作り方(HTML5実力テストの紹介)ではHTML5実力テスト開始1週間時点での平均点を紹介しています。
HTML5実力テストに関してすでにご存じの方は10Pくらいまで飛ばしてご覧ください)

Mozilla 勉強会@東京 6th

Mozilla 勉強会@東京 6thではjQueryで破棄されたrequestAnimationFrameとJSでのアニメーション実装で注意することと題して5分の発表を行いました。

jQueryで破棄されたrequestAnimationFrameとJSでのアニメーション実装で注意することではjQuery 1.6.0で採用されたrequestAnimationFrameがjQuery 1.6.3, jQuery 1.7b1で削除された経緯と、requestAnimationFrame自体の使用法を紹介しています。

カヤックでは勉強会に積極的な技術者も募集しています!