この記事の内容を分かりやすくするために、まずはStack Overflowの平均日次統計の変化から始めましょう。 以下の数字は2013年11月12日時点の統計からのものです。
- ロードバランサーは148,084,833件のHTTPリクエストを受け入れました
- そのうち36,095,312ページがページロードでした
- 送信には833,992,982,627バイト(776 GB)のHTTPトラフィックが使用されます
- 合計で286,574,644,032バイト(267GB)のデータが受信されました
- 合計で1,125,992,557,312バイト(1,048 GB)のデータが送信されました
- 334,572,103件のSQLクエリ(HTTPリクエストのみを含む)
- 4億12,865,051件のRedisリクエスト
- タグエンジンリクエスト数3,603,418件
- SQLクエリには558,224,585ms(155時間)かかりました
- Redisのリクエストには99,346,916ms(27時間)かかりました
- タグエンジンのリクエストに132,384,059 ms(36時間)を費やしました
- ASP.Net プロセス処理には2,728,177,045 ms(757時間)かかりました
以下のデータは2016年2月9日時点の統計変動を示しており、比較できます。
- ロードバランサーによって受信されたHTTPリクエスト数:209,420,973(+61,336,090)
- 66,294,789(+30,199,477)のうちのページは読み込まれます
- 送信されたHTTPデータ:1,240,266,346,053(+406,273,363,426)バイト(1.24TB)
- 受信した総データ量:569,449,470,023(+282,874,825,991)バイト(569 GB)
- 送信された総データ量:3,084,303,599,266(+1,958,311,041,954)バイト(3.08 TB)
- SQLクエリ(HTTPリクエストのみ):504,816,843(+170,244,740)
- Redis キャッシュヒット数:5,831,683,114(+5,418,818,063)
- エラスティックサーチ:17,158,874件(2013年は追跡されていません)
- タグエンジンリクエスト数:3,661,134件(+57,716件)
- SQLクエリを実行する累積時間:607,073,066(+48,848,481)ms(168時間)
- Redisキャッシュヒットタイム消費時間:10,396,073(-88,950,843) ms(2.8時間)
- タグエンジンリクエストによる消費時間:147,018,571(+14,634,512)ms(40.8時間)
- ASP.Net 処理で消費時間:1,609,944,301(-1,118,232,744)ms(447時間)
- 22.71(-5.29) ms 49,180,275 号の平均レンダリング時間(そのうち 19.12 ms は ASP.Net に消費)
- 11.80(-53.2) ms 6,370,076ページの最初の平均レンダリング時間(そのうち8.81ms ASP.Net に消費)
なぜ ASP.Net 1日あたり6100万件多くリクエストを処理しているのに、処理時間は757時間短縮されているのか疑問に思うかもしれません(2013年と比べて)。 これは主に2015年初頭にサーバーのアップグレードや、多くのアプリ内パフォーマンス最適化作業によるものです。 忘れないでください:パフォーマンスは依然として売りの一つです。 もし具体的なハードウェアの詳細に興味があればご安心ください。これらのサイトを運営するサーバーのハードウェアの詳細は次回の記事で付録としてお伝えします(時期が来たらこのリンクを更新します)。
では、過去2年間で何が変わったのでしょうか? 特に多くはなく、サーバーやネットワーク機器の一部を交換するだけです。 現在、あなたのウェブサイトを運営していたサーバーの概要をご紹介します(2013年以降の変化にご注目ください)
- Microsoft SQL Serverサーバー4台(うち2台は新しいハードウェアを使用しています)
- 11台のIISウェブサーバー(新ハードウェア)
- 2台のRedisサーバー(新ハードウェア)
- タグエンジンサーバー3台(そのうち2台は新しいハードウェアを使用しています)
- 3 Elasticsearchサーバー(上記と同じ)
- 4台のHAProxy負荷分散サーバー(CloudFlare対応のために2台追加)
- ネットワークデバイス2台(Nexus 5596コア+2232TMファブリックエクステンダー、全デバイス10Gbps帯域幅にアップグレード)
- Fortinet 800Cファイアウォール2台(Cisco 5525-X ASAの代替)
- Cisco ASR-1001ルーター2台(Cisco 3945ルーターの代替)
- Cisco ASR-1001-xルーター2台(新製品!) )
Stack Overflowを機能させるには何が必要ですか? 2013年以降大きな変化はありませんが、最適化と前述の新しいハードウェアのおかげで、今ではウェブサーバーは1台だけで十分です。 私たちはすでにこの状況を意図せず何度もテストし、成功を収めています。 ご注意ください:私はただ効果があると言っただけで、良いアイデアだとは言っていません。 しかし、こういう時はいつもとても興味深いです。
サーバースケーリングのアイデアの基準ができたところで、これらのクールなウェブページをどのように作ったか見てみましょう。 完全に独立して存在するシステムはほとんどなく(私たちのシステムも例外ではありません)、これらの部分を統合する全体的な視点がなければ、建築計画の意味は大きく薄れてしまいます。 私たちの目標は全体の状況を把握することです。 今後、各分野を深く掘り下げた記事がたくさん出てくるでしょう。 この記事はキーハードウェアの論理構造の概要に過ぎず、次回の記事ではこれらのハードウェアに関する具体的な詳細を記載します。
このハードウェアが今どんなものか見たい方は、2015年2月にサーバーをアップグレードした際に撮ったキャビネットA(キャビネットBはまったく同じ)の写真をいくつか載せてください。
さて、アーキテクチャのレイアウトに入りましょう。 以下は、主要な既存のシステムの論理アーキテクチャの概要です。
基本原則
ここでは、順に紹介する必要のない一般的な原則をいくつか挙げます。
- すべて冗長なバックアップがあります。
- すべてのサーバーとネットワークデバイスは、少なくとも2つの10Gbps帯域幅接続を備えています。
- すべてのサーバーには2つの電源があり、2台のUPSユニット、その背後に2台の発電機、そして2台のグリッド電圧フィードフォワードを通じて電力を供給しています。
- すべてのサーバーには、Rack AとRack Bに冗長バックアップがあります。
- すべてのサーバーとサービスは、コロラド州の別のデータセンターに二重冗長バックアップがありますが、主にニューヨークをカバーしています。
- すべて冗長なバックアップがあります。
インターネット
まずは私たちのウェブサイトを見つける必要があります。これはDNS関連のものです。 ウェブサイトの発見は速いため、CloudFlareは世界中の隅々にDNSサーバーを持っているため、今はそれを提供しています。 私たちはAPIを通じてDNSレコードを更新しており、APIがDNSの「管理」を担当しています。 しかし、私たちの悪党的な心の中では、根深い信頼の問題のために自分たちのDNSサーバーをまだ持っているのです。 終末がまさに終末的であるとき――GPLやPunyon、キャッシュの問題などが原因かもしれませんが――そして人々が注意をそらすためにプログラミングを続けたいと考えるとき、私たちは自分たちのDNSサーバーに切り替えます。
ブラウザが私たちの隠し場所を見つけると、4つのISP(レベル3、Zayo、Cogent、ニューヨークのLightower)からのHTTPトラフィックが、4つの高度なルーターのいずれかに入ります。 私たちは、ネットワークプロバイダーからのピアツーピアトラフィックを制御し、サービスへのアクセス効率を最も効率的に提供するために、非常に標準的なプロトコルであるボーダーゲートウェイプロトコル(BGP)を使用しています。 ASR-1001とASR-1001-Xルーターは2つのグループに分かれており、それぞれアクティブ/アクティブモードで両方のネットワークプロバイダーからのトラフィックを処理する必要があります。ここでは冗長バックアップがあります。 すべて同じ物理帯域幅10Gbpsを持ちますが、外部からのトラフィックは外部VLANからのトラフィックとは独立しており、負荷分散には別途接続されています。 トラフィックがルーターを通過した後、ロードバランサーにたどり着きます。
そろそろ、2つのデータセンター間で10Gbpsの帯域幅を持つMPLSがあることを言及すべきかもしれませんが、これはウェブサイトサービスに直接関係しているわけではありません。 この技術を活用して、特定の緊急事態に対応するためにオフサイトでのレプリケーションや迅速なデータ復旧を行います。 「でもニック、これには冗長なんてないよ!」 技術的な観点からは(前向きな意味で)あなたの言う通りですが、このレベルでは単一故障点です。 でも待って! ネットワークプロバイダーを通じて、さらに2つのOSPFフェイルオーバールートがあります(MPLSが第一選択で、コスト面で第二・第三の選択肢です)。 前述の各デバイス群は、フェイルオーバー時にネットワークトラフィックの負荷分散を行うため、コロラド州のデータセンターに適切に接続されます。 もちろん、これら2つのデバイスを互いに繋げて4つの経路セットにすることもできたはずですが、それは忘れて、次に進みましょう。
負荷分散(HAProxy)
負荷分散は、CentOS 7(私たちのお気に入りのLinuxバージョン)上で動作するHAProxy 1.5.15で実装されています。 そしてHAProxyにTLS(SSL)のセキュア伝送プロトコルを追加してください。 また、HTTP/2プロトコルのサポートがすぐに提供されるHAProxy 1.7にも注目しています。
デュアル10Gbps LACPネットワーク接続を持つ他のサーバーとは異なり、各ロードバランサーは外部ネットワーク用とDMZ用にそれぞれ10Gbps接続を2つ持っています。 これらのサーバーはSSLプロトコル層をより効率的に処理するために64GB以上のメモリを搭載しています。 メモリ上で多くのTLSセッションをキャッシュして再利用できる場合、同じクライアントに接続する際に消費する計算資源が減ります。 これにより、セッションをより速く、より安価に復元できるのです。 メモリは非常に安価なので、簡単に選べる選択肢です。
負荷分散自体は簡単に設定できます。 私たちは複数の異なるIPで異なるウェブサイトを(主に証明書やDNS管理のため)受信し、その後ホストヘッダーに基づいて異なるバックエンドにトラフィックをルーティングしています。 ここで行うのはレートを制限し、ウェブ層からヘッダー情報をスクレイピングしてHAProxyのシステムログメッセージにログインすることだけです。これにより、各リクエストごとにパフォーマンス指標を記録できます。 この点については後ほど詳しく触れます。
ウェブ層(IIS 8.5、ASP.Net MVC 5.2.3、および.Net 4.6.1)
ロードバランシングは、私たちがプライマリウェブサーバー(01-09)と呼ぶ9台の開発用ウェブサーバー(10-11、テスト環境)にトラフィックを分散させます。 メインサーバーはStack Overflow、Careers、そしてすべてのStack Exchangeサイトを動かし、meta.stackoverflow.com と meta.stackexchange.com は他の2つのサーバーで動作しています。 メインのQ&Aアプリ自体はマルチテナントで、Q&Aサイトからのすべてのリクエストを1つのアプリで処理します。 つまり、Q&Aアプリ全体を1つのアプリケーションプール、1台のサーバーで実行できるということです。 Careers、API v2、Mobile APIなど他のアプリは独立しています。 マスターサーバーと開発者サーバーのIISで見られる内容は以下の通りです:
こちらがOpserver(当社の内部監視ダッシュボード)で見られるStack Overflowのウェブ層の分布です:
そして、これらのウェブサーバーのリソース消費量は以下の通りです:
なぜ私たちがこれほど多くのリソースを過剰に提供しているのかについては、次の記事で詳しく述べます。特にロールビルド、余裕、冗長性に焦点を当てています。
サービス層(IIS、ASP.Net MVC 5.2.3、 NET 4.6.1とHTTPです。 SYS)
ウェブ層の隣にサービス層があります。 また、Windows 8.5R2のIIS 2012上でも動作します。 この層は、ウェブ層やその他の本番環境の内部システムをサポートする内部サービスを実行します。 主なサービスは2つあります。タグエンジンを動かし、http.sys(IISではなく)を基盤とする「Stack Server」です。 Providence API(IISベース)。 興味深い事実として、スタックサーバーが問題リストを2分ごとに更新する際にL2とL3のキャッシュに頻繁にアクセスするため、2つのプロセスを異なるソケットに接続する必要がありました。
これらのサービスを実行するマシンはタグエンジンやバックエンドAPIにとって重要なので冗長でなければなりませんが、9倍冗長であってはいけません。 例えば、データベースからすべての記事とそのタグをn分ごと(現在は2分)読み込みしており、これは決して少ない時間ではありません。 ウェブ層でこの読み込み操作を9回繰り返したくはなく、3回あれば安全です。 また、タグエンジンやエラスティックインデックスジョブ(このレイヤーで動作)の計算およびデータロード特性をより最適化するために、これらのサーバーには異なるハードウェア構成を使用しています。 「タグエンジン」自体は比較的複雑なテーマであり、専用の記事で取り上げます。 基本的な原理は、address/questions/tagged/javaにアクセスしたときにタグ付けエンジンにアクセスし、それに合う質問を取得することです。 エンジンは/search以外のすべてのタグマッチングを処理しているので、新しいナビゲーションを含めてすべての場所がこのサービスを通じてデータを受け取っています。
キャッシュ&公開/購読(Redis)
私たちは一部の場所でRedisを使いましたが、非常に安定しています。 月間に最大1600億回の操作がありますが、インスタンスあたりのCPU比率は2%を超えず、通常はそれより低いです。
私たちはL1/L2レベルのキャッシュシステムにRedisを使用しています。 「L1」レベルは、ウェブサーバーや類似のアプリケーションで動作するHTTPキャッシュのことです。 「L2」レベルは、前のレベルのキャッシュが失敗した後にRedisを通じてデータを取得するためのものです。 私たちのデータは、Marc Gravelによって作成されたprotobuf-dot-netによって実装されたProtobuf形式で保存されています。 Redisクライアントでは、社内で開発されたオープンソースライブラリであるStackExchange.Redisライブラリを使用しました。 もしWebサーバーがL1とL2の両方のキャッシュに当たらなければ、データソース(データベースクエリやAPI呼び出しなど)からデータを取得し、その結果をローカルキャッシュとRedisに保存します。 次のサーバーは同じデータを取得する際にL1キャッシュに欠けている場合もありますが、L2/Redisでデータを取得するため、データベースクエリやAPI呼び出しの必要がなくなります。
また、多くのQ&Aサイトも運営しており、それぞれL1/L2キャッシュにプレフィックスとしてキーを、L2/RedisキャッシュにデータベースIDを割り当てています。 このテーマについては今後の記事で詳しく掘り下げていきます。
すべてのサイトインスタンスを動かす2つのメインRedisサーバー(1つはマスターと1つはスレーブ)に加え、主にメモリの理由から、2つの専用スレーブサーバーを使って機械学習用のインスタンスも構築しました。 このサーバー群は、ホームページでの質問の推奨やより良い求人マッチングの提供など、さまざまなサービスを提供するために使用されます。 このプラットフォームは「プロビデンス」と呼ばれ、ケビン・モントローズがそれについて書いています。
メインのRedisサーバーは256GBのRAM(約90GB)、Providenceサーバーは384GB(約125GB使用)のメモリを持っています。
Redisは単なるキャッシュ用ではなく、1台のサーバーがメッセージを公開し、他のサブスクライバー(サーバー上の下流クライアントからのRedisも含む)がメッセージを受け取る、公開・購読メカニズムも備えています。 この仕組みを使って他のサービスのL1キャッシュをクリアし、ウェブサーバー上のキャッシュの整合性を維持しています。 しかし、もう一つ重要な用途があります。それはウェブソケットです。
Websockets(NetGain)
websocketsを使って、トップバーの通知、投票、新しいナビゲーション、新しい回答、コメントなど、リアルタイムのアップデートをユーザーに配信しています。
ソケットサーバー自体はウェブ層上で動作し、ネイティブソケットを使用しています。 これはオープンソースライブラリ実装であるStackExchange.NetGainに基づく非常に小さなアプリケーションです。 ピーク時には約50万のウェブソケット接続が同時に存在し、これは多くのブラウザを同時に使っていました。 豆知識ですが、これらのブラウザの中には18ヶ月以上営業しているものもあり、開発者がまだ生きているかどうかを確認するために誰かを見つける必要があります。 以下のチャートは今週のウェブソケットの並行性のパターンを示しています:
なぜWebSocketを使うのですか? 私たちの規模では、世論調査よりもはるかに効率的です。 こうすることで、より少ないリソースでより多くのデータをプッシュし、ユーザーにとってよりリアルタイムに対応できます。 しかし、このアプローチには問題もあります。一時的なポートやロードバランサーの枯渇したファイルハンドルなどは非常に興味深い問題であり、後ほど詳しく説明します。
探索(Elasticsearch)
ネタバレすると、ここにはあまりワクワクするようなことはない。 ウェブ層はElasticsearch 1.4を使用し、超軽量で高性能なStackExchange.Elasticクライアントを実装しています。 ほとんどのものとは異なり、この部分をオープンソース化する予定はありません。なぜなら、必要なAPIのごく一部を露出させるからです。 公開することは損失を上回り、開発者を混乱させるだけだと確信しています。 これらの場所ではelastic:/searchを使って関連する質問を計算し、質問時に提案をしています。
各エラスティッククラスター(各データセンターごとに1つ)は3つのノードで構成され、それぞれ独自のインデックスを持っています。 Careersサイトには追加の索引もあります。 私たちの構成の中でやや標準的でない部分として、3台のサーバーのクラスターは通常の構成よりも少し強力です。各サーバーはSSDストレージ、192GBのメモリ、10Gbps帯域幅のデュアルネットワークを使用しています。
同じアプリケーションドメインであるStack Server(はい、ここで.Net Coreに振り回されました)もタグエンジンをホストしており、Elasticsearchを使って連続的なインデックス作成を行っています。 ここでは、SQL Server(データソース)でROWVERSIONを使ってElasticの「最下位」文書と比較するという小さなコツを使います。 明らかに連続しているため、前回の訪問後に内容が変更された場合、クロールやインデックス作成が容易です。
SQLの全文検索のような技術の代わりにElasticsearchを使う主な理由は、そのスケーラビリティとコスト効率にあります。 SQLはCPUで比較的高価ですが、Elasticはずっと安く、最近は多くの新機能があります。 なぜSolrを使わないのですか? ネットワーク全体で(複数のインデックスを同時に)検索する必要がありますが、Solrは私たちの決定時点でこのシナリオをサポートしていません。 2.xをまだ使っていない理由は、2.xでタイプが大きく変わったため、アップグレードしたい場合はすべてを再インデックスしなければならないからです。 要件の変更や移行に備える時間が足りません。
データベース(SQL Server)
私たちはSQL Serverを唯一の真実の情報源として使っています。 ElasticとRedisのすべてのデータはSQL Serverから来ています。 私たちは2つのSQL Serverクラスターを持ち、AlwaysOnの可用性グループで構成されています。 各クラスターにはニューヨークにプライマリサーバーがあり(ほぼ全負荷を負担)、レプリカサーバー、さらにコロラド州のレプリカサーバー(当社の災害復旧データセンター)があります。 すべてのコピー操作は非同期です。
最初のクラスターは、それぞれ384GBのメモリ、4TBのPCIe SSDと2つの12コアCPUを備えたDell R720xdサーバーのセットです。 Stack Overflow、Sites(名前は良くないですが後で説明します)、PRIZM、そしてMobileのデータベースが含まれています。
2つ目のクラスターは、768GBメモリ、PCIe SSDと8コアCPU2基を搭載したDell R730xdサーバーのセットです。 このクラスターには、キャリア、オープンID、チャット、例外ログ、その他のQ&Aサイト(例:スーパーユーザー、サーバーフォールトなど)を含むすべてのデータベースが含まれています。
データベース層ではCPU利用率を非常に低いレベルに抑えたいと考えていますが、実際には計画的なキャッシュ問題が発生するとCPU使用率はやや高くなります(現在トラブルシューティング中です)。 現在、NY-SQL02と04がメインサーバー、01と03がレプリカサーバーです。SSDアップグレードのため、今日それらを再起動しました。 ここ24時間での彼らのパフォーマンスは以下の通りです:
私たちのSQLの使い方は非常にシンプルです。 シンプルは速いことを意味します。 クエリ文の中には歪められるものもありますが、SQLとのやり取り自体はかなりネイティブな方法で行われます。 レガシーなLinq2Sqlもありますが、すべての新しい開発はPOCOを利用したオープンソースのマイクロORMフレームワークDapperを使用しています。 別の言い方をしましょう:Stack Overflowのデータベースにはストアドプロシージャが1つしかなく、この最後のストアドプロシージャを終了し、コードに置き換えます。
図書館
では、考えを変えましょう。もっと直接的に役立つことをご紹介します。 以前にもいくつか挙げましたが、私たちが管理し、皆が使っている多くのオープンソースの.Netライブラリのリストをお伝えします。 彼らにはコアなビジネス価値がないからオープンソース化していますが、世界中の開発者を助けることができます。 ぜひ今から使えることを願っています:
- Dapper(.Net Core)– ADO.Net 向けの高性能マイクロORMフレームワーク
- StackExchange.Redis(StackExchange.Redis) – 高性能なRedisクライアント
- MiniProfiler – すべてのページで使う軽量プロファイラー(Ruby、Go、Nodeもサポート)
- 例外 – SQL、JSON、MySQLなどのエラーログ用
- Jil – 高性能JSONシリアライズおよびデシリアライザー
- Sigil – .Net CIL生成ヘルパー(C#が十分に速くない場合に使用)
- NetGain – 高性能Websocketサーバー
- Opserver – ほとんどのシステムを直接調査し、Orion、Bosun、またはWMIから情報を取得できる監視ダッシュボード
- ボースン – 背景で監視システム、Goで書かれた
|