コンテンツへスキップ
DedicatedPHP 接触

長時間実行されるPHPプロセスの時間制限とキャンセル設計

ステージごとの期限を定め、deadlineを依存先へ伝播し、キャンセルを記録することで、長時間実行されるPHPプロセスを状態が分かる形で終了させます。

全体deadline、ステージごとの制限、キャンセル経路を示すPHPプロセスの技術図

数時間かかるインポート、複数のデータソースを照会するレポート、外部連携を調整するタスクは、役に立たなくなった後もリソースを消費し続けることがあります。明示的な制限がなければ、処理中なのか、停止しているのか、未完了なのかを判断するのも困難です。長時間実行されるPHPプロセスの時間制限を設計するには、各ステージに許される時間、処理の停止方法、実行が中断された場合に保持する状態を定める必要があります。

目的は、プロセス全体に単一の数値を適用することではありません。開始から終了まで一貫した時間予算を設定し、それを依存先の処理に伝え、最終状態に実際の経過を反映させることです。タイムアウト、要求されたキャンセル、強制終了はそれぞれ異なる状況であり、区別して扱う必要があります。

明示的な制限が必要な理由

明示的な制限が必要な理由 — guía visual de DedicatedPHP

期限のないプロセスは、メモリ、接続、実行能力を無期限に占有するおそれがあります。また、進行していないにもかかわらず、タスクが実行中のままになることもあります。ワーカー環境では問題が拡大します。停止した実行がワーカーを占有し、後続のジョブを遅らせるためです。

PHPやサーバーの制限は、アプリケーションのポリシーの代わりにはなりません。たとえば、max_execution_timeはスクリプトの実行を制限できますが、ビジネス上許容される総待ち時間を表すとは限りません。また、Webサーバー、PHP-FPM、キューマネージャー、HTTPクライアント、データベースの制限は、それぞれ独立して動作する場合があります。設定が異なれば、あるレイヤーが処理を終了させても、別のレイヤーが対応する状態を保存できないことがあります。

まず、プロセスがどこで実行され、どのコンポーネントが停止させる可能性があるかを特定します。コンソールタスク、キューワーカー、Webリクエストでは、仕組みも結果も異なります。アプリケーションの制限は外部の制御と共存させ、それらのいずれかだけで終了処理を解決できると依存しないようにします。

タイムアウト、deadline、キャンセルを区別する

  • 操作のタイムアウト:クエリやHTTPリクエストなど、個々の呼び出しが待機できる最長時間。
  • 全体のdeadline:プロセス全体の期限。各ステージに全体の時間を与えるのではなく、残り時間を共有します。
  • 要求されたキャンセル:ユーザーがタスクをキャンセルした場合や、システムがキューから取り除いた場合など、処理を停止する明示的な判断。

タイムアウトは、ある操作が割り当てられた時間内に完了しなかったことを示します。それだけでプロセス全体を停止する必要があるとは限りません。停止するかどうかは、ステージと製品のルールによります。一方、キャンセルは制御信号であり、プロセスは安全に対応できる時点で確認し、処理する必要があります。どちらの場合も、OSやスーパーバイザーが正確な時点で実行を停止する保証はありません。強制終了は確認処理の合間に起こり得ます。

deadlineは絶対時刻、または単調増加時計から算出した残り時間として表現します。各ステージを開始する前に、時間に余裕があるかを確認します。これにより、個別のタイムアウトを持つ操作を連続させた結果、全体の期限を大幅に超えることを防げます。PHPでは、比較処理をあちこちに重複させるのではなく、計算とキャンセル確認を共有のオブジェクトまたはサービスにまとめます。

ステージと依存先に時間予算を割り当てる

たとえば、読み取り、変換、書き込み、外部との調整など、プロセスを観測可能なステージに分割します。全体のdeadlineを超えないように、各ステージが消費できる時間を定めます。すべての期限を同じにする必要はありません。上限が明確なローカル処理と、リモートサービスへの呼び出しでは、異なる制限が必要な場合があります。

依存先を呼び出す際は、その操作のタイムアウトとプロセスの残り時間のうち、短い方を使います。ライブラリが対応している場合は、接続と読み取りの制限を個別に設定します。接続がすぐに確立しても、応答に時間がかかることはあります。また、読み取りタイムアウトに、その前段階の所要時間が含まれるとは限りません。同じ考え方をSQLクエリ、ストレージ操作、子プロセスにも適用し、それぞれのクライアントが提供する制御機能を使います。

操作がキャンセルに対応していない場合、即時に中断可能であるかのように扱ってはいけません。待機時間を制限する、別の作業単位に隔離する、または処理の前後でキャンセルを確認する設計にできます。サブプロセスでは、終了を要求するために必要な識別子を保持し、プロセスが終了したことを確認します。シグナルを送ることは、終了を確認することと同じではありません。

処理を停止し、理解できる状態を残す

deadlineに達した場合、またはキャンセルを受け取った場合は、新たなステージを開始しないようにします。安全な時点で、結果を記録するために必要なローカル処理を完了し、カーソルや一時ファイルなど、制御可能なリソースを解放します。クリーンアップ処理で保留中の作業まで完了させようとしてはいけません。また、クリーンアップ自体にも時間制限を設け、停止理由を隠さないようにします。

タスクの状態は、安定した識別子と関連付けて永続的な場所に保存します。少なくとも、running、completed、cancelled、timed_outなど区別可能な状態に加え、現在のステージと関連するタイムスタンプを記録します。オペレーターが読める理由も追加しますが、不要な機密データは保存しません。可能な場合は状態をアトミックに更新し、更新が完了する前にプロセスが終了する可能性も考慮します。

タスクを開始した人に返す結果では、「キャンセル済み」と「期限超過による失敗」を区別します。突然の終了後に状態をまだ確認できない場合は、完了ではなく、不明または確認待ちとして表示します。メトリクスや構造化ログには所要時間、ステージ、停止種別を含められます。秘密情報、プライベートな内容、アクセス制御のないトレースの記録は避けてください。

部分的な効果を再開できるか判断する

データを書き込んだ後、ステージの完了を記録する前に中断されることがあります。そのため、再開できるかどうかの判断は、単にステージ番号を保存するだけでは済みません。どのような効果が発生した可能性があるかを把握する必要があります。ローカル操作では、所要時間とデータベースエンジンが許す場合、トランザクションで関連する変更をまとめられます。長時間の外部呼び出し中にトランザクションを開いたままにしてはいけません。

バッチ処理では、安定したキーを使って完了済みの単位を記録し、各単位が実際に確定したとみなせる条件を定めます。再起動後に書き込みが繰り返される場合、重複を防ぐか、すでに効果が存在することを認識できる設計にします。キャンセルに対応していない外部連携では、クライアントが待機をやめた後も、相手側が要求を受け付けている可能性があります。効果がなかったと決めつけず、その不確実性を状態に反映します。

最後に保存した地点から安全に続行できない場合は、タスクをレビューまたは専用の復旧処理の対象として記録します。曖昧なマーカーから自動的に再開すると、誤った結果を生む可能性があります。どのステージを再実行できるか、どのステージで確認が必要か、どの条件でプロセスを完了とみなせるかを文書化します。

時間制限と中断をテストする

テストでは、例外が発生したことだけでなく、停止処理と最終状態の両方を確認します。制御可能な依存先を使って、遅い呼び出し、期限を超えるクエリ、期限ぎりぎりに返る応答をシミュレートします。各ステージの所要時間によって全体のdeadlineが無視されないことを確認します。

ステージ間のキャンセルと、処理が戻ったときにしか停止できない操作の実行中のキャンセルをテストします。重要な箇所での突然の終了もシミュレートします。たとえば、外部への効果が発生した後、状態を保存する前、クリーンアップ中です。ワーカーを再起動した後に状態が理解でき、再開ポリシーによって作業の重複や欠落が起きないことを確認します。これらのテストでは、隔離されたデータを使い、実際の外部サービスに依存しないようにします。

既存プロセスのチェックリスト

既存プロセスのチェックリスト — guía visual de DedicatedPHP
  • PHP、サーバー、ワーカー、および使用する各クライアントの制限を特定する。
  • 全体のdeadlineと、それを合計で超えない各ステージの時間予算を定める。
  • 可能な場合は、残り時間をHTTP、データベース、その他のプロセスに伝播する。
  • 安全な時点でキャンセルを確認し、受信後は新しい処理を開始しない。
  • 区別可能な状態、現在のステージ、終了理由を永続化する。
  • どのような部分的効果が残り得るか、どの条件で再開できるかを明確にする。
  • 遅延、キャンセル、突然の終了、および再起動後に観測される状態をテストする。

堅牢な設計は、あらゆる操作を瞬時に停止できるとは約束しません。利用可能な時間を明確にし、各依存先の待ち時間を制限し、中断を解釈するために必要な情報を保持します。この組み合わせにより、長時間のプロセスは不透明な実行から、運用・検証可能な処理単位へと変わります。

これらのアイデアをあなたのプロジェクトに活用してみませんか?あなたのPHPプラットフォームについて話し合いましょう。
関連サービスを見る