コード更新後に常駐プロセスを再起動する判定

  • #常駐プロセス
  • #自動更新
  • #運用

今回やったこと

自動更新でディスク上のコードが新しくなっても、すでに動いているプロセスが自動で新しいコードを読み直すとは限らない。Pythonのプロセスは、起動時に読み込んだモジュールをメモリ上に保持する。ファイルが更新されたことと、実行中のプロセスがその内容を使っていることは別の状態だ。

この差を埋めるため、常駐サービスの起動時刻と、サービスが依存するファイルの更新時刻を比べ、必要なら再起動する仕組みが用意された。対象はダッシュボードとトンネル接続の2サービスで、更新を取り込む定期処理の最後から判定を呼び出す。

pullの差分だけでは見えなかった

以前の判定は、特定の起動スクリプト自身が git pull を実行した前後の差分を見ていた。しかし、別の自動更新ジョブが先にpullを済ませると、後から動く処理では前後に差がない。ファイルは更新済みなのに、再起動判定だけが更新を認識できない状態になる。

実際の記録では、機能追加のコードがサーバーに届いた後も、常駐プロセスが古い状態で動き続けた。新しい機能が404を返していた例があり、ファイルの更新を見ただけでは稼働中の実体まで更新されたか分からないことが表面化した。

そこで、誰がpullしたかではなく、現在の状態を比較する方式に変えた。サービスの起動時刻よりファイルの更新時刻が新しければ、プロセスは古いコードを保持している可能性がある。プロセス起動時刻は秒単位に丸められるため、判定には2秒の余裕を設けている。

依存ファイルの更新時刻 > プロセス起動時刻 + 2秒
    → 古い可能性あり
    → 安全条件を確認して再起動

依存ファイルを手で並べない

ダッシュボードは起動ファイルだけでなく、その中から読み込まれるモジュールにも依存する。関数の中で遅れて読み込まれるモジュールもあり、入口のファイルだけを監視すると更新を見落とす。そこで、Pythonの構文木を使い、起動ファイルからimport先をたどって依存ファイル群を求める。依存一覧を人が固定で書く方式では、importを追加したときに一覧だけ古くなるからだ。

一方、すべてのサービスを無条件に再起動すると、可用性を損なう場合がある。トンネル接続は複数の接続先で冗長化されているため、相手側が接続中で、落ち着いた状態にあることを確認してから片側を再起動する。相手が確認できない場合は、その回の再起動を見送り、次回に再判定する。見送りが永久化しないよう、保留時間にも上限がある。

「更新済み」と「稼働中」を分けて観測する

この方式は、ファイルの更新時刻を使うため、更新経路が増えても同じ条件で判定できる。通常の自動更新だけでなく、別経路でファイルが新しくなった場合も、次の判定で拾える。再起動が一度失敗した場合も、状態が古いままであれば次の機会に再び対象になる。

ただし、ファイルの時刻は実行中の状態そのものではない。依存関係の拾い漏れや、サービス定義が別の実体を指している場合は、時刻比較だけでは解決しない。そのため、対象サービスがそのリポジトリのファイルを実際に使うかを確認し、必要な依存ファイルも追跡する設計になっている。

自動更新の完了を確認するときは、ディスク上のファイル、プロセスの起動状態、サービスが実際に返す機能を分けて見る。更新の成功ログだけで稼働中のコードまで新しくなったと判断しないことが、常駐プロセスを扱う際の基本になる。

更新履歴

  • 2026-10-10: 初稿作成。リポジトリ内の鮮度判定実装と経緯をもとに構成。