Workflow ノードのタイムアウトが頻発する:タイムアウト設定・ノード最適化・非同期パターンの実践ガイド
はじめに
Dify の Workflow を本番運用に乗せた途端、「ノードがタイムアウトしました」というエラーが頻発する――これは多くのエンタープライズチームが経験する壁である。単純に「モデルの応答が遅い」と片付けがちだが、実際にはフロー全体の設計、入力データのサイズ、外部依存の応答時間、リトライ戦略の欠如が複合的に作用している。
本記事では、Workflow ノードタイムアウトの原因を体系的に分析し、ノード分割・コンテキスト最適化・非同期処理パターンを含む具体的な解決策を解説する。
症状
| 症状 | 発生しやすい場面 |
|---|---|
| LLM ノードが途中で打ち切られ、不完全な応答が返る | 長文入力、複雑な指示、大量のコンテキスト |
| HTTP リクエストノードがタイムアウトする | 外部 API のレスポンスが遅い、レート制限に抵触 |
| Workflow 全体が数分後にエラー終了する | 中間変数の肥大化、直列ノードの積み重ね |
| 特定時間帯だけタイムアウトが増える | 同時実行数の増加、外部サービスの負荷集中 |
| PDF/画像処理を含むフローが安定しない | VLM 処理、OCR、大容量ファイルの解析 |
原因分析
Workflow 実行の制約を理解する
Dify の Workflow は無制限にリソースを消費できるわけではない。公式の環境変数ドキュメントで以下の制約が明示されている:
| 環境変数 | デフォルト値 | 意味 |
|---|---|---|
MAX_VARIABLE_SIZE | 204800 (200KB) | 単一変数の最大サイズ(バイト) |
WORKFLOW_MAX_EXECUTION_STEPS | 500 | Workflow の最大実行ステップ数 |
WORKFLOW_MAX_EXECUTION_TIME | 1200 | Workflow の最大実行時間(秒) |
HTTP_REQUEST_MAX_CONNECT_TIMEOUT | 300 | HTTP リクエストノードの接続タイムアウト(秒) |
HTTP_REQUEST_MAX_READ_TIMEOUT | 600 | HTTP リクエストノードの読み取りタイムアウト(秒) |
HTTP_REQUEST_MAX_WRITE_TIMEOUT | 600 | HTTP リクエストノードの書き込みタイムアウト(秒) |
タイムアウト発生の5つのレイヤー
flowchart TD
A[タイムアウト発生] --> B{どのレイヤー?}
B --> C[LLM応答遅延]
B --> D[外部API遅延]
B --> E[中間変数の肥大化]
B --> F[同時実行数の飽和]
B --> G[ファイル処理の長時間化]
C --> C1[入力トークン過大]
C --> C2[出力生成が長い]
C --> C3[モデル選定が不適切]
D --> D1[レート制限]
D --> D2[外部サービス障害]
D --> D3[タイムアウト設定不足]
E --> E1[上流ノードの出力未圧縮]
E --> E2[ループによる変数蓄積]
F --> F1[Worker数不足]
F --> F2[キュー詰まり]
G --> G1[大容量PDF]
G --> G2[VLM処理]
G --> G3[OCR処理]
レイヤー1:LLM ノードの応答遅延
LLM ノードのタイムアウトで最も多い原因は、入力コンテキストが大きすぎる ことである。
典型例:
- ナレッジベースから Top-K=10 で取得したチャンクをすべて LLM に投入
- 上流ノードの出力(数千トークン)をそのまま下流に伝搬
- 1つの LLM ノードに「抽出 → 要約 → フォーマット → 結論生成」を全部させる
入力サイズとレスポンス時間の目安:
| 入力トークン数 | GPT-4o 応答時間目安 | Claude 3.5 Sonnet 応答時間目安 |
|---|---|---|
| 1,000以下 | 2〜5秒 | 2〜5秒 |
| 5,000 | 5〜15秒 | 5〜12秒 |
| 20,000 | 15〜40秒 | 12〜30秒 |
| 50,000以上 | 40〜120秒 | 30〜90秒 |
レイヤー2:外部 API・ツールノードの遅延
HTTP リクエストノードやカスタムツールノードが外部サービスに依存している場合、そのサービスの応答時間がボトルネックになる。
よくあるケース:
- サードパーティ API のレート制限(429 Too Many Requests)
- データベースクエリの遅延
- 外部 SaaS サービスのメンテナンス時間帯
レイヤー3:中間変数の肥大化
上流ノードの出力がそのまま下流に渡され、各ノードで蓄積されることで、MAX_VARIABLE_SIZE の制限に抵触する。
ノード A(LLM)→ 出力 5KB
→ ノード B(LLM)→ 入力 5KB + 出力 8KB = 13KB
→ ノード C(LLM)→ 入力 13KB + 出力 10KB = 23KB
→ ノード D(LLM)→ 入力 23KB + ...
レイヤー4:ファイル処理の特殊性
PDF、画像、VLM(Vision Language Model)を扱うフローは、テキストベースのフローとは桁違いの処理時間を要する。
解決策
解決策1:ノード分割(最も効果的)
一つの LLM ノードに複数の責務を持たせるのではなく、機能ごとにノードを分割する。
Before(アンチパターン):
[単一LLMノード]
プロンプト: 以下のドキュメントを読み、
1. 重要なポイントを抽出し
2. 要約を作成し
3. JSON形式でフォーマットし
4. 推奨アクションを提案してください
After(推奨パターン):
[ノード1: 抽出] → [ノード2: 要約] → [ノード3: フォーマット] → [ノード4: 推奨]
メリット:
- 各ノードの入出力が小さくなり、タイムアウトリスクが低減する
- 途中のノードが失敗しても、そこだけリトライできる
- 軽い処理には軽量モデル、重い判断には高性能モデルを使い分けられる
解決策2:コンテキストの圧縮
# 悪い例:上流の全出力をそのまま渡す
llm_node:
context: "{{node_a.output}}" # 5000トークンの生テキスト
# 良い例:要約ノードを挟んで圧縮する
summary_node:
model: gpt-4o-mini # 軽量モデルで要約
prompt: "以下を300字以内で要約: {{node_a.output}}"
llm_node:
context: "{{summary_node.output}}" # 300字程度に圧縮済み
解決策3:モデルの使い分け
| タスク種別 | 推奨モデル | 理由 |
|---|---|---|
| テキスト分類・ルーティング | gpt-4o-mini / Claude 3.5 Haiku | 高速・低コスト |
| 要約・情報抽出 | gpt-4o-mini / Claude 3.5 Sonnet | バランス型 |
| 複雑な推論・判断 | gpt-4o / Claude Opus | 精度重視 |
| コード生成・分析 | Claude 3.5 Sonnet | コード処理に強い |
解決策4:HTTP リクエストノードのタイムアウト設定
# HTTP リクエストノードの設定
http_request:
url: "https://api.example.com/process"
method: POST
timeout:
connect: 10 # 接続タイムアウト(秒)
read: 30 # 読み取りタイムアウト(秒)
write: 30 # 書き込みタイムアウト(秒)
retry:
max_attempts: 3 # 最大リトライ回数
backoff: exponential
解決策5:条件分岐による早期リターン
不要な処理をスキップするための条件分岐を入れる。
flowchart LR
A[入力] --> B{入力長 > 5000トークン?}
B -->|Yes| C[要約ノード]
B -->|No| D[直接処理]
C --> D
D --> E[出力]
解決策6:環境変数の調整(セルフホスト環境)
docker-compose.yml または .env で以下を調整する:
# Workflow 実行制限の緩和
WORKFLOW_MAX_EXECUTION_TIME=1800 # 30分に延長
WORKFLOW_MAX_EXECUTION_STEPS=1000 # ステップ数上限を緩和
# 変数サイズ制限の緩和
MAX_VARIABLE_SIZE=524288 # 512KB に拡大
# HTTP リクエストタイムアウトの調整
HTTP_REQUEST_MAX_CONNECT_TIMEOUT=60
HTTP_REQUEST_MAX_READ_TIMEOUT=300
HTTP_REQUEST_MAX_WRITE_TIMEOUT=300
注意:これらの値を無制限に大きくすることは推奨しない。リソース消費が増大し、他のリクエストに影響を与える可能性がある。
解決策7:非同期処理パターン
処理時間が本質的に長いフローには、同期的な応答を期待せず、非同期処理パターンを採用する。
sequenceDiagram
participant User as ユーザー
participant API as Dify API
participant WF as Workflow
participant CB as コールバック
User->>API: タスク投入(POST /workflows/run)
API->>User: task_id を即時返却
API->>WF: バックグラウンド実行開始
WF->>WF: ノード1 → ノード2 → ... → ノードN
WF->>CB: 完了通知(Webhook)
User->>API: 結果取得(GET /workflows/{task_id})
API->>User: 処理結果を返却
Dify API の非同期呼び出し例:
# Workflow をブロッキングモードで実行(同期)
curl -X POST 'https://api.dify.ai/v1/workflows/run' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
-d '{
"inputs": {"query": "処理対象テキスト"},
"response_mode": "blocking"
}'
# Workflow をストリーミングモードで実行(非同期)
curl -X POST 'https://api.dify.ai/v1/workflows/run' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
-d '{
"inputs": {"query": "処理対象テキスト"},
"response_mode": "streaming"
}'
予防策
1. Workflow 設計レビューチェックリスト
- 1つの LLM ノードに3つ以上の責務が集中していないか
- 上流ノードの出力サイズを把握しているか
- 外部 API 呼び出しにタイムアウトとリトライが設定されているか
- ファイル処理ノードが直列チェーンに含まれていないか
- 条件分岐で不要な処理をスキップしているか
2. 負荷テストの実施
本番投入前に、以下の条件でテストする:
# 並行実行テスト(10件同時)
for i in $(seq 1 10); do
curl -X POST 'https://api.dify.ai/v1/workflows/run' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
-d '{"inputs": {"query": "テスト入力'$i'"}, "response_mode": "blocking"}' &
done
wait
3. モニタリングとアラート
Workflow の実行ログを定期的に確認し、以下の指標を監視する:
| 指標 | 警告閾値 | 対応アクション |
|---|---|---|
| ノード平均実行時間 | 30秒超 | ノード分割を検討 |
| タイムアウト率 | 5%超 | 原因ノードの特定と最適化 |
| 中間変数平均サイズ | 100KB超 | コンテキスト圧縮の導入 |
| Workflow 全体実行時間 | 5分超 | 非同期化を検討 |
4. トラブルシューティング手順
タイムアウトが発生した場合の推奨調査順序:
- ログで特定:どのノードでタイムアウトが発生しているかを確認
- 入力サイズを確認:該当ノードへの入力変数のサイズを確認
- 外部依存を確認:外部 API のレスポンス時間やステータスを確認
- 同時実行を確認:同時間帯の Workflow 実行数を確認
- 分割可能性を検討:該当ノードを分割・簡素化できないかを検討
- 非同期化を検討:処理が本質的に長時間を要する場合、非同期パターンに切り替え
まとめ
Workflow のタイムアウト問題は、「モデルの速度の問題」ではなく「フロー設計の問題」である。本当に安定したフローは、ノード分割による責務の分離、コンテキストの圧縮による入力サイズの制御、失敗時のリトライと降格パス、そして本質的に長い処理への非同期化設計から生まれる。
環境変数のタイムアウト値を単に延長するのは応急処置にすぎない。根本的な解決は、Workflow の設計そのものを見直すことにある。