次世代Webカンファレンス「サーバーサイドパフォーマンス」レポート #nextwebconf

nextwebcon

こんにちは、虎塚です。

10月18日(日)、次世代 Web カンファレンスへ行ってきました。イベントの趣旨は「「次世代 Web カンファレンス」を開催します - Block Rockin’ Codes」で公開されています。

最初のセッション「server_perf (サーバーサイドパフォーマンス)」に参加してメモを取ったので、共有します。

server_perf

登壇者の紹介

mirakuiさん:サーバサイドパフォーマンスというセッションは、次世代Webの文脈では話題選びがむずかしい。サーバサイドアーキテクチャもモニタリングも別にセッションがあるので、Webのパフォーマンスの話に絞る必要があった。そんな話ができる方ということで、xcirさんとcubicdaiyaさんをお呼びした。

xcirさんはVarnishに日本で一番くわしい。xcirさんの記事がないとだれもVarnishをさわらない。

cubicdaiyaさんはnginxやgoでコードをゴリゴリ書いている。また、ISUCON予選2位通過。ISUCONでの担当は、インフラチューニング。サーバーまわりのミドルウェア設定、MySQLの基本的なチューニング、ベンチマーク実行時にパケットキャプチャリングしながらリクエストヘッダを調査、nginxのログを確認して統計取得などなど。これらの作業は仕事でもよくやっており、nginxだとrequest_timeをログにはさむとリクエストの所要時間がとれる。レスポンスタイムを記録してfluentdで収集し、可視化などしている。

サーバへのリクエストにおけるスマフォ率の変化

サービスの特性によって、パフォーマンスの勘所や意味は異なる。三者の共通点はWebやスマフォアプリを使っていること。

mirakuiさん:クックパッドでは、サーバへのリクエストは、スマフォのWebビューとスマフォアプリからのAPIリクエストが支配的。Web(ブラウザ)からのリクエストの比率は下がってきている。

xcirさん:Webリクエストの比率は減って、スマフォアプリに移行している。これまでは大量サーバをとにかく並べていたが、スマフォ対応だとそもそもそんなにリクエストが来ないこともあり、パフォーマンスの勘所が変化している。APIリクエストが増えて、サーバ負荷は減りつつある。サーバコストを減らす必要がある。

cubicdaiyaさん:Webからのリクエストはもはや1割もない。APIエンドポイントへのアクセスがほとんどで、ピーク時には15000req/secくらいになる。JSONを返すサーバがほとんどなので、昔よりやることは減っているが、求められる単体処理性能が上がっている。

mirakuiさん:従来とくらべて、リクエストのサイズが小さい、レスポンスタイムは速くなければならない、という違いがある。サーバサイドのチューニングに関して注視している点やどういう改善をしているかを教えてください。

xcirさん:特殊なことはしていない。Web、APIへのリクエストすべてに流れがある。クライアントからプロキシを経由して、アプリケーションサーバが動いて、データストレージを叩いて、というような。その中でどこにボトルネックがあって、どう改善するか、というのが皆やっている点だと思う。流れを正しく理解して、どこが詰まっているかを確認して対処する。それを解決したら次はどこが詰まっているか 、と考えてチューニングするのが大事。

cubicdaiyaさん:アプリケーションサーバのCPU使用率が上がっていたら、1〜2割下げよう、というふうに目標を決めて取り組む。たとえば、いま管理している終端のnginxサーバは1台で3万接続くらい受けるが、難なく返せるように対処する。バックエンドで瞬時に返せるようにgoでサーバを書いたり、アプリケーションサーバに負荷がかからないようにプロキシサーバで非同期に返したり工夫する。ECサイトなので、商品登録時にキャッシュを作成したり、検索インデックスを作成したりは、メッセージキューに入れてジョブワーカが非同期処理するようにしている。

mirakuiさん:ECサイトはマスタデータが多いので、キャッシュがしづらく、高速化がむずかしそう。どうしても遅くなる部分はありますか。

cubicdaiyaさん:トランザクションがある部分や、MySQLに書き込む購買処理で、特に複数テーブルに書き込むケース。対処法としては、ioDriveを使うなどお金をたくさん使うこと。商品の新着リストをとってくるような処理は、memcachedでキャッシュして返せるが。

xcirさん:キャッシュできない類の(ゲームであれば)ギルドバトルの処理などはむずかしい。取得するデータが"仲間の数"だったりして多い。遅い処理を発見しても、簡単には高速化できない。高速化するには仕様を変えるしかないときもある。

キャッシュ戦略

mirakuiさん:パフォーマンスの改善といえばキャッシュだが、たくさん入れると、パフォーマンス改善と運用しやすさのトレードオフになる。デプロイ時にキャッシュが効いていて更新されなかったり、あるユーザ向けのキャッシュを別のユーザに提示してしまって事故がおきたりする。キャッシュ戦略についてどう考えていますか。

xcirさん:自分のポリシーとしては、そのデータがどれだけ再利用されるか、生成コストがかかるか、を検討する。あるユーザに1回しか使わないデータであればキャッシュすべきでない。生成コストが低ければキャッシュしなくてもよい。キャッシュは自分も好きだが、とりあえず入れれば速くなるものではない。直近で、キャッシュ対象のアイテムサイズが大きく、シリアライズ/デシリアライズ処理が重くなってパフォーマンスが下がった例を見た。どのレイヤーのキャッシュを使うかも重要だ。

cubicdaiyaさん:大きなサービスの場合、すべてのデータをキャッシュするとメモリがあふれてしまう。ランキング生成処理の場合、日次バッチでランキングを生成したり、(単純なテーブルJOINでなく)いくつかのテーブルのデータを組み合わせてキャッシュしたりして、DBへの負荷を減らしていた。また、アクセスの多い部分では、多少メモリが多くなっても、ユーザ単位でキャッシュする。事故は、セッションまわりの処理をうまくやって回避する。とはいえ、会社によってポリシーが違うので、お金をかけられないところだと、全部キャッシュしたり、開発効率を上げるためにキャッシュせずにレスポンス速くすることを目指したりする。

mirakuiさん:クックパッドでは、事故を避けるためと開発効率のため、ログイン中はWebページのキャッシュをしないことにしている。それに、ログイン中はユーザ属性によってABテストを走らせているので、キャッシュをするとやりづらい。事故の原因としてキャッシュが疑われかねないので、避けている。基本的にはキャッシュがなくても速く返せるようにしていこうという流れにある。

パフォーマンスと運用のしやすさから見た言語選択

mirakuiさん:Rubyは書きやすい/読みやすいのバランスがよいと考えている。Ruby on Railsを使っているが、処理系の特性上、高速化しづらい。書きやすさとパフォーマンスはトレードオフと考えている。ISUCONのためにコードを書いてみても、Rubyはだいぶ遅かった。お二人の言語選択の判断基準はどうですか。

xcirさん:皆さんが大好きなPHPです。言語はサービスに合ったものならなんでもいいと思う。言語を選ぶ時に、流行ではなく言語特性やメンバーのスキルで選ぶようにして、理由を説明できるようにしておいたほうがいい。

cubicdaiyaさん:仕事ではパフォーマンス改善がメインだが、ふだんはgoやCでコードを書いている。最近ではCPU1個あたりの性能は頭打ちになり、コアをたくさん積んで性能を出すようになっている。そのため、複数コアで性能を出しやすい言語を選んでいる。 LLだと並行処理をマルチプロセスで処理しようとして、オーバーヘッドが大きくなる場合がある。

mirakuiさん:Rubyはマルチコアをうまく使える言語ではないので、goを使いはじめたりもしている。……いまAPI用のサーバでRailsを使うのはFATすぎるのではという意見が(クックパッド社員から)きたが、たしかに無駄が多い。ユーザ数が増えなくてもリクエスト数が多くなってくると、1リクエストごとのオーバーヘッドが大きいので、高リクエスト時代には向いてないかもしれない。

cubicdaiyaさん:PHPを使ってると、要件としてはルーティングできれば十分なので、できるだけ軽量なフレームワークを使っている。

xcirさん:しかし、結局パフォーマンスは組み合わせだと思う。プロキシでキャッシュすればよいのではないか。Webコンテンツは基本的に、TTLが違うだけで静的なコンテンツの組み合わせで動的なコンテンツができていると思っているので、うまくキャッシュすれば下側(のサーバ)にいく負荷は減らせる。

Webサーバのミドルウェア

mirakuiさん:Webサーバのミドルウェアの話をしたい。お二人をお呼びしたのもこのへんが理由です。

xcirさん:本業以外も含め、ESIを触っている。さくらのVPS上で性能がすごくでる。モジュールは、Varnishの最新バージョン4.1が出てからいじれるところが増えた。たとえば、response bodyを加工できるようになった。smalllightのようなものがVarnishでも作れるようになったし、実際に作ってみたら動いた。

xcirさん(補足):ヒットレートは85〜95%くらい。キャッシュに不整合がでるのを避けるため、ESIも多段にしている。特定の時間にキャッシュから消えるべきものは、多段にして、リクエスト生成元を1つに絞らなければならない。

cubicdaiyaさん:HTTPサーバ、LBが30台くらいあるが、nginxをその前段においてロードバランスしたり、検索サーバ10数台の前に置いたり、検索結果をキャッシュしたりしている。さらに、スマフォのpush通知のリクエストをhttpで飛ばして複数サーバにロードバランシングしたりもする。デプロイ時にロードバランサからいくつかのサーバを外す処理を書いたり、設定ファイルをLuaなどスクリプト言語で拡張したり。秒間数万リクエスト捌けるようにしたり。smallightのようなnginxで画像変換するモジュールつかったり。

mirakuiさん:ESIでバラバラにすると開発時に大変ではないでしょうか。

xcirさん:開発環境にもVarnish立ててます。

mirakuiさん:niginx用のsmalllightは使っていませんか?

cubicdaiyaさん:自分自身は使ってないが、使っている人もいます。nginxのsmalllightは、mod_smalllightの移植で、Apache版のほうがメモリ効率がよい。多少メモリリークしてもなんとかなる。nginx版はどんどんまずくなる。

mirakuiさん:tofu (mod_tofu) でもApacheを対象としていて、MaxRequestsPerChild設定に完全に頼っている。メモリリークしまくる。(参考: tofu - COOKPAD's image system)ImageMagicがそもそもスレッドセーフでない部分があり、1プロセスで複数走るものはあまり向いていない。preforkで対応している。

一同:nginxでは、マルチプロセスだが、各プロセス内でシングルスレッドで動く。nginxでもワーカーたくさん立てなければいけなくなる。

mirakuiさん:nginxをモジュールで書く場合と、LL(mrubyやLuaなど)で書く場合の使い分けは?

cubicdaiyaさん:90%くらいの用途では、mrubyかLuaのどちらかで書けばよいと思う。例外として、smalllightは、nginxモジュールの書き方を知らない状態だったので、Cで書いた。また、ngx_dynamic_upstreamは、Luaやmrubyでnginxのデータ構造に触れられなかったので、Cで書いた。

mirakuiさん:nginxScriptは最近どうですか?

cubicdaiyaさん:まだあまり実用的ではない。変数のsetと、content phaseの処理を1個書けて、response headerの書き換えくらいしかできない。CloudFlareでは動いているようだが。masterにない機能がbranchにあって、SSLまわりをLuaで触れたりするらしい。nginxではupstreamをいじるAPIが、今年夏に追加された様子。

mirakuiさん:Luaとmrubyの違いは?

cubicdaiyaさん:nginxのノンブロッキングAPIをいじれるかどうかが違う。mrubyはまだだが、Luaでは可能。外部のhttpサーバやRedis、memcachedに、ブロッキング的な書き方でノンブロッキングな挙動を実現できる。サブリクエストも送信できる。nginxはシングルスレッドでノンブロッキングで動く。すべてノンブロッキングで動くようにして、少ないスレッドで大量の処理をさばけるようにすることで、nginxの強みが活かされる。一方で、単純なリクエストだとnginx mrubyのほうが速い。起動時にコードをすべてキャッシュし、キャッシュ済みのコードを実行する。nginx Luaはコルーチン単位で実行する。そうしばければ、上記のようなこと(記述はブロッキングで挙動はノンブロッキング)ができない。一旦コルーチン抜けて別のコルーチンで処理、ということができる。

xcirさん:Varnishの最近の動きは、2週間前に4.1が出たこと。何がよかったかというと、インタフェースの提供が増えた。smalllightのようなものも作れる。フェッチしてストレージに入れる処理の中間や、クライアントへのデータ提供前にフィルタを入れられる。将来の話としては、HTTP2対応が、開発者IRCでスケジュールを含めて出てきている。

mirakuiさん:VarnishのHTTP2への姿勢は?

xcirさん:VarnishはHTTP2がきらいなのではなく、SSL/TLSがきらい。Heartbleed脆弱性が出た時の開発者の喜びようといったら。Varnishは、コアがアクセラレーション処理に集中して、SSL/TLSは他にまかせるという方針。以前からHTTP2対応の議論はあり、バージョン4に上がるときに、将来HTTP2対応を見越した変更がたくさん入った。来年のQ1、Q2にはHTTP2対応したいという情報を見かけた。また、Varnish自体の安定性としては、2系や3の早いバージョンでは落ちることがあったが、ここしばらく落としたことがない。

海外のネットワーク

国内サービスだとネットが安定しているが、海外だと環境が整備されていなかったり災害で落ちたりする。国際展開するサービスでは海外からの接続を受ける必要がある。工夫している点は?

cubicdaiyaさん:商習慣が違うので、日本向けとアメリカ向けでアプリを分けている。アメリカは国土が大きいのでレイテンシが大きい。httpsの終端をnginxにしているが、TLS session ticketsなどの技術を使って、ハンドシェイクの手間をへらしたり、証明書の期限切れを早く検知したりしている。

xcirさん:以前は海外にデータセンターを展開していた。最近はAWSで便利になった。現地リージョンのインスタンスで提供する。海外向けならCDNが重要になる。

mirakuiさん:クックパッドではアプリを海外展開している。既存のサービスをM&Aしたりいちからはじめたりして、1つの大きな海外向けサービスを展開している。サーバはAWSのus-eastに置いている。us-eastに置いておけば世界中どこからでも同じくらい遅い(そして日本に置くよりはマシ)という判断をした。とはいえユーザはスペインやインドネシアなどに散っているので、一箇所のままでは無理かもしれない。Route 53でレイテンシベースドルーティングに対応している。名前解決にかかる時間が一番小さいIPアドレスを返してくれるので、最近はそれを利用している。各リージョンにフロントサーバを置いていこうとしている。

xcirさん:海外はネットワークトラブルが多い。ベトナムはカジュアルに海底ケーブルが切れると聞いた。

cubicdaiyaさん:アプリのサイズを非常に小さくしないとダウンロードされない。6MB超えるとダウンロードされないとか。

mirakuiさん:インドネシアでもっとも使われているモバイルブラウザはOpera Mini

cubicdaiyaさん:Opera Turboだと画像の最適化を勝手にしてくれる。

mirakuiさん:Opera Turboはすごい技術の粋で、JSをサーバサイドでレンダリングして返してくれたりする。でもOpera mini対応はガラケー対応と同程度に大変で、東南アジアのエンジニアが疲弊しているらしい。

今までの流れとこれからの未来

Webトラフィックではスマフォアプリのリクエストが占める割合が増え、同時接続数やリクエスト数が変わってきている。フロントサーバに何を使うか、アプリケーションをどういうAPIで書くか、言語選択をどうするか、などを見てきた。

mirakuiさん:CDNを使わざるをえなくなると思う。ユーザの利用地域に近いサーバに接続したほうが速いので、高速化のためにはCDNが必須になる。

xcirさん:CDNはよく使っている。日本はオフローディングがほとんど。海外だとユーザの近くにAkamai、AWS、Fastlyなどを使う必要がある。

cubicdaiyaさん:CDNはすでに使っているし、自社でCDN構築する会社にいたこともある。昔よりCDNに求められることが増えている。スタティックコンテンツの配信しかしていないが、WAFがのったり、画像変換機能がついたり。APIへのリクエストもCDNを通して、レイテンシを抑える方向にいく会社も出てくるかも。

mirakuiさん:昔はみんなI/Oを気にしていたが、I/Oやディスクは安くなった。CPUはよくなってもネットワークの速さはあまり変わらない。フロントにボトルネックが移ってきているのがサーバーサイドパフォーマンスの現状だと思う。また、リクエストをどこで捌くかが、かなり上のほうになっている。Varnishやnginxの使い方に、次世代CDNの流れがある。CDNの中で画像変換したり、fastlyみたいにCDN自体がVarnishでアプリケーションぽくなったりするのではないか。

おわりに

mirakuiさんの振りとまとめが素晴らしく、登壇者のお二方から面白いお話をいろいろとお聞きすることができました。個々のプロダクトの具体的な最近の話もあって参考になりましたが、それに留まらず、大きな流れに話題が誘導されていました。CDNの進化の可能性については、非常に興味深いと思いました。

それでは、また。