サーバー群の伸縮性

ストーリー

大量のアクセスに対応する必要がある。

ライブイベントでは、瞬間的にアクセスが増加することが多い。そのため、すぐに対応するために事前準備を必要とする。

全国で放送されるテレビ番組の場合、

  • 人口 × 個人視聴率 × 参加率
  • 世帯あたり人数 × 世帯数 × 世帯視聴率 × 参加率

のように考える。多くの場合、視聴率は世帯視聴率を意味するので、計算には注意を要する。大雑把には

  • 1億人 × 個人視聴率 10% × サービス参加率 1% = 10万人

程度の規模感である。

_images/elasticity-general-concept.gif

暗黙の要求

「同時接続10万人」のような言い方をされても、接続数の話をしているのではなくて、10万人がいているくらいに思っておくこと。WebSocket や HTTP のロングポーリングだけで成立する場合をのぞいて、同時接続数ではなく、アクセス頻度のほうが重要なパラメータである。

対応するアクセスの上限を決めておくこと。仮に10万ユーザーが上限であると話しているときに、「15万ユーザーくらいはなんとかしたい」と言われることがある。これは15万ユーザーが上限である、ということだ。

明言されないが、最小トラフィックへの対応も必要である。イベント開催中に10万人がアクセスしていても、ほかの時間帯にはほとんどアクセスがない。イベント開催時以外にも機能を提供する場合でも、イベント開催中とは異なるアクセスパターンになる。常時、最大アクセス頻度に対応するだけのサーバー運用費用が確保できないため、アクセスが少ない時間帯にはサーバーの規模や台数を縮小し、費用を抑える必要がある。

依頼主は、ダウンタイムを嫌うけれど、サーバーのキャパシティを変更するときにダウンタイムが発生する方向で落ち着く場合がある。ゼロ・ダウンタイムを実現するには、そのためだけに開発工数を要する。すなわち、開発期間と費用がかかる。専属でインフラ担当をアサインするくらいの規模感だろう。しかしながら、イベント開催時間帯以外には、アクセスはとんどないのだ。少々サービスが停止する時間があって数人に不便を強いる代わりに、機能実装や性能向上に時間を費やすほうが、ビジネスとしての価値が高い場合はよくある。

解決すべき課題

アクセスが多いときと少ないときに、適切な規模と台数のサーバーを割り当て直せるか、が課題。このような特性を伸縮性(elasticity)と呼ぶ。

時間帯によって、サーバー台数を増やしたり減らしたりする「だけ」の作業ではない。サーバーサイドのアプリケーション、場合によってはクライアントも、データベースの伸縮を踏まえた実装が必要になる。それぞれの方法には、利点と欠点があるので、プロジェクトに適した方法を選択する。

解決策

話を単純にするために、アプリケーション・サーバーとデーターベース・サーバーを組み合わせを前提にする。

_images/elasticity-general-architecture-1.png

アプリケーション・サーバーはロジック/処理/HTMLレンダリングなどを担当するサーバーだ。Rails のプログラムや、WordPress 本体が動作するサーバーである。

データベース・サーバーには、時間とともに追加/削除/変更があるようなデータが入っている。Rails の ActiveRecord のデータや、WordPress のポストとかメタデータを保持する。MySQL などのデータベースが稼働している。

静的コンテンツ配信を分離

HTTP、CSS、JavaScript、画像、動画、音声などの静的コンテンツを、別系統のサーバーに移動する。おそらくはできれば、Amazon CloudFront や Akamai などの CDN (Content Delivery Network) を使う。

_images/elasticity-general-architecture-2.png

静的コンテンツの配信には、通常コンピューティング・パワーは不要で、すぐにデータを返す。一方、アプリケーション・サーバーでは処理ロジックで CPU を使うことがあるし、データベースからの待ち時間が発生することになる。特性の異なる、静的コンテンツとアプリケーション・サーバーを分けると、リソースをうまく使えるようになる。

さらに CDN を使えば、静的コンテンツを配信するサーバーの運用工数を減らすことができる。CDNは、広範囲に置かれたキャッシュサーバー群のようなものだ。スケーリングの運用をしなくていい。そのかわり、キャッシュがなかなか消えないので、寿命の設定は重要である。

さらに、この方法を使うと、クライアントとサーバーのプログラムをまったく別のところに置くことになる。そのため、クライアントの開発者は任意のタイミングで、デプロイできる。開発中はもちろん、運用フェーズでも、ちょっと絵を変えたいときに、クライアントとサーバーの開発者を同時刻に稼動させるためにスケジュールを調整する手間がなくなる。

アプリケーション・サーバーはレンダリングをせずに、Web API を介してデータの操作をすることになる。Rails や WordPress の入門書にあるような方法では開発できない。それでも、トラフィックの急増があるようなサービスでは、この分離は非常に有効である。

アプリケーション・サーバーをスケールさせる

スケールアップ

アクセスが増えると、Web API を提供するアプリケーション・サーバーの負荷が大きくなる。そこでアプリケーション・サーバーを処理能力の高いものにする。

_images/elasticity-general-architecture-3.png

管理するサーバーの数は少ないし、運用も簡単である。ただしサーバーを入れ替えるときには、ダウンタイムが発生する。後述するロードバランサーを使うと、ダウンタイムを発生させなくてすむ。

スケールアウト

アプリサーバーの数を増やすことで、処理能力を上げる。

_images/elasticity-general-architecture-4.png

クライアントがひとつのドメイン名でアクセスできるように、ロードバランサーを追加する。ロードバランサーは、受け取ったリクエストを、後段のアプリケーション・サーバーに渡り、レスポンスを受け取って、クライアントに返す。

利点はアプリケーション・サーバーのコード変更が不要であることと、スケールアップでは対処できない規模のトラフィックにも、数で対応できること。

欠点は管理するサーバーの台数が増えること、プログラムの変更をすべてのサーバーにデプロイする必要があること。手動でもできるが、頻繁に伸縮があるときには、工数が増える=人件費が増える。自動化しておくことが望ましい。

データベースをスケールさせ... る..?

データベース・サーバーの伸縮は、アプリケーション・サーバーよりも複雑である。単純にサーバーを入れ替えるとデータが消えてしまうからだ。したがって、いずれの方法であっても

  1. 一旦、サービスを停止させる。これでデータベースへの書き込みが発生しない。
  2. データベースのデータを退避する。
  3. データベース・サーバーを入れ替える。
  4. 対比したデータを、データベースに投入する。
  5. 必要であれば、アプリケーション・サーバーにデーターベース・サーバーを再接続させる。
  6. サービスを再開する。

というような作業が必要になる。

スケールアップ

データベース・サーバーをキャパシティの大きいものに変更する。

_images/elasticity-general-architecture-5.png

利点は、アプリケーション・サーバーの変更が不要で、開発工数もほとんど増えないこと。欠点は、調達できるサーバー1台のキャパシティの限界が、サービスの限界になること。

スケールアウト

複数のデータベース・サーバーを用意する方法が、いくつかある。いずれ場合も多かれ少なかれ、アプリケーション・サーバー側に変更が必要である。これらの方法を組み合わせることもある。

リードレプリカ

書き込み用にデータベースに、1つまたは複数の読み込み用のデータベースを従属させる(これをスケールアウトとは呼ばない気がするけれど、今回は台数を増やすということで、ここに書く)。

_images/elasticity-general-architecture-6.png

書き込み用と読み込み用のデータベースを構築し、設定をする。アプリケーション・サーバーでは、書き込むときと読み込むときに、別のサーバーを参照するようにする。

書き込みに対して、読み込みトラフィックが多いときに有効。書き込みがボトルネックの場合には、効果が小さい。書き込まれたデータが、読み込みサーバーから取得できるようになるまで時間がかかる。

垂直分割

データの種類ごとに、データベースを分割する。たとえば Q&A サービスを作っているときに、質問と回答を別のデータベースに置いておく。

_images/elasticity-general-architecture-7.png

質問と回答のデータベースを分離することで、データベースへのアクセスや処理を分散できるときに効果がある。Q&Aサイトの場合、まずキーワードを入力して質問リストを探し、その後で個々の質問への回答を閲覧する、という行動パターンが多い、という場合は効果が期待できる。

特定の種類のデータにアクセスが集中する場合には効果が小さい。たとえば、Twitter を作っているとして、プロフィールとツイートのデータベースを分割しても、ツイートへの読み書きが支配的に多いので、効果が期待できない。

アプリケーション・サーバーで、複数データベースにアクセスするような変更が必要である。また台数が増えたり減ったりするときにも、変更が必要である。

また、この種の分割をすると、データベースの仕組みをつかって複数のデータを組み合わせた検索ができない。たとえば MySQL などのリレーショナル・データベースは「100 回以上回答をしているユーザーの質問一覧を見る」を一発で取得できる。しかし、分割するとこの機能は使えないので、アプリケーション・サーバー側で対処する必要がある。

水平分割

水平分割は同じ種類のデータを、複数のデータベースに分割する。たとえば、ユーザーを2つのグループに分けて、ひとつのグループのデータをデータベース1に、もうひとつのグループはデータベース2に保存する。

_images/elasticity-general-architecture-8.png

分割したグループで、似たようなアクセスパターンである場合には、データベースへの負荷が分散できる。たとえば、いいね!を集計するときに、分割したグループのいいねをするユーザーの比率がだいたい同じなら、効果がある。

アプリケーション・サーバー側では、どのデータを、どのデーターベースに保持するかのロジックを持つ必要がある。

特定のグループのデータにアクセスが集中するような場合には効果が小さい。男女で分けてしまって、男性のほうがいいねをする数が多いとか。ランダムにグループ分けするのがよいけど、うまくやらないと偏りが出る。

書き込み時には分割されているけれど、読み込み時には両方のデータベースにアクセスが発生するようなときには、注意をようする。たとえば、Twitter を作っているとして、ユーザーごとにツイートをデータベースを分けたとする。けれどタイムラインを表示するには、多数のデータベースからデータを取得するので、全データベース・サーバーにアクセス負荷がかかる。(したがって、こんなデータ構造/分割の仕方ではよろしくない)

マルチマスター構成

複数のマスターが相互に接続し、どのマスターで読み書きしてもよいようなデータベース構成にする。

_images/elasticity-general-architecture-9.png

接続先は複数あるので、クライアントアクセスが起点になるアクセスは、複数に分散される。ただし、マスターどうしでデータを同期するための処理が裏側で動く。また、マルチマスターはどれかが落ちても、別のがちゃんと動いてることを実現することが大事な目的になっているので、必ずしも高速なわけではない。

クラスターの構築にスキルと手間がかかる。多数のクラスターがあるときには、相互通信の費用もかかる。

アプリケーション側には、変更がいるけど比較的容易。ただし、クラスターノードの数が増えたり減ったりするときに対応する必要がある。

多くのばあい、ダウンタイムは発生させずにできる。

マネージド・サービス

伸縮機能のあるマネージド・サービスで提供されるデータベースを使う。マネージド・サービスとは、サーバーの管理をやってくれる、オンライン上のサービスのこと。開発者は設定パラメータを指定したりするけど、サーバー自体の管理はしない。

Amazon の DynamoDB や、Google の Data Store は、ダウンタイム無しでスケールする。ただしリレーショナル・データベースではないので、それなりの設計が必要である。

Amazon RDS 構成によって、スケールするときにダウンタイムがあったりなかったりする。2016年10月14日時点では、MariaDB の設定であればダウンタイムなしで運用できるオプションがある。

マネージド・サービスを使うとインフラ構築や運用の工数が減り、インフラの細かいスキルも要らない。ただしガチガチのチューニングはやりにくい。さらにトラブルがあったときには待つしかない。逆に待っていれば解決する。

そのかわりサービスに支払う費用がかかる。開発チームにインフラ担当のエンジニアが居るのか、スキルはどの程度かが重要な要素となる。

注意点

ここでは障害耐性については言及していないが、別途対応が必要である。アプリケーション・サーバーが停止しても、再起動すれば復旧できる。しかし、データベースのデータが消失すると、その前の状態に復旧できない。また、複数のデータベースを使っていると、データベース同士でデータの一貫性を失うと、アプリケーション・サーバー側で予期せぬ動作やエラーが出続けることになる。

その他

Web サーバーのプロセスモデル

Web サーバーのプロセスモデルも、パフォーマンス要因になる。Apache の prefork モデルで、大量に時間のかかるリクエストに対処しようとすると、メモリを消費する。また、データベースへの接続数がも増えるため、データベースの負荷も大きくりがちである。

キュー

レスポンスは速くしたいが、すぐに処理が完了しなくてよい場合は、キューの利用を検討する。アプリケーション・サーバーは、リクエストを受け取ったら、データベースへの書き込みや処理をせずに、データをキューに渡す。別のプログラムが、キューからデータを取り出し処理する。

ただし、キューを使うと、アプリケーション・サーバーだけでなく、クライアント側にも変更が必要になる。サーバーと通信をした時点では、結果が確定しないため、「リクエストして、レスポンスで結果を受けとる」というプログラムができないからだ。

キューを使っても、全体のスループットが自動的に向上するわけではない。ただし、レスポンスが速くなるため、待ち時間が減り、タイムアウトも起きにくくなるので、結果として速くなることはある。