Documentation

You are viewing the documentation for the 2.2.0 release in the 2.2.x series of releases. The latest stable release series is 2.4.x.

§Play のスレッドプールを理解する

Play framework は、下から上まで、非同期な web フレームワークです。ストリームは Iteratee を使って非同期に扱われます。play-core 内の IO はブロックされないので、伝統的な web フレームワークと比較して、スレッドプールは低目に調整されています。

このため、ブロッキング IO や、潜在的に多くの CPU を集約して実行可能なコードを書きたいと考えた場合に、どのスレッドプールがその処理を実行しているかを知り、それに応じて調整する必要があります。これを考慮に入れずにブロッキング IO を行うと、Play framework のパフォーマンスは貧弱になり得ます。例えば、1 秒あたりほんの少数のリクエストを扱う場合にも、 CPU 使用率が 5% に貼り付くのを目にするかもしれません。それに比べて、 MacBook Pro のような典型的な開発ハードウェア上のベンチマークでは、汗を流して正確に調整しなくても、 Play が毎秒何百、何千リクエストの仕事量を扱うことができることを示しました。

§いつブロッキングされるかを知る

データベースと通信との通信は Play アプリケーションがブロックされる典型的な例です。不幸なことに、メジャーなデータベースでも JVM での非同期なドライバーを提供しているものはありませんし、ほとんどのデータベースではブロック IO を使うことを選ぶことになります。MongoDB 用のドライバーである ReactiveMongo は、Play の Iteratee ライブラリを使う注目すべき例外です。

その他のブロックするかもしれないコードは以下のようになっています。

一般的に、使用している API が future を返す場合はノンブロッキングですが、そうでなければブロッキングです。

このため、Future でブロッキングコードをラップするという誘惑に駆られるかもしれないことに注意してください。こうすることではノンブロッキングになりませんし、ブロッキングが他のスレッド内で起こるかもしれません。使用しているスレッドプールがブロッキングを扱うための十分なスレッドを持っていることを確かめる必要があります。

対照的に、以下のタイプの IO はブロックしません:

§Play のスレッドプール

Play は複数の異なる目的のためのスレッドプールを使っています。

§デフォルトスレッドプールを使用する

Play Framework での全アクションはデフォルトスレッドプールを使用します。例えば、ある非同期動作を行う場合、future での map あるいは flatMap を呼ぶ場合に、与えられた機能内での実行するために暗黙の実行コンテキストを提供する必要があるかもしれません。実行コンテキストは基本的にスレッドプール用の別名です。

Java の promise API を使用する場合、常に Play のデフォルト実行コンテキストが使用されるので、ほとんどの操作において使用される実行コンテキストを選択することはありません。

ほとんどの状況で、使用する適切な実行コンテキストは Play デフォルトスレッドプールになるでしょう。
Scala ソースファイルへ import することによって使用することができます:

Unable to find label global-thread-pool in source file code/ThreadPools.scala

§デフォルトスレッドプールを設定する

デフォルトスレッドプールは application.conf 内の play 名前空間で標準 Akka 設定を使用して設定することができます。デフォルト設定は以下の通りです:

Unable to find label default-config in source file code/ThreadPools.scala

この設定はプール内で 24 スレッドを最大として、有効なプロセッサーごとにスレッドを一つ作成することを Akka に指示します。すべての設定可能なオプションは ここ で見ることができます。

この設定が Play Akka plugin が使用する設定と分離していることに注意してください。Play Akka プラグインは、(play {} で囲まれていない) ルートネームスペース中の akka の設定によって、別々に設定されます。

§他のスレッドプールを使う

ある状況では、他のスレッドプールに仕事を割り当てたくなることがあります。このような状況には、データベースアクセスのような CPU 負荷の高い作業や IO が含まれるかもしれません。この処理を行うために、最初にスレッドプールを作成するべきです。これは、 Scala 内で簡単に行うことができます:

Unable to find label my-context-usage in source file code/ThreadPools.scala

この場合、実行コンテキストを作成するために Akka を使います。しかし Java executor を使って自分の実行コンテキストを簡単に作りたくなることがあるかもしれません。 application.conf で以下のように設定することで追加することができます。

Unable to find label my-context-config in source file code/ThreadPools.scala

Scala 内でのこの実行コンテキストを使用することで、 scala Future コンパニオンオブジェクトの関数で簡単に使うことができます。

Unable to find label my-context-explicit in source file code/ThreadPools.scala

または、暗黙的に使うこともできます。

Unable to find label my-context-implicit in source file code/ThreadPools.scala

§ベストプラクティス

アプリケーションにおける作業を異なるスレッドプール間でどのように割り振るべきかは、アプリケーションが実行している作業の種類、およびどれだけの作業を平行して行えるよう制御したいのかという要望に大きく依存します。全てのソリューションに合うただひとつの設定値はありませんので、アプリケーションのブロッキング IO 要件と、それらのスレッドプール上における意味を理解することで、最良の決定を行うことができます。設定値の調整および検証にはアプリケーションの負荷テストが役立つでしょう。

JDBC がスレッドをブロックするという事実を踏まえ、スレッドプールがデータベースアクセスのためだけに使われると仮定すると、スレッドプールの大きさは利用できる db プールへのコネクションのサイズに設定することができます。これより少ない量のスレッドでは、利用可能なコネクションの数を使い切ることはないでしょう。利用可能なコネクションの数よりも多いスレッドは、コネクションの競合により無駄になる可能性があります。

以下で、 Play Flamework で使用できるいくつかの一般的なプロファイルの概要を説明します。

§ピュアな非同期化

この場合、アプリケーションではブロッキング IO を行いません。決してブロッキングを行わないので、プロセッサーごとにひとつのスレッドを割り当てるデフォルトの設定がこのユースケースにぴったりですし、追加の設定を行う必要はありません。Play のデフォルト実行コンテキストがあらゆる状況で使われます。

§高度な同期化

このプロファイルは Java servlet container のような従来の同期 IO ベースの web フレームワークにマッチします。ブロッキング IO を扱うために大きなスレッドプールを使います。ほとんどのアクションがデータベースアクセスを行う際に、同期 IO を行うようなアプリケーションで有効です。また、他のタイプの作業での平行性に対するコントロールを望まないし必要としない場合にも有効です。このプロファイルはブロッキング IO を扱う場合にもっとも簡単です。

このプロファイルでは、どこでも単純にデフォルト実行コンテキストを使いますが、以下のように、そのプールに非常に多くのスレッドを持つように設定する必要があります。

Unable to find label highly-synchronous in source file code/ThreadPools.scala

このプロファイルは同期 IO を行う Java アプリケーションで推奨されます。Java では他のスレッドに作業を割り当てるのがより難しいためです。

§多くの特定のスレッドプール

このプロファイルはたくさんの同期 IO を、アプリケーションがどのタイプの作業を直ちに実行するか正確にコントロールしながら実行したい場合のためのものです。このプロファイルでは、デフォルト実行コンテキストでノンブロッキングに作業を行い、特別な作業を異なる実行コンテキストでブロッキングオペレーションに割り当てます。

この場合、以下のような異なるタイプのオペレーションに対して複数の異なる実行コンテキストを作る必要があります。

Unable to find label many-specific-contexts in source file code/ThreadPools.scala

これらは以下のように設定します。

Unable to find label many-specific-config in source file code/ThreadPools.scala

この後、コードにて future を作成し、future が実行していた作業と関係のある実行コンテキストを引き渡します。

§わずかな特定のスレッドプール

これは、多くの特定のスレッドプールと高度に同期化されたプロファイルの組み合わせです。デフォルト実行コンテキスト中でほとんどの単純な IO を行い、(100 くらいの) 合理的な複数のスレッドを設定しますが、その後、一度に行われる数を制限することのできる特定のコンテキストに負荷の高いオペレーションを割り振ります。