はてなで大規模サービスのインフラを学んだ

中〜大規模サービスのインフラの様子を知りたいアプリケーションエンジニア向けに、もともとアプリケーションコードを書いていた視点から、個人的な体験をベースにはてなで大規模サービスのインフラを学んだ過程や学んだ内容の一部を紹介します。

Webアプリケーションのブラックボックス

今年もはてなインターンの時期が近づいてきた。 毎年ではないけど、はてなインターンでは「インフラ講義」というのをやっている。 今年はインフラ講義の講師としてアサインされたのでちょうど何を話そうかを考えているところだ。

僕は2011年のはてなインターンに参加していた。 今でこそWebオペレーションエンジニア(インフラエンジニア)をやっているが、当時はWebアプリケーションエンジニア向けのプログラムしかなかったため、インターンでは主にWebアプリケーションを書いていた。 というよりそのときは、自分がインフラをやるとは全然考えていなかった。 当時のインフラ講義は、後に同僚となる id:halfrack さんが担当されていた。はてなの自作サーバ、はてなブックマークの構成事例、はてなのネットワークに関する内容だったと思う。 その頃はリバースプロキシが何かもよくわからない状態だったので、講義内容の半分以上はよくわからなかった。 しかし、もともと計算機のアーキテクチャや分散システム(複数台のサーバを使って何かをするぐらいの雑なイメージ)に興味があったため、よくわからないけどなんかおもしろそうだなという感想を持ったのはよく覚えている。

インターンの前後でRuby on Railsを使って何かを書いていた気がする。 サーバサイドのWeb開発はまずRailsのようなウェブアプリケーションフレームワークの使い方を覚えるというのが最初のステップになっている。 とりあえずTwitterと連携するために、OmniAuthとかDeviseかなにかを使って、OAuth認証するためのRails用のプラグインみたいなやつをよくわからずに勘で使って、2~3日かけて、Twitterでログインできるようになると大喜び。 そこから、適当にツイート情報やフォロー・フォロワー情報を取得して、ちょっとロジック書いたりしているとそのうち飽きる。 データベースのスキーマ設計をちょっと気にするぐらいはあるけど、リレーションの正規化とか意識せずに勘で設計したりする。 データベースのテーブルにインデックスを作成しておけばなんか速いらしい。 大学の情報科学科に所属していたため、さすがにリレーションの正規化は一応学んでいるし、B+treeインデックスを直接学ぶ機会はなかったけど、なんとなく雰囲気はわかる。 とはいえ、はじめてのサーバサイドのWeb開発の雰囲気はこんな感じなんじゃないかと思う。

Twitterの日本人エンジニアに聞く、世界に通用するハッカーになるには - YouTube のインタビューが結構好きで、動画の公開当時から何度か観ている。 インタビュー中(12:00~13:00ぐらい)に、意外にもTwitterの開発環境は普通のRailsと同じだねという文脈で「(Railsの)モデルの部分はすごいインフラがその先にある」という話がでてくる。 確かに最初は大規模サービスのインフラってまさにこんな雑なイメージがあった。 要はO/Rマッパーのその先が完全にブラックボックスだった。

例えばはてなブックマークのシステムを眺めてみると相当複雑ではある。 10年運用しているシステムなので、歴史的事情による負債が蓄積していたり他のサービスと連携していたりするとどうしても複雑になる。 歴史的事情を除いたり、はてなブックマークのシステム単体でみたとしても、それなりには複雑だと思う。

はてなインターンで自分が書いたコードは、一応今もプロダクションで動いているけど、実はデータベースが垂直分割されているとか何も意識していなかった。 なんかMySQLのスレーブが並んでるらしいぐらいのイメージ。 インターン期間中ではとてもではないけどシステムの全体像は掴めない。

一方で、個人開発のサービスの構成は非常に単純だ。もちろん、例外はいくらでもあるとは思う。 今はHerokuのようなPaaSを使うのが一般的なので、そもそもサーバというものを意識することは少ない。 多少がんばってデプロイするなら、さくらVPSを使ったりするかもしれない。 後者なら、LinuxサーバにNginxやMySQLをインストールして、アプリケーションコードをgit cloneやrsyncを用いてサーバに配置して起動コマンドを叩いたりする。 もうちょっとがんばって、ChefやAnsibleを使って、これらの手順を自動化することまでやるかもしれない。

個人開発の視点でみると、大規模システムというのはとにかくすごいというイメージがある。 データベースの使い方とかデータ量が全然違うだろうし、ロードバランサとか使って複数台構成にしててなんかかっこいい。 ネットワークのレイヤになるとイメージもできない。 アプリケーションそのものというより、あれこれミドルウェアを組み合わせて使っていたり、ネットワークがすごいという、なんとなく漠然としたイメージがあった。

しかし、最近重要だと感じるのは、たとえ大規模なインフラであろうともシステムの中心となるのはあくまでアプリケーションサーバである、ということだ。 超大規模なシステムだとよくわからないけど、はてなぐらいの規模ならそのように感じる。 特にパフォーマンスの大部分が思った以上にアプリケーションサーバのロジックや使用するシステム系のライブラリに依存すると思っている。 以降では、アプリケーションサーバを中心としたインフラの世界をみてみよう。

Webアプリケーションフレームワークの向こう側

先述したように、WebアプリケーションフレームワークとO/Rマッパーにより、開発が楽になった反面、その先はブラックボックスとして扱われがちになってきた。 極端な場合、フレームワークより下のレイヤが「インフラ」として扱われるようになっていると感じることがある。

そのような状況で、「インフラ」に降りていくためにはどうしたらよいか。

僕の場合は、はてなにアルバイトにきたときに、すでにプロジェクトごとに最低限のフレームワークを作るという文化だった。 Perlの世界には、Plack(RubyでいうところのRack)、URLルーター、データベースアクセス用の小さなモジュールなど、フレームワークを作るための部品が集まっていたため、自分で好きな部品を組み合わせてフレームワークを作ることは難しくなかった。 O/Rマッパー(というよりは重めのデータベースアクセスライブラリ)を自作したりもした。

Webアプリケーションフレームワークを作ろうとすると、リクエストの受信から対応するレスポンスの送信までのライフサイクルを意識するようになる。 リクエストを受信している部分を探しにいくと、Plackの下で動いているWebサーバ(Starletなど)のコードに行き当たる。 UNIXプロセスとソケットの世界。 このあたりの仕組みがわかってくると、例えばアプリケーションコード内でグローバル変数に突っこんだオブジェクトはどうなるのかといったことがわかるようになる。 StarletはPreforkモデルのWebサーバであり、後続のリクエスト処理をするために同じプロセスを使いまわしていくため、グローバル変数は後続のリクエスト処理中にも参照され得るとかそういうことがわかる。 マルチスレッドモデルならば、全てのリクエスト処理中に同時にアクセスされ得るということもわかる。 Webサーバの仕組みについては、以前ブログに書いたので、詳しくはそちらを参照してほしい。

一方で、O/Rマッパーを作ろうとすると、データベースとの接続管理に気を使うようになる。 既存のO/Rマッパーのコードを読むと、接続が切れていないかを PING で確認して、再接続するような処理が入っていたりする。 どのようなケースで接続が切れるのか、再接続しないとどうなるのかということが気になり始める。 Webアプリケーションにおけるデータベース接続については、以下のエントリに書いてある。

システムの規模が小さかったり、システムの稼働率を気にするような環境でなければ、以上のような内容はそれほど気に留めなくてもいいかもしれない。 例えばデータベースの再接続をやらないと、データベース側でフェイルオーバしたときに、古い方のデータベースへの接続しているつもりになったまま待ち状態になるプロセスがいたりする。 このように接続が残留する理由はいくつかあるが、例えばAWSのRDSを使用していると アクセス数が少なければ問題ないが、アクセス数が多ければあっという間に残留プロセスがWebアプリケーションサーバのワーカープロセスを占有して、新規のリクエストを受け付けられなくなることがある。 こうなるともうアプリサーバを再起動するしかない。

@sonotsさんが別の視点で似たようなことを書かれている。運用を楽にするためのアプリケーションコードを書くということ - sonots:blog

はてな(と日本のPerlコミュニティの一部)が必要な分だけ自作するような文化だったことは自分にとって幸運だった。 これがRailsだったら複雑すぎてコードを追って仕組みを把握するのはたいへんだったと思う。

なぜ複数のサーバが必要なのか

以上がインフラ視点でみたアプリケーションサーバの話になる。 ここまでは、基本的に単体のサーバの話だった。 一方で、中〜大規模なインフラは基本的に複数のサーバで構成される。

Webサービスのサーバ構成というと一般にリバースプロキシサーバ、アプリケーションサーバ、データベースサーバの3層だ。 Webサービス3層構成については、後述するとして、そもそもなぜ複数のサーバが必要なのかということを整理しよう。

サーバが複数台必要な理由は、システムの可用性の担保と負荷分散、疎結合化である、と思っている。 いずれか1つだけの理由で複数台構成にすることもあれば、複合的な理由で複数台構成にすることもある。

まず、システムの可用性について。サーバは物理的に壊れたり過負荷やオペレーションミスで停止したりすることがある。したがって、1台壊れても大丈夫なようにスタンバイ機を用意しておいて、スタンバイ機に処理を引き継ぐというようなことをやる。これを冗長化と呼ぶ。必ずしもこのようなアクティブ・スタンバイ構成をとるわけではないが、少なくともはてなでは代表的な冗長化の方法だ。 冗長化するためにkeepalivedやHeartbeatを使うことが多い。 これらのツールは基本的にアクティブ・スタンバイ間で疎通監視をして、アクティブ側に異常があると判断すればスタンバイがアクティブに昇格する。 昇格した場合、アクセス先を切り替える必要がある。とはいえ、アプリ側でフェイルオーバを検知して、別のIPアドレスへ向き先を変えるのはかなり難しい。 実際には、VIPという仕組みを用いて、サーバの実IPアドレスとはまた別の冗長化用IPアドレスがアクティブ側に付けられることにより、アプリ側では何もせずに、向き先を変更する。

次に、負荷分散について。LVSやHAProxy、ELBのようなロードバランサを前段において、同じ役割のサーバ群に対して、リクエストやSQLクエリを振り分ける、というのが代表的だ。 データベースの場合、後述する垂直分割や水平分割を用いることもある。 Nginxのようなリバースプロキシもロードバランサとして使えるため、専用のロードバランサを導入するのが面倒ならばそちらを使ってもよい。 ロードバランサには、先述したVIPのようなエンドポイント(FQDN)が設定されていて、プロキシやアプリケーションからはそのエンドポイントに向けてアクセスさせれば、勝手に振り分けられる。

最後に、疎結合化について。あまり疎結合化という言葉が適切かどうか自信がない。 例えば、同じホストにアプリサーバサーバとデータベースサーバを同居させることはできるといえばできる。 実際には、問題の切り分けのしやすさや、アプリサーバとデータベースサーバの負荷を分散させたりといった目的で、ホストごと分けることが多い。 役割の異なるものは一緒にしないというのは他の分野でも通用する話かもしれない。 ただし、役割が異なるからといって、全部分けたほうがよいわけではない。いたずらにサーバの数が増えてしまう恐れがあるため、同居させても困らないと判断すれば同居させてもよいと思う。

突然のWebサービス3層構成

Webサービスの標準的なサーバ構成はリバースプロキシサーバ、アプリケーションサーバ、データベースサーバの3層となる。 なぜ3層構成なのかは説明しようとすると意外と難しい。 こういうものだよねとスルーされることが多いような気がする。

図では省略しているが、実際には、リバースプロキシ、アプリケーションサーバ、データベースの参照専用スレーブの前段にロードバランサを挟むことが多い。 アプリケーションサーバとデータベースサーバがあるというのは特に疑問はない。 手元の開発環境でも、アプリケーションとデータベースを起動して開発しているので、それらを別々のサーバに分けただけだ。 リバースプロキシというのが何なのかが主な関心になると思う。

リバースプロキシ

リバースプロキシの説明として、サーバ・インフラを支える技術の2章に以下のような記述がある。

リバースプロキシを利用すると、クライアントからの要求がWebサーバへ届く途中の処理に割って入って、さまざな前後処理を施すことができるようになります。 これが、リバースプロキシ導入のメリットです。より具体的な利点/機能には以下が挙げられます。

  • HTTPリクエストの内容に応じたシステムの動作の制御(L7スイッチが果たす役割と似ている)
  • システム全体のメモリ使用効率の向上
  • Webサーバが応答するデータのバッファリングの役割
  • Apacheモジュールを利用した処理の制御

リバースプロキシはさまざまな機能を持つため、これが正しいリバースプロキシの説明というのは難しい。 NginxやApacheなどのよく現場で採用されているリバースプロキシは、プロキシとしての機能以外にロードバランシングやキャッシュの仕組みを持つ。 要はアプリケーションサーバの前段に制御可能ポイントを増やして、パフォーマンスを向上させたり、システム全体の運用効率を上げたりすることができるといったざっくりした役割がある。

例えば、CSSやJS、画像ファイルのような静的なコンテンツをリバースプロキシで配信させて、アプリケーションサーバの負荷を減らし、それらのコンテンツのレスポンスタイムを小さくできる。 または、HTTPヘッダのUser-AgentやIPアドレスをみて、迷惑行為を行うアクセス元に対してリバースプロキシで遮断して、アプリケーションサーバへアクセスさせないことができる。 もしくは、アプリケーションサーバの前段のロードバランサとして使用できる。 あるいは、検索クローラ用のアクセスを処理させるための専用のアプリケーションサーバ群を用意して、リバースプロキシがHTTPヘッダのUser-Agentをみてクローラかどうかを判定して、クローラ用かユーザ用かどちらかのアプリケーションサーバへ振り分ける。 これは、検索クローラのアクセスが増えてきて、アプリケーションサーバのpreforkされたワーカープロセスを占有して、ユーザのリクエストを受け付けられないといった問題に対する対策だ。

リバースプロキシの導入は必須というわけではない。 システムがごく単純でパフォーマンスやサーバ負荷に困っていないなら、アプリケーションサーバの前段にELBのようなロードバランサを置いて、いざというときにアプリケーションサーバを増やせるようにしておくだけで十分だろう。 もしくは、リバースプロキシはアプリケーションサーバに比べてメモリ使用量が控えめで動作が高速なため、システムの規模が小さいうちはアプリケーションサーバと同じサーバ上に配置することがある。

アプリケーション

アプリケーションサーバはWebサーバ上でアプリケーションロジックを実行する。 データベースにアクセスしたり、memcached/RedisのようなKVS/NoSQLサーバにアクセスすることもある。 詳しくは 2015年Webサーバアーキテクチャ序論 - ゆううきブログ に書いた。

昔はWebサーバとしてApacheを用いることもあったが、最近ではアプリケーションロジックを書く言語と同じ言語で書かれたWebサーバを用いることが多い。 Perlの場合は、StarletやStarman、Monoceros、Rubyの場合は、UnicornやThin、Passengerなどだろう。 これらのWebサーバは前段にリバースプロキシが置かれている前提で実装されていることがある。 Webサーバは通常グローバルに公開されて、第三者であるユーザのアクセスを直接受けるものだ。 当然、おかしなフォーマットのリクエストをくわされることがあるため、さまざまなエラー処理を実装しておかなければならない。 したがって、リバースプロキシを用いると、NginxやApacheのような実績のあるWebサーバをユーザに公開し、アプリケーションロジックを実行するWebサーバは内部に隠すという形になる。 ユーザのHTTPリクエストを直接受けなければよいため、リバースプロキシではなくELBのようなHTTPレベル(L7)のロードバランサを使用すると、ELBがユーザのリクエストをパースしてくれる。

データベース

データベースとして、ここではMySQLを選択したとする。 MySQLは基本的にマスタ1台、スタンバイ機をいれて2台で運用することが多い。 パフォーマンスが落ちてきたり、ディスクが足りなくなってきたら、マスタのサーバをスケールアップする。 基本的に、頻繁に参照されるテーブルのデータ分が全部メモリに載っていて、テーブルに適切にインデックスがはられていれば1台でかなりの規模まで捌ける。

マスタのスケールアップに限界がきたら、負荷分散のために参照専用のスレーブを用意する。

MySQLなどのマスタ・スレーブ型のデータベースにおける負荷分散というと、参照専用のスレーブを並べるというのはよく知られている。 基本的にマスタに更新クエリを投げて、レプリケーションという仕組みで、スレーブに更新内容を伝搬させる。 参照専用のスレーブに対して、前段にロードバランサを配置し、アプリケーションサーバからはロードバランサ経由でスレーブにクエリを投げる。

参照クエリを参照専用のスレーブに投げるにはどうするか。 これは単にSELECTで始まるクエリを参照専用スレーブに投げればよいという話ではない。

レプリケーションは基本的にマスタ・スレーブ間のデータ更新タイミングに遅延がある。 つまり、アプリケーションサーバからみてマスタへの更新が成功して処理が戻ってきても、スレーブではまだ更新内容が反映しているかが不定であるということだ。 MySQLのレプリケーション遅延については @nippondanji さんの資料が参考になる。 漢(オトコ)のコンピュータ道: MySQLにおけるレプリケーション遅延の傾向と対策

レプリケーション遅延がアプリケーションにとって問題になるケースがある。 例えばマスタに更新した内容をアプリケーションからすぐに参照しようとすると、参照クエリがスレーブに投げられて、スレーブにはまだ更新内容が伝搬しておらず、意図しないデータを取得することになるかもしれない。 したがって、アプリケーション側でレプリケーション遅延を考慮してマスタに参照クエリを投げなければならないことがある。 このため、マスタにクエリを向けるのか、スレーブにクエリを向けるのかを都度選択できるようにするために、マスタ用のDBのインスタンスとスレーブ用のDBのインスタンスを用意したりする。 このような問題があるため、なるべく参照専用スレーブの導入は避けて、マスタをスケールアップするのが楽だ。

MySQL(に限らない)のサーバ構成について、pixivの @harukasan さんが非常にわかりやすい資料を書かれている。 http://blog.harukasan.jp/entry/2014/09/11/181006

参照用のスレーブによる負荷分散は当然のことながら参照クエリのみをスケールさせる。 書き込み系のクエリを負荷分散したければどうするのか。水平分割(シャーディング)、垂直分割をするわけだが、このあたりについても、上記の資料にわかりやすく書かれている。

余談だが、2~3年ぐらい前にはてなの面接を受けたときに、とあるシステムをその場で設計して議論してくれと言われた。 リアルタイム通知系のシステムだったと思う。 リアルタイム系なので、書き込み系のクエリが多いことが予想できる。 データベースの書き込みで詰まった時どうする?という問いにうまく答えられなかったことを覚えている。 シャーディングってなんだっけ。水平分割や垂直分割をちゃんと理解していなかった。

ちなみに、RedisのようなNoSQLを使うのが正解と言われたような気がする。 確かに、という感じだった。

その他のコンポーネント

上記の3層構成以外によく用いられるコンポーネントを紹介する。

キャッシュは麻薬

パフォーマンス対策の典型の1つとして、キャッシュがある。 この場合、キャッシングといったほうが正しい気もするが、キャッシュと呼ぶことにする。

キャッシュは様々なレイヤーで使われている高速化のための技術だ。 キャッシュという仕組み自体は明示的に使用しなくても暗黙的に使わていたりすることが多い。 CPUのキャッシュメモリ(L1,L2,L3キャッシュなど)や、OSのページキャッシュ、MySQL InnoDBのバッファプールなどがそれにあたる。 一方、Webサービスで明示的に使用するキャッシュの仕組みは、memcachedのような分散キャッシュサーバを使ったり、Varnish/Squidのようなキャッシュ機能をもったリバースプロキシを使うことが多いだろう。 画像など静的なコンテンツは、CDNで配信したりする。 もちろん、アプリケーションのプロセス内でキャッシュすることもある。生成したオブジェクトの使い回しなんかはよくやってると思う。

memcachedとVarnish/Squidをどのように使い分けるのか。 後者はアプリケーションサーバが生成したページを丸ごとキャッシュするのに向いている。 はてなブックマークのホットエントリのような多くのユーザが同じ内容をみるようなサービスに適している。

一方で、昨今のWebサービスはユーザにあわせて動的にコンテンツを生成することが多いため、他のユーザが生成したコンテンツをキャッシュしにくいという問題がある。 その場合、memcachedのようなキャッシュサーバに粒度の小さいデータをキャッシュしておく。

キャッシュは麻薬という名言がある。 キャッシュは同じデータを複数箇所に格納することになるため、各箇所でそれぞれデータの整合性がとれているかを意識する必要がある。 オリジルのデータを変更した場合に、なんらかの方法でキャッシュデータを更新しなければならない。

サービスの性質を利用して、場合によっては、オリジルのデータの更新にあわせて、キャッシュデータを逐次更新する必要がないことがある。 その場合、キャッシュデータにキャッシュしておく期限を決めておくといったことをする。 期限がきれたときに一斉にオリジンへアクセスが殺到するといった問題(Thundering Herd問題)がないかどうかを意識する。

Facebookでは、数千台のmemcachedサーバを運用しているらしく、その様子を論文として公開している。 システムの規模が増えるにつれて、段階的にさまざな工夫をしている。 Facebookの数千台規模のmemcached運用について - ゆううきブログ

キャッシュについて興味深いツイートがあったので言及しておく。

キャッシュにより高速化したいのはI/Oアクセスだけではないというのが解答だと思う。 前段でキャッシュすればするほど、後続のサーバへリクエストやクエリを送信して受信するまでのネットワークレイテンシやCPU処理をスキップできる。 データベースに格納されているデータ構造では、どうしてもアプリケーションに必要なデータを取り出すまでに時間がかかるため、アプリケーションにとって使いやすいデータ構造でmemcachedに格納していくという考え方もある。

飛び道具としてのKVS/NoSQL

先ほど、更新系クエリの多いシステムでRedisを用いるという話をした。 Redisはmemcachedと異なり、基本はオンメモリデータベースだが、ディスクにデータを保存することができるため、永続ストレージとして使用できる。 Redisの更新性能は永続化のモードにより大きく変わるが、逐次書き込みモードでなければ基本はオンメモリなので高速だ。 MySQLのようなRDBMSは基本的にディスクストレージであり、データの書き込みに加えて、トランザクションログを書き込むコストがある。 その代わり、データベースがクラッシュしたときのデータロストのリスクが低いなどの、ストレージとしての安心感がある。

このように負荷削減やパフォーマンス向上のために、RDBMSに向いていない機能をKVSやNoSQLと呼ばれるデータベースやキャッシュサーバに肩代わりさせることがある。

例えば時系列データベースがそうである。時系列データベースも非常に書き込みが多い。 Mackerelで使っている時系列データベースについては下記のエントリに書いた。

非同期処理

非同期という言葉は様々な文脈で語られることが多い。 ここでの非同期処理は、アプリケーションサーバの処理のうち、メールの送信やTwitterへの投稿など外部のシステムに依存しているものや実行時間が長いものを分離して、あとで実行するというものだ。 外部のシステムに依存していると、外部のシステムの障害に引きづられて、システムが停止することがある。 集計系の処理でどうしても実行時間が長いものはユーザに同期的にコンテンツを返却することを諦めて、送らせて反映させることで解決したりする。 メールの送信など、ユーザにとくにコンテンツを返却するものがない処理は、非同期化しやすい。

非同期処理の実現方法はさまざまだが、はてなでは基本的にMySQLをストレージとしたジョブキューを使用している。 ジョブキューの仕組みの概要はそれほど難しくない。 非同期化したい処理をジョブとして、ジョブを表現したクラスの特定名のメソッドとして実装しておき、ユーザのリクエスト処理中に、ジョブキュー用のデータベースに該当クラス名とメソッドパラメータを突っ込んでおく。 これとは別にワーカーと呼ばれるジョブを実行するためのプロセスが動いていて、ジョブキュー用のデータベースに新規のジョブがないかどうかを定期的にポーリングしてチェックしている。新規のジョブがあれば、ジョブレコードからクラスやパラメータを取得して、該当ジョブのロジックを実行する。

例えばユーザ登録メールの送信は多少遅れて実行してもよいが、実行されないことがあると困るため、MySQLのような永続ストレージにジョブを登録するのがよい。 ジョブの種類によっては、ロストしてもよいがとにかく数が多いことがあるため、オンメモリのジョブキューを使うこともある。

サーバ構成としては、ジョブキュー用データベースとワーカー用のサーバが必要になる。

新しいジョブキューシステムの設計について、以前エントリに書いた。現行システムで困っていることをまとめている。

バッチ処理

定期的に情報を更新したい場合、バッチサーバを用意して、バッチサーバ上のcronでロジックを実行する。 毎朝ユーザにメールを配信したいというような用途に用いる。 バッチサーバ上では、スクリプトが動作してロジックを実行する。

バッチ処理は、データベースの内容を集計するような重いクエリが投げられることが多く、マスタDBの負荷が上がってしまうことがある。 マスタDBの負荷により、システムが不安定になることを防ぐため、バッチ処理専用のスレーブを用意することがある。

Mackerelの場合

3層構成に加えて、キャッシュサーバ、KVS/NoSQL、ジョブキュー、バッチ処理といったオプション的なサーバ構成について説明した。

実際のシステムはどうなっているかの参考のためにMackerelのサーバ構成の図だけを用意した。 基本的なシステムの概要については、Mackerelで採用している技術一覧とその紹介 - Hatena Developer Blog に書かれている。

もう少し詳細なサーバ構成を下図に示した。かなり簡略化した図になっている。特にGraphiteの構成についてはほぼ全て省略しているため、Mackerelを支える時系列データベース技術 - ゆううきブログ の記事を参照してほしい。

Mackerelはまだ新しいサービスで歴史的事情が少ないシステムだ。 しかし、サーバ管理ツールということもあり、アプリケーションロジックが複雑なため、それにあわせてそれなりに複雑なシステムになっている。

とはいえ、基本はproxy-app-dbの3層構成になっていることに注目してほしい。 複雑だからといって、4層になっていたり、5層になっていたり縦に層が増えることはあまりない。 アプリケーションサーバがあくまで中心で、3種類のデータベースを使い分けているだけとも言える。

参考

まとめ

はてなの大規模なインフラにおけるアプリケーションの様子とサーバ構成の基本を紹介しました。

はてなのインフラで学んだこと、学びたいことはまだまだたくさんあります。 サーバパフォーマンス、仮想化、モニタリング、ネットワーク、オンプレとクラウド、サーバプロビジョニング...etc。 振り返ってみると、4年前はわけのわからなかったWebシステムのインフラをちゃんと理解できているかは別として今では人に説明したりするようになったんだと実感します。

@hfm ことおっくんがよく言っている(厳密には「ウェブオペレーション」という本に書かれている)のが、ウェブオペレーションは科学ではなく技芸であるということです。 技芸、つまり技の世界なので、トップダウンに結論が導かれるというよりは、現場の経験からボトムアップに解を求めがちです。 したがって、大規模サービスのインフラの様子というのは中の人にしかわからないことが多いでしょう。 インターン講義がちょうどよい機会だったので、実際に自分がインフラわからんと思っていたときの気持ちを思い出して、たたき台としてなるべくアプリケーションエンジニア視点から下に降りていくという気分で書いてみました。

本題とは関係ないですが、そういえば、気づけば技術ブログエントリを書いてはてなブックマークで200users以上を獲得することを月課にしていました。 この活動を今回で最後にしようと思います。 多分、記録上は9ヶ月くらい続いたような気がします。意識したのは半年くらいです。 数字上の目標は200users以上であったものの、他にも複数の暗黙の制約を課していたため、それらの制約条件を同時に満たす内容を書くのは想像以上にたいへんでした。 ネタは一応まだあるものの、このペースだとそのうち内容が浅くなっていくことが見えてきました。 そこで、一旦ブログでのアウトプットにかける時間を減らして、別のことに集中しようと思います。

集大成として、今回の活動について来月のYAPC::Asiaで話します。 トーク日程は前夜祭ということで、どのチケットでも参加していただけるようです。

[24時間365日] サーバ/インフラを支える技術 ?スケーラビリティ、ハイパフォーマンス、省力運用 (WEB+DB PRESS plusシリーズ)

[24時間365日] サーバ/インフラを支える技術 ?スケーラビリティ、ハイパフォーマンス、省力運用 (WEB+DB PRESS plusシリーズ)

  • 作者: 安井真伸,横川和哉,ひろせまさあき,伊藤直也,田中慎司,勝見祐己
  • 出版社/メーカー: 技術評論社
  • 発売日: 2008/08/07
  • メディア: 単行本(ソフトカバー)
  • 購入: 133人 クリック: 2,270回
  • この商品を含むブログ (288件) を見る

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)