実行エンジン

Mule Runtime Engine は、非ブロックで非同期の実行用に調整されたリアクティブ実行エンジンを実装します。
リアクティブプログラミングの詳細は、​https://en.wikipedia.org/wiki/Reactive_programming を参照してください。

タスク指向の実行モデルでは、非ブロック I/O を高い同時実行レベルで透過的に活用できます。つまり、ユーザーはフローのスレッドや非同期性を考慮する必要がありません。Mule フロー内の各操作は実行に関するメタデータを提供するタスクで、Mule はそのメタデータに基づいて調整の決定を行います。

Mule イベントプロセッサーは、CPU Intensive (CPU 負荷大)、CPU Light (CPU 負荷小)、I/O Intensive (I/O 負荷大) のいずれの操作を行うかを Mule に示します。これらのワークロード種別によって、Mule はさまざまなワークロードを調整することができ、最適なパフォーマンスを実現するためにユーザーがスレッドプールを手動で管理する必要がなくなります。Mule は、システム内で使用可能なリソース (メモリや CPU コアなど) を調査し、スレッドプールを自動的に調整します。

処理種別

Mule イベントプロセッサーは、実行する処理の種類を Mule に示します。これは、次のいずれかです。

  • CPU Light (CPU 負荷小)
    短時間の操作 (約 10 ミリ秒) または非ブロック I/O 用。たとえば、ロガー (​logger​) や HTTP 要求操作 (​http:request​) など。これらのタスクではブロック I/O アクティビティを実行できません。ログで該当する文字列は ​CPU_LIGHT​ と ​CPU_LIGHT_ASYNC​ です。

  • Blocking I/O (ブロック I/O)
    コール元スレッドをブロックする I/O 用。たとえば、Database Select 操作 (​db:select​) や SFTP 読み取り (​sftp:read​) など。ログで該当する文字列は ​BLOCKING​ と ​IO​ です。

  • CPU Intensive (CPU 負荷大)
    CPU の負荷が大きい計算用。通常は実行時間が 10 ミリ秒を超えます。これらのタスクでは I/O アクティビティを実行できません。たとえば Transform Message コンポーネント (​ee:transform​) など。ログで該当する文字列は ​CPU_INTENSIVE​ です。

特定のコンポーネントやモジュールがサポートする処理種別については、それらのドキュメントを参照してください。指定しない場合、デフォルトの種別は CPU Light (CPU 負荷小) です。

Mule SDK を使用して作成されたコネクタの場合は、コネクタの実装方法に基づいて SDK によって最適な処理種別が決定されます。そのメカニズムについての詳細は、​Mule SDK のドキュメント​を参照してください。

スレッド

Mule 4.3 には、UBER プールと呼ばれる一意のスレッドプールが 1 つ含まれています。このスレッドプールは Mule によって管理され、同じ Mule インスタンス内のすべてのアプリケーションで共有されます。Mule は、起動時にシステム内で使用可能なリソース (メモリや CPU コアなど) を調査し、Mule が実行されている環境に合わせて自動的に調整します。このアルゴリズムはパフォーマンステストによって確立されたもので、ほとんどの状況で最適な値になっています。

この 1 つのスレッドプールを使用することで Mule の効率が向上し、Mule 3 に比べて、同じ量のワークロードを処理するために必要なスレッド数 (およびそれに伴うメモリフットプリント) が大幅に削減されました。

以前の Mule 4.x バージョンからアップグレードする場合は、​「Threading in Mule 4.2 (Mule 4.2 のスレッド)」​を参照して Mule 4.3 のスレッドモデルに適用される違いを理解してください。

プロアクターパターン

プロアクターは非同期実行のための設計パターンです。プロアクター設計パターンのしくみを理解するには、​https://en.wikipedia.org/wiki/Proactor_pattern を参照してください。

この設計パターンに従うと、タスクは Mule 処理種別に対応するカテゴリに分類され、各タスクは UBER プールに送信されて実行されます。

パフォーマンステストにより、一意のスレッドプールが 1 つしかなくてもプロアクターパターンを適用するとパフォーマンスが向上することがわかっています。これは、スレッドをよりすばやくメインループに戻すことができ、I/O タスクがブロックされて待機している間もシステムが新しい処理の受け入れを続行できるためです。

トランザクション

アクティブなトランザクションがある場合は、すべてのスレッド切り替えが停止されます。トランザクションに参加するイベントプロセッサーは同じスレッドで実行されます。

設定

スレッドプールは Mule の起動時に自動的に設定され、CPU やメモリなどの利用可能なリソースを考慮する数式が適用されます。

Mule Runtime Engine をオンプレミスで実行している場合、ローカル Mule インスタンスで ​MULE_HOME/conf/schedulers-pools.conf​ ファイルを編集することで、これらのグローバル数式を変更できます。この設定ファイルには各プロパティをドキュメント化するコメントが含まれていますが、プール戦略に応じて、固有のプロパティを設定できます。

2 つの使用可能な戦略間で切り替えるように ​org.mule.runtime.scheduler.SchedulerPoolStrategy​ パラメーターを設定します。

  • UBER
    統合スケジュール戦略。デフォルト。

  • DEDICATED
    分離プール戦略。従来。

# The strategy to be used for managing the thread pools that back the 3 types of schedulers in the Mule Runtime
# (cpu_light, cpu_intensive and I/O).
# Possible values are:
#    - UBER: All three scheduler types will be backed by one uber uber thread pool (default since 4.3.0)
#    - DEDICATED: Each scheduler type is backed by its own Thread pool (legacy mode to Mule 4.1.x and 4.2.x)
org.mule.runtime.scheduler.SchedulerPoolStrategy=UBER

UBER スケジュール戦略

戦略が ​UBER​ に設定されていると、次の設定が適用されます。

  • org.mule.runtime.scheduler.uber.threadPool.coreSize=cores

  • org.mule.runtime.scheduler.uber.threadPool.maxSize=max(2, cores + ((mem - 245760) / 5120))

  • org.mule.runtime.scheduler.uber.workQueue.size=0

  • org.mule.runtime.scheduler.uber.threadPool.threadKeepAlive=30000

schedulers-pools.conf​ ファイルの例:

# The number of threads to keep in the uber pool.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=UBER
org.mule.runtime.scheduler.uber.threadPool.coreSize=cores

# The maximum number of threads to allow in the uber pool.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=UBER
org.mule.runtime.scheduler.uber.threadPool.maxSize=max(2, cores + ((mem - 245760) / 5120))

# The size of the queue to use for holding tasks in the uber pool before they are executed.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=UBER
org.mule.runtime.scheduler.uber.workQueue.size=0

# When the number of threads in the uber pool is greater than SchedulerService.io.coreThreadPoolSize, this is the maximum
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=UBER
# time (in milliseconds) that excess idle threads will wait for new tasks before terminating.
org.mule.runtime.scheduler.uber.threadPool.threadKeepAlive=30000

DEDICATED スケジュール戦略

戦略が ​DEDICATED​ に設定されていると、デフォルトの UBER 戦略のパラメーターが無視されます。

この設定を有効にするには、​schedulers-pools.conf​ ファイルで次のパラメーターのコメントを解除します。

  • org.mule.runtime.scheduler.cpuLight.threadPool.size=2*cores

  • org.mule.runtime.scheduler.cpuLight.workQueue.size=0

  • org.mule.runtime.scheduler.io.threadPool.coreSize=cores

  • org.mule.runtime.scheduler.io.threadPool.maxSize=max(2, cores + ((mem - 245760) / 5120))

  • org.mule.runtime.scheduler.io.workQueue.size=0

  • org.mule.runtime.scheduler.io.threadPool.threadKeepAlive=30000

  • org.mule.runtime.scheduler.cpuIntensive.threadPool.size=2*cores

  • org.mule.runtime.scheduler.cpuIntensive.workQueue.size=2*cores

schedulers-pools.conf​ ファイルの例:

# The number of threads to keep in the cpu_lite pool, even if they are idle.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.cpuLight.threadPool.size=2*cores

# The size of the queue to use for holding cpu_lite tasks before they are executed.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.cpuLight.workQueue.size=0

# The number of threads to keep in the I/O pool.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.io.threadPool.coreSize=cores

# The maximum number of threads to allow in the I/O pool.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.io.threadPool.maxSize=max(2, cores + ((mem - 245760) / 5120))

# The size of the queue to use for holding I/O tasks before they are executed.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.io.workQueue.size=0

# When the number of threads in the I/O pool is greater than SchedulerService.io.coreThreadPoolSize, this is the maximum
# time (in milliseconds) that excess idle threads will wait for new tasks before terminating.
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.io.threadPool.threadKeepAlive=30000

# The number of threads to keep in the cpu_intensive pool, even if they are idle.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.cpuIntensive.threadPool.size=2*cores

# The size of the queue to use for holding cpu_intensive tasks before they are executed.
# Supports Expressions
# Only applies when org.mule.runtime.scheduler.threadPool.strategy=DEDICATED
org.mule.runtime.scheduler.cpuIntensive.workQueue.size=2*cores

考慮事項

使用するプール戦略を変更したり、その設定値を変更したりすることはお勧めしません。この設定はグローバルであり、Mule Runtime インスタンス全体に影響することに注意してください。

実際のシナリオに含まれるすべてのアプリケーションを使用して負荷テストとストレステストを実行することによって、スレッド設定への変更を検証し、Mule 4 でのスレッドプールの動作について理解することをお勧めします。

アプリケーションレベルでの設定

アプリケーションコードに次のコードを追加することで、アプリケーションで使用するプール戦略を定義できます。

<ee:scheduler-pools poolStrategy="UBER" gracefulShutdownTimeout="15000">
   <ee:uber
       corePoolSize="1"
       maxPoolSize="9"
       queueSize="5"
       keepAlive="5"/>
</ee:scheduler-pools>

poolStrategy​ パラメーターは後方互換性を確保するために存在しており、以前の Mule バージョンの 3 つのプールのスキームに戻ることができます。

<ee:scheduler-pools gracefulShutdownTimeout="15000">
   <ee:cpu-light
           poolSize="2"
           queueSize="1024"/>
   <ee:io
           corePoolSize="1"
           maxPoolSize="2"
           queueSize="0"
           keepAlive="30000"/>
   <ee:cpu-intensive
           poolSize="4"
           queueSize="2048"/>
</ee:scheduler-pools>

考慮事項

アプリケーションレベルでプール設定を適用すると、Mule アプリケーション用の完全に新しいスレッドプールセットが作成されます。この設定によって ​scheduler-conf.properties​ のデフォルト設定は変更されません。オンプレミスデプロイメントでは同じ Mule インスタンスに多くの Mule アプリケーションをデプロイされるため、特にこれが重要です。

デフォルトの設定を使用して Mule を実行することをお勧めします。 Mule 4.1.x または Mule 4.2.x からアップグレードする場合、​scheduler-conf.properties​ に対するすべてのカスタマイズが引き続き使用されるように、DEDICATED 戦略を使用できます。ただし、最適化がまだ必要かどうかを判断するために新しいデフォルト設定をテストすることをお勧めします。また、従来のモードに戻す前に、まず新しい UBER 戦略をカスタマイズしてみてください。

CloudHub にデプロイされた Mule アプリケーションのアプリケーションレベルでプール設定を定義する場合、断片的 vCore はメモリが少ないため、ワーカーサイズに注意してください。詳細は、​「CloudHub ワーカー」​を参照してください。

カスタムスレッドプール

一意の UBER スレッドプールに加えて、一部のコンポーネントによって特定の目的のために次のような追加プールが作成される場合があります。

  • NIO セレクター
    非ブロック I/O を有効にします。各コネクタは、必要に応じて何個でも作成できます。

  • 繰り返しタスクプール
    一部のコネクタやコンポーネント (有効期限監視、キューコンシューマーなど) によって、繰り返しタスクを実行するための特定のプールが作成される場合があります。

バックプレッシャー管理

バックプレッシャーは、負荷が大きい場合に Mule が特定のイベントを処理するために使用可能なリソースがないときに発生する可能性があります。この問題は、スレッドがすべて使用中で新しく受信したイベントのハンドオフを実行できない場合や、現在のフローの ​maxConcurrency​ 値をすでに超過している場合に発生する可能性があります。

Mule がイベントを処理できない場合、その状況に関する次のメッセージが記録されます: Flow 'flowName' is unable to accept new events at this time​ (フロー「flowName」は、現在新しいイベントを受け付けられません)。また、フローのソースには、必要なアクションを実行するように通知されます。

バックプレッシャーのために Mule が実行するアクションは、各コネクタの取得元に固有です。たとえば、​http:listener​ は ​503​ エラーコードを返す可能性があり、メッセージブローカーリスナーはリソースが使用可能になるまで待つかメッセージを削除する可能性があります。 場合によっては、取得元は処理しきれないデータを取得しないようにリモートシステムから切断し、サーバーが正常な状態に戻った後に再接続することがあります。

Mule 4.2 または 4.1 からのアップグレード

Mule 4.2.x または 4.1.x から Mule 4.3.x にアップグレードする場合は、次のアクションを実行します。

  • カスタムスレッド設定が適用されない場合 (​scheduler-pools.conf​ を使用、または Mule アプリケーションで直接)、アクションは必要ありません。

  • カスタムスレッド設定が適用される場合、デフォルト設定を使用して Mule アプリケーションをテストします。
    UBER プール戦略をはじめとする Mule 4.3 で実装されたパフォーマンス更新のために、カスタムスレッド設定が不要な場合もあります。

  • テストでカスタム設定がまだ必要であることが確認された場合は、アプリケーションのリソース管理、いずれかのアプリケーション連動関係、または Mule インスタンスに問題があることが考えられます。 カスタム設定を使用すると、リソースの使用が不十分になる場合があり、基盤となる問題が見えてこないこともあります。

トラブルシューティング

Mule アプリケーションのトラブルシューティングを行う場合は、スレッドの命名規則を考慮してください。

たとえば、次のフローがあるとします。

<flow name="echoFlow">
   <http:listener config-ref="HTTP_Listener_config" path="/echo"/>
   <set-payload value="#['Echo ' ++ now()]" mimeType="text/plain"/>
   <logger level="ERROR" message="request at #[now()]" />
</flow>

実行後に、フローで次のログが生成されます。

ERROR 2020-03-12 19:13:45,292 [[MuleRuntime].uber.07: [echo].echoFlow.CPU_LITE @63e7e52c]
[event: bd56a240-64ae-11ea-8f7d-f01898419bde] org.mule.runtime.core.internal.processor.LoggerMessageProcessor:
request at 2020-03-12T19:13:45.283-03:00[America/Argentina/Buenos_Aires]

この場合、スレッド名は ​uber.07​ です。Mule では操作の種別もログに記録します。この場合は ​CPU_LITE​ です。

スレッド名はスレッドダンプにも表示され、プロファイラーを使用する場合も表示されます。

プロファイラーのスレッドダンプ