チュートリアル
hiapi /v1/tasks タスクのハングまたはタイムアウト:診断とリトライの手引き
開発者向け実践マニュアル:停止した非同期タスクのデバッグ、ポーリング間隔の調整、そしてリトライすべき時と再送信すべき時の判断
非同期タスクがハングやタイムアウトを起こす理由
/v1/tasks 上の非同期タスクは三者間の契約です:あなたのクライアント、HiAPI プラットフォーム、そして実際にその処理を実行する計算リソース。ハングはほぼ常に、どこかの層が別の層を待っている状態であり、デバッグで最初にすべきことはどの層かを突き止めることです——層ごとに修復方法が異なり、闇雲なリトライは状況を悪化させるだけだからです。
頭に入れておくべきライフサイクルは次のとおりです:
ライフサイクル、段階ごとの説明
- 送信(Submit)——クライアントがリクエストを送り、プラットフォームが受け付けてタスク識別子を返します。
- キュー投入(Enqueue)——タスクがスケジュールされます。この段階は高負荷時に最も遅くなりやすく、また最も見えにくい部分です。
- 実行(Run)——計算リソースがタスクを処理します。ここでの所要時間はワークロード次第で、あなたの HTTP クライアントとは無関係です。
- 完了(Complete)——終端状態が記録され、同じタスク識別子で読み取れるようになります。
「ハング」は単一の出来事であることは稀で、通常は次のいずれかに該当します:
- タスクは実際にまだ実行中。 長い入力、大きな出力、混雑したキューは第 3 段階を引き延ばします。何も壊れておらず、ただ焦りすぎているだけです。
- タスクはキューに入ったが開始していない。 第 2 段階は長時間に及ぶことがあり、あなたの側からはハングと見分けがつきません。
- クライアントが早々に諦めた。 HTTP タイムアウトをタスクではなく状態照会に設定したため、プラットフォームは正常なのにプロセスが中断してしまいました。
- タスクは終端状態に入ったが検出できなかった。 ポーリングループが答えを得る前に停止した、クラッシュした、または終了しました。
- 真のプラットフォーム側障害。 より稀ですが実在し、プラットフォームレベルのリトライを考えるべき唯一のケースです。
核心的な誤解
最もよくある自滅的なタイムアウトは、二つの時計を混同することです。状態を照会するための HTTP リクエストは短くあるべきです——それはただの読み取りです。タスクにはそれとは別の、はるかに長い期限があります。両方に同じタイムアウト値を使うと、本来健全なタスクをキャンセルし、それをリトライして、もうすぐ完了する作業を二重に実行してしまいます。
何かを変更する前に、あなたが実際に上記五つのケースのどれに当たるのかを判定してください。この手引きの残りの部分はすべて、この答えに依存しています。
ステップ 1:タスクの証拠を収集する
最初の 1 分でコードを変えたりリトライを始めたりする衝動を抑えてください。ハングは、十分な情報を記録して初めて事後デバッグできる状態です。介入する前に以下を取得してください。
各タスクで記録すべきもの
- 送信時に返されたタスク識別子。 これがすべてへの鍵であり、これがなければ推測するしかありません。
- 最後に観測した状態文字列を、言い換えず逐語的に記録。
- タイムスタンプ:送信した時刻、各状態照会を発行した時刻、各照会が返った時刻。
- 実行した状態照会の回数とその間隔。
- リクエストペイロードの形態——完全な内容でなくてもよいが、再現できる程度に:入力規模、パラメータ、実行時間に影響するものすべて。
- 設定したクライアント側の期限(あれば)、およびそれが発動したかどうか。
同じ識別子で手動でもう一度照会する
結論を出す前に、同じタスク識別子で新規の状態照会を一度行ってください。結果は二通りです:
- 状態が進んだ。 タスクは生きています。遭遇したのはポーリングやタイムアウトのバグであり、タスクのバグではありません。ポーリングの節へ進んでください。
- 状態が変わらず、タスクがかなり古い。 これで本当にスタックしている疑いのあるタスクが得られました。その状態にどれだけ留まっているかを記録し、自分の想定と比較してください。
書き留める
試行ごとに短いログ一行を残してください:識別子、状態、経過時間、下した判断。繰り返し発生するハングは、ほぼ常に履歴から診断されるものであり、現場の出来事からではありません。あるタスクが 1 時間前に何をしていたかを復元できなければ、同じ障害を何度もデバッグし続けることになります。
忘れられがちな証拠がもう一つあります:時刻とワークロードの種類です。ハングが特定の種類の入力に集中しているなら、あなたが見つけたのはリトライすべき偶発的障害ではなく、報告に値するパターンです。
タスク状態を正しく解釈する
状態はあらゆるリトライ判断の事実根拠です。小さな状態機械として扱い、各状態を正確に一つのアクションに対応させれば、実行中のタスクをリトライすることは決してなくなります。
目にする状態とその意味
- Queued / pending(キュー済み/保留中)——受け付けたが未開始。ここに長時間留まるのはスケジューリング圧力を意味します。リトライしないでください;タスクはすでに存在します。
- Running / processing(実行中/処理中)——計算リソースが作業中。何も問題ありません。待つのが正しいアクションです。
- Succeeded / completed(成功/完了)——終端状態。結果を読み取って終わりです。
- Failed / error(失敗/エラー)——終端状態で、そのエラーペイロードが最良のデバッグ材料です。何かを決める前に必ず読んでください。
- Cancelled / expired(キャンセル済み/期限切れ)——ポリシーまたはあなた自身の操作による終端状態。リトライするかどうかは新たな判断です。
- Unknown または欠落——識別子が認識できません。これは失敗状態ではなく証拠状態です。識別子を読み直し、正しいものを参照しているか確認してください。
どの状態に対しても問うべき二つの質問
- これは終端状態か? そうでなければ、唯一正しいアクションは予算内で待ち続けることです。
- 終端状態なら、リトライ可能か? 成功はリトライできません。失敗はリトライ可能な場合もそうでない場合もあります——まずエラーを読んでください。未知の識別子はリトライではなく調査が必要です。
経過時間で状態を推測しない
「もう 10 分走っているから、きっと詰まっている」は診断ではなく推測です。経過時間はいつ照会するかを教えるだけで、状態が何であるかは決して教えません。タスクはキューに長時間留まった後、完璧に完了することがあります。結論は状態文字列に出させ、あなたの時計はどれくらいの頻度で問い合わせるかだけを決めます。
合理的なポーリング間隔を設計する
「ハング」のほとんどはポーリングのバグが姿を変えたものです。主な失敗モードは二つ:ポーリングが攻撃的すぎて自身の信頼性を損なう場合と、ポーリングが素朴すぎて終端状態を永遠に検知できない場合です。上限付きの間隔は両方を同時に解決します。
ゆっくり始め、控えめにバックオフする
タイトループでポーリングしないでください。まず短い間隔で速いタスクを素早く完了させ、タスクが古くなるにつれて間隔を広げます:
import time
def poll(job_id, check_status, deadline_s=600):
start = time.monotonic()
interval = 1.0
while True:
status = check_status(job_id)
if status in ("succeeded", "failed", "cancelled", "expired"):
return status
if time.monotonic() - start > deadline_s:
return "client_deadline_exceeded"
time.sleep(interval)
interval = min(interval * 1.5, 30.0) # 上限を設定
重要な性質:初期間隔が速いタスクを捕まえられるほど短いこと、成長因子が長いタスクのコストを低く保つこと、そして上限により 1 時間も眠って結果を逃すことが決してないことです。
常にクライアント側の期限を設定する
プラットフォームが完了シグナルを代わりに出してくれると決して仮定しないでください。自分自身の期限——あなたが待つことを許す最長時間——を設定し、それを超えることは失敗ではなく独立した結果として扱ってください。この区別は重要です:client_deadline_exceeded という結果は「もう見ない」という意味であり、「タスクが死んだ」という意味ではありません。そのように記録してください。
二つの実用的なルール
- 照会総数を制限する。 最大反復回数のないループは、やがて起きるハングそのものです。期限と間隔上限から算出してください。
- 読み取りタイムアウトとタスク期限を分離する。 状態取得の HTTP 呼び出しには独自の短いタイムアウトを持つべきです。状態読み取りがタイムアウトしたらこの読み取りをリトライしてください——タスクを決してリトライしないでください。
エンドポイントを尊重する
ポーリングは安価ですが無料ではありません。数百のタスクをチェックする場合は、それらをずらし、同じクライアントを再利用してください。迷ったときは、より遅い方がほぼ常に安全な方向です:6 分待つ価値のあるタスクは、6 分 30 秒待つ価値もあります。
リトライと再送信のどちらかを決める
これは正確性とコストが衝突する判断です。同じタスクのリトライと新しいタスクの送信は互換ではなく、選び間違えると作業を重複させるか、健全なタスクを放置することになります。
判断ルール
順番に処理してください:
- タスクは終端状態か? そうでなければ、リトライも再送信もしないでください。待ってください。実行中のタスクへのリトライは作業を重複させます。
- 終端状態なら、状態は何と言っているか? 成功は完了を意味します。失敗にはエラーペイロードがある——まずそれを読んでください。
- その失敗は一時的なものかもしれないか? 高負荷下のタイムアウト、一時的な利用不可、レート制限——これらはリトライ候補です。
- その失敗は恒久的なものかもしれないか? 無効な入力、未サポートのパラメータ、不正な形式のペイロード——何度試しても同じように失敗します。リクエストを修正してください。
以下の場合は同じタスクをリトライする……
タスクが存在し、再現しない可能性のある理由で終端失敗状態になり、プラットフォームのそのタスクに対するセマンティクスがもう一度の試行を許す場合。識別子を再利用し、履歴を一つ保ち、同じことをする二つ目のタスクを作るのを避けます。
以下の場合は新しいタスクとして再送信する……
- 識別子が未知または取得不能——リトライすべきものが何もありません。
- 元のタスクがポリシーにより明示的に期限切れになり、リトライの付け所がない。
- 入力を変更する必要がある。 リトライは同じリクエストであり、修正は新しいリクエストです。リトライの意味を元のリクエストと異ならせては決してなりません。
コストの非対称性
実行中のタスクへのリトライを 1 回行うと、1 つの結果のための作業量が倍になります。再送信は明確に新しいタスクです——新しい識別子、新しいライフサイクル、そして新しい費用。どちらも正当なツールであり、どちらも慎重に使うべきです。経験則:インフラに不確実性があるときはリトライ、入力に確実性があるときは再送信。
リトライを安全にする
黙って作業を重複させるリトライはハングより悪いです。静かに失敗し、あなたの費用を消費するからです。必要になる前に安全性を内蔵してください。
冪等性の観点で考える
実行するあらゆる種類のタスクについて問いかけてください:二度実行されても結果は正しいか? 答えが否なら——たとえばそのタスクがレコードを追記したり下流の副作用を引き起こす場合——リトライロジックは状態をゲートにしなければならず、決してタイマーを使ってはなりません。終端失敗のみをリトライし、しかも新規の状態照会がその状態を確認した後に限ります。
試行回数を制限する
すべてのリトライループには最大試行回数と最大総所要時間が必要で、両方を明示的にチェックしなければなりません。無制限のリトライループは、より遅く、さらにお金のかかるハングにすぎません。予算が尽きたら、失敗を表面化させてください——静かに飲み込まないでください。
試行間にバックオフを入れる
リトライ試行自体も間隔を空け、失敗ごとに広げるべきです。すでに故障した依存関係への即時リトライは、通常また失敗し、すでに苦しんでいるシステムに負荷を追加するだけです。
エラーだけでなく試行を記録する
すべての試行を記録してください:タスク識別子、判断時の状態、試行番号、使用した間隔、結果。「ときどきハングする」と「火曜日の午後に大きな入力を処理するとハングする」の違いはこれです。後者は直せますが、前者は直せません。
真の失敗を決して覆い隠さない
タスクが 3 回失敗し、ラッパー層が一般的なエラーだけを返すなら、原因を示すエラーペイロードを捨ててしまいます。最後の状態とエラー詳細を呼び出し元まで保持してください。リトライは復旧機構であり、隠蔽機構ではありません——基盤の問題が浮上する機会を減らすのではなく、見られる機会を減らすべきです。
繰り返すハングの事後レビューチェックリスト
ハングが一度起きたら、その場のタスクを直せば十分です。二度起きたら、このチェックリストを実行してください——さもなければ永遠にリトライの費用を払い続けることになります。
チェックリスト
- 事象を分類する。 観測をやめた実行中タスクか、本当にスタックしたタスクか、クライアントタイムアウトか、読み取れなかった状態か? それを言い表せれば、半分は解決しています。
- ポーリングの計算を確認する。 間隔上限、期限、試行上限は実際に有効か? 無制限のループはないか?
- タイムアウトが分離されていることを確認する。 状態読み取りタイムアウトがタスク期限と混同されたことはないか?
- リトライ判断を見直す。 非終端状態のタスクへのリトライはなかったか? 実行中タスクを重複させる再送信はなかったか?
- 失敗ペイロードを確認する。 エラーは保持されたか、ラッパー層に飲み込まれたか?
- 入力パターンを探す。 失敗はペイロードサイズ、パラメータ、ワークロードの種類で集中しているか?
- 期待を見直す。 タスクが期限を超えたのは、作業が実際にそれだけ大きいからではないか? そうなら、バグは期限の側にあります。
発見をデフォルト設定に変える
上記の各項目は、クライアントのデフォルトのやり方に変換すべきです:上限付きポーラー、独立した期限結果、状態駆動のリトライ戦略、そしてタスク識別子をキーとする構造化ログ。これらを実現すれば、次回のハングは午後を潰す出来事ではなく、ログの一行にすぎなくなります。
非同期タスクは契約であり、その状態を尊重する限り契約は成立します。まだ実行中と永遠に完了できないを区別し、上限付きの間隔で待ち、安全にリトライできるものだけをリトライする——そうすればハングは小さな煩わしさにとどまり、障害にはなりません。
関連記事
すべての記事を見る →-
チュートリアル
HiAPI API で hailuo-2.3-fast/image-to-video を呼び出す方法:curl と Python の実践
高速・低コストな画像から動画へのティアにおける最初の実行可能なリクエスト — モデル ID から MP4 のダウンロードまで
-
チュートリアル
HiAPI APIでhailuo-2.3/text-to-videoを呼び出す方法:curl、Python、実行可能なリクエスト例
-
チュートリアル
HiAPI で Kandinsky 6.0 Pro のテキストto動画を使用する:curl と Python チュートリアル
リクエスト優先の実践デモ:プロンプトから動画クリップへの呼び出しを構築し、リクエストを送信し、完了状態をポーリングし、結果をダウンロードする