速いアプリケーションの作り方

(Last Updated On: 2018年8月13日)

Phalcon Adventカレンダー18日目として書いています。

一台のアプリケーションサーバーで10リクエスト/秒で十分というサービスであれば、どんなプラットフォームを選んでも問題ありません。一台のサーバーが10リクエスト/秒しか処理できなくても、ページがキャッシュできるならリバースプロキシで簡単に数千リクエスト/秒以上でサービスできます。このようなサービスであればPhalconのようなフレーワムワークを使わなくても大丈夫です。

しかし、メッセージング系などリアルタイム性の高いサービス、つまりHTTPキャッシュがあまり有効に利用できないシステムでは速度が非常に重要です。

ある程度の規模(Webサーバーが複数)のサービスの場合、速いシステムはコスト削減に役立ちます。リアルタイム性がそれほど重要でないサービスであっても、速くレスポンスを返すことは良いユーザーエクスペリエンスとなります。

速いシステムはメモリなどのリソースもあまり使わないことが多いです。このため、同じメモリでもスケールし易くなります。Phalconもメモリ利用量を抑えることができます。

 

速さの限界を知る

どんなに開発者が頑張ってもプラットフォームの限界を超える速さのサービスを作ることは不可能です。速いサービスが必要とわかっているのであれば、プラットフォームとして速い物を選ぶべきです。

MVCフレーワムワークはルーティング、コントローラ、モデル、ビューの処理を行いページを生成します。アプリケーションはフレームワークの処理速度を超えることはできないです。サーバー1台あたり1000リクエスト/秒の性能が目標のアプリケーションで、性能が1000リクエスト/秒を超えないフレームワークを選択することはあり得ません。

速くなればなるほどシビアになる

当たり前のことですが速くなれば成る程ベンチマークの結果は微妙な違いで大きな違いになってしまいます。少々乱暴な書き方ですが

  • 500リクエスト/秒のシステムを倍速(1,000リクエスト/秒)にするには”2ms→1ms”(差分1ms)速くすれば良い
  • 5,000リクエスト/秒のシステムを倍速(10,000リクエスト/秒)にするには”0.2ms→0.1ms”(差分0.1ms)速くすれば良い

さらに高速化して0.05ms実行時間を削減した場合、前者のシステムは

  • ほぼ1,000リクエスト/秒のまま

後者のシステムは

  • 20,000リクエスト/秒

となり倍速になります。高速なシステムほど、微妙な差異に敏感にならなければなりません。

違う「速さ」に価値がある?

プラットフォーム/フレームワークの価値はシステム性能的な「速さ」だけではありません。開発の「速さ」、メンテナンスの「速さ」に価値がある場合もあります。開発の「速さ」は単純には決まりません。プロジェクトに参加する開発者の知識・スキル、プロジェクトの内容、プロジェクトの優先順位(コスト、納期、品質、メンテナンス性)などに左右されます。どういうフレームワークを選ぶのか?は、これらを総合的に判断します。

速いアプリケーションに必要なこと

Webアプリケーションが遅くなる最大の要因は「外部サービス」の利用です。Webアプリケーションでは

  • セッション
  • データベース
  • キャッシュ
  • 接続の最適化

が外部サービスを利用している場合が多いです。速いアプリケーションを作るためのキーワードはI/O削減です。

セッションの高速化

実はPHP 5.6でセッションのLazy Write機能を入れるはずだったのですが、私がマージを任せ確認していなかったので入りませんでした。Lazy Write機能はユーザー定義セーブハンドラでも実装できます。読み込んだセッションデータを書き換えていなければ、保存する意味はありません。不必要なデータ書き換えを省略すればその分高速化します。

書き込みの省略はMemcacheやファイルを利用している場合にメリットが得られます。Memcacheの場合、アクセスでタイムスタンプが更新されます。ファイルの場合も開いたときにmtimeを更新すれば良いです。

データベースであってもPostgreSQLのようにRETURNING句(UPDATE文などで結果を返す機能)をサポートしていれば、セッションデータのI/Oを半減可能です。

データベースの高速化

データベースを高速化することは容易ではありません。しかし、非同期にアクセスすることは可能です。データベース性能自体は変わらなくても非同期にアクセスするとスループットを向上できる場合があります。

例えば、私が作ったPHP用のネイティブPostgreSQLセッションセーブハンドラモジュールは非同期クエリを利用しています。ベンチマークを取ると性能の違いが分かります。

データベースは高速化しずらいので、キャッシュを利用したり、更新と参照を同時にしたり、できるだけアクセスしないようにします。

キャッシュの高速化

スケールアウトを前提としたシステムで、キャッシュと言えばMemcacheやRedisのような外部システム、多くの場合はネットワークを経由するキャッシュが多いですが、ローカルキャッシュの速さには敵いません。

ローカルキャッシュにはAPCなどが利用できます。ローカルキャッシュの場合、全てのWebサーバーが分散してキャッシュするのでそのオーバーヘッドもあります。しかし、データのアクセスパターンによってはローカルキャッシュで大幅な高速化も可能です。アクセスパターンを考慮しローカルキャッシュの利用を検討すると良いです。MemcacheやRedisなどのキャッシュサーバーを各Webサーバー専用としてローカルホストに配置すると良い結果になる場合もあります。

接続の最適化

PHPの場合、永続的データベース接続が利用可能です。しかし、他のアプリケーションとの共存を考えてデフォルトでは有効になっていないことがほとんどです。永続的DB接続は接続文字列毎に作られます。複数アプリがある場合、最大接続数をオーバーして新しくDB接続ができなくなる可能性があるのでデフォルトでは有効化されていません。

永続的データベース接続を利用した場合としない場合の違いも明らかです。専用システムの場合、迷いなく永続的データベース接続をすべきです。もちろん、データベース接続の最大数を超えないように注意してください。

RDBMSやキャッシュシステムなどのデータベースがローカルにある場合、TCP接続よりUNIXソケットの方が高速です。最近のTCPプロトコルスタックはかなり高速ですが、仕組み自体が単純なUNIXソケットの方が良い性能であることがほとんどです。UNIXソケットが利用可能な場合、UNIXソケットを使用します。

 

まとめ

PhalconやPlay2など、ポテンシャルの高いプラットフォームでも高い性能を実現するには様々な工夫が必要です。 工夫がなければ他のフレームワークとさほど変わらない性能になります。アルゴリズム的に遅いコードを書かないことも重要ですが、I/Oが最大の速度低下要因です。I/Oを無くす、I/Oを効率的にする対策が最も重要になります。

  • I/Oに20ms必要なシステム
  • フレームワークの基本性能が10ms vs 0.1ms (100リクエスト/秒  vs 10,000リクエスト/秒)
  • 結果は両方ともあまり変わらない33リクエスト/秒 vs 50リクエスト/秒

Phalconを使えば他のPHPフレームワークより確実により速いアプリケーションになりますが、確実に目に見えて速いアプリケーションになる訳ではありません。

しかし、上の例のようにスループットではあまり変わらなくてもI/O性能に引きずられてシステム性能が低下している場合、速いシステムの方はリソースの余裕があります。基本性能が良ければその分Webサーバー集約でき、Webサーバーの台数を大幅に削減できます。コスト削減が目的であればこの状態でも成功と言えるでしょう。

リアルタイム系のWebシステムではI/Oを無くすことはできません。リアルタイムにデータを管理する部分の性能を向上させる工夫をしましょう。

PostgreSQLのケースで非同期化について触れていますが、I/O削減・効率化をした後はI/Oの非同期化が有効です。非同期I/Oにはメッセージキューなど色々あるので活用しましょう。特に速いログシステムは速いWebアプリには必須です。

投稿者: yohgaki