MoreBeerMorePower

Power Platform中心だけど、ノーコード/ローコード系を書いてます。

Apply to each のパフォーマンス改善 (配列加工の場合)

はじめに

Power Automate でフローを作成していると、意図せず/特に問題視せず Apply to each を利用したループ処理を利用してしまいます。

多くの場合、問題になるのはその実行速度 (遅さ) です。特に設定を変更せず、Apply to each を使い、さらに配列変数への追加アクションをその中で実行すると処理件数や中でのアクション数に応じて数分~数十分かかるケースがでてきます。

今回は特に配列操作にApply to each を利用しているケースで、ループ処理の効率化を考えます。

そもそもどのくらい遅いのか

簡単のために、まずは他のデータソースへのアクセスを行わない場合を考えます。

入力として [1,2,3...,200] の1~200までの数字を配列として用意し、出力はその前に文字をくっつけただけの配列とします。 f:id:mofumofu_dance:20200811233928p:plain

フローは非常に簡単で、以下のようなApply to each処理になります。

f:id:mofumofu_dance:20200811234545p:plain
Apply to eachを利用する場合

実行結果は以下の通りです。実行時間は変動しますが、おおよそ40秒~50秒の範囲でした。これだけ単純な処理でも1分弱かかりますので、さすがにリアルタイム処理には使えなさそうです。 件数分の処理が直列に実行されるので、基本的には処理件数に比例した実行時間になります。

f:id:mofumofu_dance:20200811235058p:plain
Apply to each 実行結果 (設定変更なし & 配列変数利用)

改善1. Apply to each を利用しない

上のケースでは、[1,2,3,...,200]という配列をもとに、単純な変換を行って新しい配列を作っているだけです。遅いのは元の配列にあるデータを1件1件処理しているからです。

元の配列から、なんらかの変換で得られるような出力を得たい場合には Apply to each を利用しない 選択肢も検討してみてください。それが Filter または Select アクションの利用 です。

Select を利用した配列の変換

最初の例は、単純に配列の各値の前に"ITEM"をつけて、IDというプロパティ(列) に格納しているだけです。この場合にはSelect アクションで 0秒で処理可能です。

フローは下図右側のようになります。Select アクションには処理対象の入力配列と、出力配列のキー:値のペア (列名 : 値) を指定します。

f:id:mofumofu_dance:20200812002828p:plain
Selectアクションを利用した書き換え

実行結果は同じ結果を返していますが、Select アクションの場合には 0s で処理が完了しているのがわかります。

f:id:mofumofu_dance:20200812003357p:plain
Select アクションを利用した場合の実行結果

0秒処理であれば、リアルタイムでも利用できますし、他のパートに時間がかかったとしてもフロー全体として妥当な処理時間で完了させることができます。

ここでは非常に単純な変換のみ考えましたが、少し複雑であっても Selectアクションを利用できるケースは多くあります。具体例は以下の投稿を参照してください。

リスト形式のデータを Adaptive Cards で表示する方法 - MoreBeerMorePower

Filter を利用した配列の抽出

例えば元の配列に何らか条件を加えて、新しい配列を作りたいとき、ついついApply to each の中に条件分岐を挿入してしまいがちです。

f:id:mofumofu_dance:20200812000507p:plain
Apply to each + 条件分岐の例

実行結果は先ほどと同程度です。期待結果通り{"ID" : "ITEM奇数"} な配列ができていますが、処理は40~50秒程度です。

f:id:mofumofu_dance:20200812001454p:plain
Apply to each + 条件分岐の実行結果

このように条件分岐を加えて配列を作成したい場合には Filter アクションが有効 です。

Filter アクションはその名前のとおり、元の配列に条件を課して、部分的な配列を抽出します。上記の例を実現するのであれば、元の配列 [1,2,3,...,200] をFilterアクションで [1,3,5,...,199]の奇数のみの配列に処理してから、Selectアクションで変換できます。

f:id:mofumofu_dance:20200812004543p:plain
Filter + Selectアクションでの書き換え

実行した結果は以下の通りです。 Selectの場合と同様、Apply to each+条件分岐と同じ結果 (配列) がえられており、かつ実行時間は 0秒です。

Apply to each が必須かを考えよう

ここで見たように、入力の配列を加工して新しい配列を作成する場合には、まずApply to eachが本当に必要かを見直してみると劇的な速度改善につながります。 判断するポイントの1つは Apply to eachの中で何らかのコネクターで情報を取得しているか です。

改善2. Apply to each で コンカレンシー制御を有効化

詳細は以前書いた投稿に記載していますが、Apply to eachの設定には並列実行の有効化およびその多重度の指定があります。

Power Automateの裏側をみてみよう! (2/2) - MoreBeerMorePower

これを有効にすることで最初の処理であっても実行速度自体は 劇的に改善します。

f:id:mofumofu_dance:20200812012720p:plain

多重度を1と50で比較すると、Apply to each部分の実行時間が1/3程度に短縮されているのが上の図からわかります。

ただし、結果の配列を見てみると、多重度を上げた場合には元の配列の順序性が失われています

このことから、処理後の配列で元の配列の順序性を意識しなくてよい場合には、配列変数+コンカレンシー制御はパフォーマンス改善に非常に有効であると言えます。

改善3. 配列変数を使わない

Apply to each はどうしても使わなければいけないようなケース、例えば処理の中でデータソースへのアクセスがある場合には、配列変数を使わない という改善方法もあります。

これが Pieter's method (あるいは Advanced Pieter's method) と呼ばれる手法です。

The advanced Pieter's method in Power Automate

やることはそれほど難しくありません。Apply to each の中で配列変数を利用せず、Apply to eachの直後にループ内のアクションの結果を Compose アクション に入れるだけです。

簡単のために、ID1~200までアイテムが登録されているSharePointリストから、アイテムを取得し、タイトルからなる配列を作成してみます。

f:id:mofumofu_dance:20200812020859p:plain
配列変数を利用するフローとPieter's methodの比較

この場合の実行結果は下図の通りです。

図の左右いずれもコンカレンシー制御を有効にし、多重度を50にしています。Pieter's method (図右) では、配列変数を利用した場合 (図左) に比べて半分程度の時間で処理が完了しています。また、結果の配列についても順序性が保たれていることがわかります。

f:id:mofumofu_dance:20200812020251p:plain
配列変数を利用した場合と、Pieter;s methodを利用した場合の比較。いずれも多重度50

このように、たとえApply to eachを利用せざるをえないケースでも、コンカレンシー制御とPieter's methodを組み合わせることで、実行時間を飛躍的に改善できることが分かりました。

まとめ

今回はApply to eachを利用する代表例の1つ、配列の加工をテーマにそのパフォーマンス改善方法を紹介しました。

  1. まずはApply to each が必須かを見極めましょう。中で他のサービスにアクセスしないなら不要かも?
  2. Apply to eachの設定を変更して、並列化を試みましょう。その際、出力の順序が問題になるかは要検討
  3. Apply to eachで配列変数を利用しないオプションを考えましょう。 おそらく多くの場合には不要です。

上記3つの方法で簡単に、しかも劇的にパフォーマンス改善が期待されます。ぜひすっきりしたフローを目指してみてください。