Azure Logic Apps を使っていると、実行履歴(Run history)に表示される各アクションの所要時間が「足し算で合わない」「親より子の方が短いのに、親の表示が長い」といった違和感に出会います。本記事では、時間表示がズレて見える理由と、実際の処理時間を正しく計測・改善するための具体策を、ワークフロー設計の観点から整理します。
起きていること:実行履歴の「所要時間」が一致しない
典型的には、メインワークフローがサブワークフローや Function App を呼び出し、さらに別のワークフローや外部 API を連鎖させる構成で発生します。例えば次のような流れです。
- トリガー → 変数初期化 → do-while ループ
- SQL コネクタで行取得(404 の場合は分岐)
- 404 の場合にサブワークフローを呼び出して詳細検索
- 別ワークフローで認証トークン取得(Function App を 2 つ呼び出し)
- 取得したトークンで HTTP コネクタからサードパーティ API 呼び出し
- 404 の場合は DB に insert してループ終了
このとき、親フローでは「サブワークフロー呼び出し」アクションが 6 秒と表示される一方、サブフロー側は 4.3 秒と表示されるなど、見た目の時間がかみ合わなくなります。さらにサブフローが呼び出す別フローや Function App でも同様の現象が連鎖し、「全体で 10 秒くらいかかっている。C# のアプリならもっと速いのでは?」と感じやすくなります。
結論:実行履歴の時間は「純粋な処理時間」ではなく「そのアクションが完了するまでのエンドツーエンド時間」
Logic Apps の実行履歴に表示される所要時間は、アクションが開始してから「そのアクションが完了と判定される」までの時間です。ここには、ビジネスロジックの実行時間だけでなく、オーケストレーションや通信などの周辺コストが含まれます。したがって、親と子、あるいは複数の階層で見たときに「数値がズレて見える」こと自体は珍しくありません。
| 実行履歴の所要時間に含まれやすい要素 | 具体例 | ズレとして見える典型パターン |
|---|---|---|
| オーケストレーションのオーバーヘッド | サブワークフロー起動、状態永続化、再開(リハイドレーション) | 親の「呼び出し」アクションが、子の実行時間より長く表示される |
| 認証・権限チェック | コネクタの接続情報検証、Managed Identity のトークン取得 | 同じ API 呼び出しでも時々だけ遅い(初回が特に遅い) |
| ネットワークレイテンシ | DNS、TLS ハンドシェイク、経路混雑、リージョン間通信 | サブフロー内の HTTP は短いのに、親側の待機が伸びる |
| 内部キューイング/スロットリング | 同時実行制御、コネクタのスロットル、バックエンドの待ち行列 | 一定の負荷を超えた瞬間から、所要時間が階段状に増える |
| リトライ/ポーリング | 再試行ポリシー、長時間実行のポーリング(非同期完了待ち) | 失敗していないのに「1 回だけ極端に長い」実行が混ざる |
| Function App のコールドスタート | Consumption/Premium の起動、依存ライブラリ読み込み | 子フローの 1 アクションだけが時々数秒伸びる |
実行履歴の見方:どの値が「ズレ」を生みやすいか
実行履歴の画面では「Duration(所要時間)」だけが目立ちますが、原因を掘るときは、アクション詳細で表示される開始・終了タイムスタンプも一緒に見ます。特にサブワークフロー呼び出しや HTTP のような外部連携では、Duration は “待ち” を含むことが多いからです。
| 実行履歴で見る項目 | 意味 | 誤解しやすい点 | 使いどころ |
|---|---|---|---|
| Started time | アクションが開始したとランタイムが判断した時刻 | 「子の処理開始」とは一致しない(呼び出し準備や送信開始の可能性) | 遅延が発生し始めた地点の特定 |
| Ended time | アクションが完了したと判断した時刻 | 出力の受領や完了通知の待機を含むことがある | 待機が長いのか、実処理が長いのかの当たりを付ける |
| Duration | Started~Ended の差分 | 内部キュー・認証・通信・ポーリングなどの合算で「内訳」が見えない | 運用上の監視(P95 など)には有用 |
| Retry history / Status | リトライやスキップ等の状態 | 成功していても裏で複数回試行されている場合がある | 「たまに遅い」の説明材料になる |
なぜ親フローとサブフローの時間が「足し算で合わない」のか
親フロー上の「サブワークフロー呼び出し」アクションの時間は、ざっくり言うと次の式で考えると理解しやすくなります。
親の所要時間 ≒(子が実行される時間)+(呼び出しと待ち受けの固定費)+(観測のズレ)
イメージしやすい分解例
親で 6.0 秒、子で 4.3 秒と表示されるケースを、あくまで例として分解すると次のようになります。
- 親:呼び出し要求の組み立て・接続確認(0.4 秒)
- 親→子:バックエンド受け渡し・スケジューリング待ち(0.6 秒)
- 子:実処理(4.3 秒)
- 子→親:完了通知・出力受領・状態更新(0.7 秒)
このように、親の「c アクション」には子の実行以外の時間が含まれます。子側の 4.3 秒が「嘘」というわけではなく、親が見ている計測範囲がより広い(=待ちの固定費を含む)だけ、という理解が近いです。
計測の開始点・終了点がそもそも違う
親のアクションは「子を呼び出す要求を作って送る」瞬間から計測が始まります。一方、子フロー側の計測は「子が実行可能になり、最初のアクションを開始した」タイミングから始まります。両者の間には、バックエンドでの受け渡し・スケジューリングが挟まるため、同じ“仕事”を見ているようで、計測範囲は完全には一致しません。
並列実行や分岐があると、時間の足し算が成立しない
Scope(スコープ)内で並列分岐している、または do-while の中で複数の枝が条件で切り替わる場合、画面上の時間を単純に合計しても「実際の経過時間」と一致しません。並列に動いている枝は合計ではなく最大値に寄るためです。実行履歴は “アクション単体の観測” なので、全体像は「タイムライン」として捉えるのが安全です。
内部キューや状態管理が入る(Logic Apps の特性)
Logic Apps はワークフローを確実に実行・再開できるように、アクションごとの状態を管理し、必要に応じて永続化します。この仕組みは可観測性やリトライ耐性の面では強力ですが、純粋なメモリ内実行よりもオーバーヘッドが出ます。特に、ループや分岐が多い、サブフローを何度も呼び出す、という構成では「小さな固定費」が積み上がり、全体の体感を押し上げます。
コネクタは「外部サービスとの橋渡し」なので時間の内訳が見えにくい
SQL コネクタや HTTP コネクタの所要時間には、実際のクエリ/API 処理時間だけでなく、接続確立、認証、ゲートウェイ、スロットリング待ち、レスポンスの返却までの待ち時間が一体化して表示されます。さらに、サードパーティ API 側の遅延が一定でない場合、Logic Apps 側の実行履歴だけを眺めても原因が特定しにくくなります。
表示は「観測値」であり、丸めや非同期処理の影響を受ける
実行履歴は、あくまで Azure 側が観測したタイムスタンプから算出されます。複数のバックエンド(Logic Apps ランタイム、コネクタ、Function のホストなど)をまたぐため、タイムスタンプの粒度、表示の丸め、非同期完了の判定タイミングにより、数百ミリ秒〜数秒程度の差が積み上がることがあります。
「実際のビジネスロジック時間」を知りたい場合の計測方法
実行履歴は運用上の入口として便利ですが、性能検証では「ワークフロー全体」「外部呼び出し」「Function の中身」を分けて測ることが重要です。おすすめは、相関 ID(Correlation ID)を軸にした分散トレーシングです。
相関 ID を最初に作り、全呼び出しに渡す
トリガー直後に GUID を 1 つ生成し、以降のサブワークフロー呼び出し、Function App 呼び出し、外部 API 呼び出しのヘッダーやパラメータに載せます。これにより、ログを横断して「同じ 1 回の処理」が追えるようになります。
{
"correlationId": "@{guid()}",
"startedAt": "@{utcNow()}"
}
Function App では開始・終了を必ずログに残す
Function App 側の「本体処理時間」を知るには、開始直後と終了直前でタイムスタンプを記録し、その差分を Application Insights などに送るのが最短です。ポイントは、単にログを出すのではなく、相関 ID を customDimensions に入れて検索できる状態にすることです。
Logic Apps 側でも「開始」「終了」を明示的に残す
サブワークフローの入口と出口に utcNow() を書き込み、必要なら Compose で差分を計算してログとして出します。これにより、実行履歴の表示だけでは見えない「待ち時間(親から呼ばれて実行開始するまでの時間)」も推定できます。
| 計測したいもの | どこで測る | 記録する値 | 得られる示唆 |
|---|---|---|---|
| Function の純粋な処理時間 | Function のコード内 | 開始/終了時刻、相関 ID | ビジネスロジックが遅いのか、呼び出しが遅いのかを切り分け |
| HTTP 呼び出しの実時間 | Function または API クライアント側 | 送信/受信時刻、ステータス、リトライ回数 | API 側遅延、ネットワーク、TLS の影響を判定 |
| ワークフロー全体の体感時間 | Logic Apps(トリガー直後と最後) | 開始/終了時刻、runId、相関 ID | 運用上の SLO(例:90〜99 パーセンタイル)を作れる |
| オーバーヘッド(差分) | 計測値の突合 | (実行履歴の時間)-(Function/外部の実測) | スロットリング、キュー、状態管理の固定費を見積もれる |
ボトルネック切り分けのためのチェックポイント
ズレの説明が付いたとしても、体感が遅いなら改善は必要です。改善の第一歩は「どこが遅いか」を誤判定しないことです。Logic Apps はオーケストレーターなので、遅さの原因は Logic Apps そのものではなく、呼び出し先や設計にあることが多いからです。
SQL コネクタ:取得量とクエリの形を疑う
- 必要な列だけを取得しているか(
SELECT *を避ける) - インデックスが効いているか(特に do-while の条件列)
- 同じ行を何度も取りに行っていないか(ループ内の重複クエリ)
- 1 件ずつ取りに行っていないか(可能ならバッチやページングにする)
SQL の遅延は「毎回一定に遅い」傾向があり、実行履歴でも比較的素直に反映されます。まずは DB 側の実行計画と待機イベントを確認し、Logic Apps に問題があると思い込まないのがコツです。
認証トークン取得:毎回取りに行く設計は要注意
認証トークン取得のために「別ワークフロー → Function App 1 → Function App 2」というチェーンを毎回回している場合、少しの固定費が積み上がって全体を押し上げます。トークンが数十分〜数時間有効なタイプなら、次のような工夫で体感を大きく改善できることがあります。
- 1 回の実行(run)内ではトークンを変数に保持し、繰り返し取得しない
- 複数の API 呼び出しで同じトークンが使えるなら、まとめて取得する
- サードパーティ API が許すなら、トークンのキャッシュ層(例:Redis)を用意する
- 可能であれば Managed Identity や接続の統合認証で “毎回の取得処理” を減らす
HTTP コネクタ:遅いのは Logic Apps ではなく相手かもしれない
HTTP 404 のように「結果はすぐ返ってくるはず」と想像していても、実際には相手側で検索やレート制限が発生していることがあります。次の観点で確認すると、原因が早く絞れます。
- API の応答時間を相手側のログ(またはサポート)で確認できるか
- API のレート制限(429)や遅延応答がないか
- 同一リージョンから呼ぶと速いが、別リージョンからは遅いなどの地域差があるか
- TLS ハンドシェイクが頻発していないか(接続再利用が効いているか)
do-while ループ:回数と待機を設計する
Logic Apps のループは便利ですが、短い周期で回し続けると、オーケストレーションの固定費が効いてきます。「404 の場合は insert して終了」のように条件が明確なら、ループ回数を減らす設計(1 回のクエリで判定できる形、または DB 側で状態を管理する形)に寄せるだけで、全体時間が目に見えて短くなることがあります。
改善の方向性:プランと構成で “効くレバー” が違う
パフォーマンス改善は、闇雲に速いプランにするより「どの遅さを消したいか」を決めて打つのが効果的です。特に、Logic Apps と Function App はそれぞれランタイム特性が異なり、効く対策も変わります。
Logic Apps(Consumption / Standard)の違いを意識する
同じ “Logic Apps” でも、運用形態によって実行の揺れ方が変わります。Standard(シングルテナント)はアプリとして常駐させやすく、ワークフローが温まっている状態を作りやすい一方、Consumption(マルチテナント)はスケールの恩恵を得やすい反面、バックエンドの共有状況でブレが出ることがあります。いずれも万能ではないため、狙うのが「平均の短縮」なのか「ばらつき(P95)の削減」なのかを先に決めると選びやすくなります。
コネクタの種類:Managed と Built-in で経路が違う
特に Standard では、ワークフロー内で “アプリ内で完結するコネクタ(Built-in)” と “サービス経由のコネクタ(Managed)” が混在し得ます。Managed コネクタは外部サービスとの間にコネクタ基盤が入りやすいため、認証・接続・待機の固定費が上乗せされることがあります。遅延の原因がコネクタ経路にありそうなら、同じ目的をより軽い経路で実現できないか(HTTP で直接呼ぶ、Built-in を使う等)を検討する価値があります。
Function App:コールドスタートが疑わしいなら実行環境を見直す
「初回だけ遅い」「一定時間アクセスがないと遅い」「時々だけ数秒伸びる」なら、Function App のコールドスタートが疑われます。一般的には、常時稼働に近い形(Always On 相当、プリウォーム、より安定したプラン)に寄せるとブレが減ります。ただし、これは “Function の起動待ち” を減らす効果であり、Logic Apps のオーケストレーション自体の固定費を消すものではありません。
サブワークフロー:分割の粒度を見直す
再利用性・可読性のためにサブワークフローを分割するのは良い設計です。しかし「呼び出し回数が多い」「1 回の処理が軽い」部分を細かく分けすぎると、呼び出しの固定費が相対的に大きくなります。次の基準で整理すると、過剰分割を避けやすくなります。
- 1 回の呼び出しで実行する “本体処理” が 100〜200ms 程度しかないなら、統合候補
- 同じ相手に連続で呼ぶなら、1 回のサブフローにまとめてバッチ化できないか検討
- トークン取得 → API 呼び出し → 結果整形 など、常にセットなら一体化を検討
並列化:独立な処理は Parallel branch で短縮できる
「Function App を 2 つ呼ぶ」「複数の補助検索を並べて実行できる」など、依存関係がない処理は並列化の余地があります。Logic Apps の並列分岐を使うと、合計処理時間を最大値に近づけられる可能性があります。ただし、外部 API の同時呼び出しはスロットリングやレート制限を招くため、同時実行数の上限(Concurrency)とセットで設計します。
リトライとタイムアウト:速さのために “適切に諦める”
外部 API や SQL の瞬断に備えるリトライは重要ですが、既定のリトライが「体感を悪化させる」こともあります。例えば “404 の場合は別経路へ” のように次の手が決まっているなら、404 は即分岐し、余計なリトライを避けるのが有利です。また、タイムアウトを短くしすぎると逆にリトライが増えるので、API 側の SLA と合わせて設計します。
データ転送量:アクション間で巨大な JSON を渡さない
実行履歴の表示時間が長いとき、意外と効くのがデータ量です。SQL から大きな行を引き、サブワークフローにそのまま渡し、さらに Function に渡す、といった形は、ネットワークだけでなくシリアライズ/デシリアライズや状態保存のコストを押し上げます。必要最小限のフィールドに絞り、重いデータはストレージに置いて参照する、という分離が効くケースがあります。
| よくある改善レバー | 効きやすい症状 | 具体策 | 注意点 |
|---|---|---|---|
| コールドスタート対策 | 初回だけ遅い/時々だけ遅い | Function の実行環境を安定化、依存を減らす | 固定費は残る。費用とのバランスが必要 |
| 呼び出し回数削減 | 小さな処理の連鎖で全体が伸びる | サブフロー統合、Function 統合、バッチ化 | 再利用性とのトレードオフ |
| トークン取得の最適化 | 認証だけで毎回数秒 | run 内キャッシュ、外部キャッシュ、統合認証 | セキュリティ要件に合わせた管理が必要 |
| 外部 API の最適化 | HTTP の揺れが大きい | リージョン近接、リトライ設計、回数削減 | 相手側仕様(レート制限)を優先 |
| データ量削減 | JSON が肥大化、履歴表示も重い | 必要列だけ、参照設計、圧縮・ID 化 | 運用時の可読性も考える |
「C# のアプリなら速いはず」と感じたときの整理の仕方
結論から言うと、同じ処理を “単一プロセスの C#” で書けば、オーケストレーションや状態永続化の固定費が少ない分、レイテンシは小さくなりやすいです。一方、Logic Apps には「可視化」「運用しやすさ」「再試行」「コネクタ」「監査」「ノーコード運用」という強みがあります。重要なのは、要求性能に対して、どこまでの固定費を許容できるかです。
| 要求・状況 | Logic Apps が向くケース | C# / Function 主体が向くケース |
|---|---|---|
| 人や業務プロセスが関わる | 承認・通知・分岐が多い。失敗時の再実行や監査が重要 | — |
| 外部サービス連携が中心 | コネクタで素早く実装し、運用で改善したい | — |
| レイテンシが厳しい | — | サブ秒やミリ秒単位が必須。高頻度・高スループット |
| 処理が CPU 集約 | — | 画像処理、重い変換、複雑なアルゴリズムなど |
| 障害時の補償や再実行を簡単にしたい | ワークフローで可視化し、補償トランザクションを組みやすい | Durable Functions 等で実装する選択肢もある(ただし設計負荷は上がりやすい) |
運用の落とし穴:平均値ではなく “ばらつき” を見る
「だいたい 6 秒」と「たまに 20 秒」が混ざると、体感や SLA に大きく影響します。Logic Apps/Function/外部 API はいずれも共有基盤の影響を受けるため、平均値だけでは判断を誤ります。次のように、パーセンタイル(P90/P95/P99)で見ると実情に近づきます。
- ワークフロー全体の P95 が許容範囲か
- 遅い実行のとき、どのアクションが伸びているか
- 伸びているアクションは毎回同じか、ランダムか
遅延がランダムならコールドスタートや外部要因、毎回同じなら設計やクエリが原因、といった仮説が立てやすくなります。
実務で効く “付き合い方” のまとめ
- 実行履歴の所要時間は「オーケストレーションやネットワークを含むエンドツーエンド」であり、純粋な処理時間ではない
- 親フローとサブフローの表示時間が合わないのは、計測範囲の差+固定費+観測のズレがあるためで、不具合とは限らない
- 本当に速くしたいなら、相関 ID でログを貫通させ、Function/外部 API の実測と突合してボトルネックを特定する
- 改善は「コールドスタート対策」「呼び出し回数削減」「トークン取得の最適化」「データ量削減」「並列化」「リトライ最適化」の順で当たりやすい
- サブ秒が必須なら C#/Function 主体、数秒〜十数秒が許容される業務連携なら Logic Apps を中心に据える、という棲み分けが現実的

コメント