わたしのEffective PowerAutomate パフォーマンス編

随時更新。
PowerAutomateを利用してワークフローを構築／運用する上で、パフォーマンスの観点で考慮すべき事項をリストアップしています。

# 1. Apply to eachを減らす

ループ処理をするとき組み込みの「Apply to each」（それぞれに適用）アクションは重宝しますが、所要時間のボトルネックを生じさせやすいアクションです。
可能な限り、 __同じく組み込みの「アレイのフィルター」や「選択」、関数を利用して置き換えるべき__ です。

置き換えに困難があるときも、「どうしても無理」と断念する前に、フローの構成やデータ構造のレベルで見直してみましょう。

ループそのものをなくすことはできなくても、 __ループ処理対象のデータ件数を削減__ できるかもしれません。データ件数が減らせないとしても、__ループの中で実行するアクションの数を削減__ できるかもしれません。データ件数の削減にせよアクション実行の削減にせよ、ループの所要時間に確実な改善をもたらすはずです。
そのためには、ループの中で行おうとしていた処理の一部を、「アレイのフィルター」や「選択」、関数などを利用してループに入るより前に済ませておくようにします。

:::note info
VBAやJava（とくにStream API登場以前のJava）などの手続き型プログラミング言語に慣れているとどうしてもループ構造に頼りがちということはあるかもしれません。
C#のLINQやJavaScript／TypeScriptの `map`・`filter`といった関数に馴染みがないと、「選択」や「アレイのフィルター」を自然と回避してしまうこともあるかもしれません。
ただこれは慣れの問題です。パフォーマンス改善のために克服しましょう。
また、Apply to eachやDo untilのループの作成やデバッグは意外と手間がかかります。そういう意味でも脱ループには価値があります。
:::

# 2. アクションを関数で置き換える

フローに含まれるアクション（ステップ）の数が増えると所要時間が長くなります。
アクションが増えることで個々のアクションの所要時間が加算されるのはもちろん、アクションの結果を受けてその次のアクションを呼び出すというPowerAutomateのフロー制御のオーバーヘッドもかかるためです。
可能な限り、__アクションの組み合わせを関数の組み合わせで置き換え__ ましょう。
現在時刻を取得したり、オブジェクトのプロパティを抽出したり、オブジェクトの変換をしたり、配列同士を連結（union）したり・・・といった微細なオペレーションは、可能なときはいつでも非アクション化しましょう。

:::note info
些細な処理でも、ステップバイステップ、アクションの組み合わせで作り込んでいくことで、フローの可読性が上がることはあります。
関数呼び出しの入れ子構造 `foo(bar(baz()))`よりも、アクションや関数呼び出しを列挙 `foo(); bar(); baz();...`のほうが読みやすいという方もいらっしゃるでしょう。
ただ、これらは多分に「好みの問題」「決めの問題」となりがちです。
その「好み」「決め」にこだわるうちにチリツモ（塵積）効果でフロー全体が我慢ならない所要時間になってしまう・・・なんてことを回避するほうが優先事項だと私は考えています。
:::

# 3. 変数を減らす

変数そのものよりも、変数の存在に頼って変数で管理されているデータを更新（上書きやアペンド）をするようなフローが問題です。
このようなフローは「Apply to eachを減らす」原則や「アクションの組み合わせを関数の組み合わせで置き換える」原則に抵触しやすく、結果的に所要時間の増大に繋がりやすいためです。
__どうしても変数で管理しないといけないもの以外は変数で管理しない__ ようにしましょう。

:::note info
通常のプログラミング言語によるコーディングと同じく、ミュータブルなコードはフロー全体の保守性を低下させます。性能の問題は脇においたとしても、なるべくイミュータブルなコードを記述するように心がけたほうがよいです。
:::

# 4. Excelの「表内に存在する行を一覧表示」で少数レコードの除外のためにフィルターを指定しない

Excelの「表内に存在する行を一覧表示」アクションでフィルタークエリを指定すると所要時間が大幅に伸びます。
フィルターで除外するレコードが少数の場合、このコストは割に合わないことがあります。
__フィルターをすることで取得対象レコード数が大幅に削減されるようなケース、そして取得対象データが膨大でフィルターをしないと件数制限に抵触するようなケースを除いて、「アレイのフィルター」を使用__ しましょう。

:::note info
なお、「クエリの選択」で結果セットに含まれる列を絞り込むことができますが、「表内に存在する行を一覧表示」アクションの所要時間の大きな部分を占めるのは テーブルの読み取りそのものとフィルタークエリの適用の部分らしく、結果セットに含まれる列を絞り込んでもそれほど効果は見込めないようです。
通信速度やテーブルの構造にもよると思われますが、私が手元で確認した限りでは違いが見られないか、むしろ若干時間が増加しました。
:::

# 5. 1回あたりの処理対象はできるだけ少なくする

PowerAutomateは「M365上で発生するイベントに応じて都度なにかしらのオペレーションを実行」することを得意としています。
イベントとは、ユーザーのOutlookでのメール送受信であったりFormsでのアンケート回答であったりSharePointでのリストへの項目追加、ドキュメントライブラリーのファイル更新といったオペレーションがメインです。
そうしたイベントの発生を受けて、都度々々フローが実行されるわけです。処理対象となるデータは、通常、比較的小規模なものとなるでしょう。

逆に言えば、PowerAutomateは一時に何百何千というデータをまとめて処理する、いわゆる「バッチ処理」にはあまり向いていません。
前述の通り Apply to eachのようなループ処理は所要時間の観点でとてもコストがかかります。
フロー内に埋め込める各種のアクションには、処理対象データの件数に上限が設けられています。1回あたりの処理データ量に制限があるとともに、一定時間内に実行できる回数にも制限があります。
また仮に制限内（ないし無制限）であっても、処理対象データが増えると所要時間が増大し、最悪の場合タイムアウトエラーになります。

したがって、

* __フローの1回あたりの処理対象データはできるだけ少量とする__ べきです。
* 一般に「定時起動でまとめて処理」のバッチ処理は回避すべきで、例外は定時起動することでむしろ処理データ数が小出しにできたり、より重要な他のフローの入力データを事前準備できたりするケースです。
* 大量データの処理がどうしても必要なケースでも、前述のApply to eachのようなループ処理は極力さけるべきで、可能なら「選択」「アレイのフィルター」といった組み込みアクション、そして各種の関数を活用しましょう。
* 大量データの処理がどうしても必要で、しかもループ構造のような複雑な制御が必要なケースでは、Office Scripts（TypeScript）の利用も選択肢に入ります。Excel Onlineコネクタの「スクリプトの実行」系アクションはそれ自体決して高速ではありませんが、Apply to eachなどPowerAutomate組み込みの制御を利用するよりはマシなことがあります。ただしOffice Scriptsのスクリプト実行やスクリプト内の個々の関数呼び出しにも時間制限やタイムアウトリスクがあることは認識しておくべきです。
* 最後に、これらの代替策がいずれも採用できないケースであっても、（したがってフロー全体の所要時間増加が回避できない場合であっても）__処理対象データをできるだけパーティション化して、件数制限やタイムアウトエラーのリスクを減らし__ ます。パーティション化とは、データに含まれる特定の属性（例：申請データの登録日など）を使って、データを論理的に区画して、一時に処理するデータ量を制限することです。

# 6. Excelテーブルの複数行更新は追加＆重複削除で行う

PowerAutomateからExcelファイル上のテーブルにアクセスし、複数のレコードを更新するというケースがあり得ると思います。
データ数が少ないうちは、キーを指定してレコードを更新するというのも手ですが、「まずレコードが存在するかしらべて、あれば更新する、なければ追加する」というロジックはアクション数もかさばりますし、所要時間もかかりがち。時間がかかればタイムアウトリスクも増加します。

データ数が多くなるに従って __「とにかくまず追加する、その後一括で重複レコードを削除する」というロジックのほうが、少ないアクションかつ短い所要時間で済む可能性が高く__ なります。
当然重複を判断するための属性（テーブルの列）を検討したり、重複を一括削除するためのOffice Scriptsの準備したりする必要が生じます。しかし、いつまで経っても終わらないフローやしょっちゅうタイムアウトするフローを作っても仕方がないので、そこは必要な工夫ということになります。

一括削除のためのOffice Scripts活用についてはこちらの記事を参考にしてみてください：

https://qiita.com/mizukyf/items/fa64669a895c110c8b1d

https://qiita.com/mizukyf/items/c6eddca2ff66a90ad6a3

# 7. Excelテーブルへのアクセスを減らす

Excelのテーブルにアクセスするアクション（例：Excelコネクターの「行の取得」）は、当該テーブルとそれが配置されているExcelブックにアクセスする他のユーザーとの競合が発生しやすいです。
競合により、PowerAutomateからのテーブル操作が「GatewayTimeout」エラーにより再試行されフローが停滞したり、最悪の場合フローが失敗となる可能性があります。

したがって、
* PowerAutomateから __Excelテーブルへのアクセスはなるべく控え__ ます。
* Excelではなく __相対的に競合リスクが少ない仕組みを利用__ することを検討しましょう（例：SharePointリストやJSONファイル）。
* それが難しい場合でも、当該 __Excelファイルへアクセスできるユーザーをなるべく少なく__ するよう工夫します（例：ファイルへのアクセス権をフロー実行ユーザーやフロー運用ユーザーに絞る）。
* どうしても回避できない場合は、Excelテーブル・アクセスでエラーが発生したときフローを運用するメンバー宛に通知を送信するステップを用意します。

どれも簡単にできることではないかもしれませんが、競合が発生した場合のフローへのパフォーマンス影響はかなり大きいため考慮に値するはずです。

コンテキストは異なりますが、PowerAutomateのワークフローからJSONファイルにアクセスする方法についてはこちらの記事を参考にしてみてください：

https://qiita.com/mizukyf/items/db7a2329cda787bec399

//