ポーリングを止め、イベントに即座に反応する
以前はファイルの処理にCronを使用していましたが、それはあまりスマートな解決策ではありませんでした。60秒ごとにスクリプトが起動し、新しいデータがないかディレクトリをスキャンしてはまた停止する。動作はしていましたが、遅延が気になりました。例えば、12:00:01にファイルが届いても、59秒間放置されてしまいます。さらに悪いことに、フォルダが空でもスクリプトが実行されるため、無意味にCPUサイクルを消費し、ログを汚すことになっていました。
Systemd Pathユニットがその状況を変えてくれました。1分ごとに「何か新しいものはあるか?」と確認するのではなく、イベントが発生した瞬間にシステムが通知してくれます。4GBのRAMを搭載した標準的なUbuntu 22.04サーバーで、CronからPathユニットに切り替えたところ、待機時のCPU使用率が低下しました。PathユニットはLinuxカーネルのinotifyサブシステムを使用するため、アクティビティを待機している間のメモリ消費量は1MB未満です。これは、sysctlによるLinuxカーネルパラメータのチューニングと同様に、リソースを効率的に管理する上で非常に有効です。incronのような追加ツールをインストールする必要もありません。OSに標準で組み込まれています。
クイックスタート:5分でできる設定
パストリガーを機能させるには、監視対象を監視する.pathユニットと、スクリプトを実行する.serviceユニットの2つのファイルが必要です。ここでは、/tmp/trigger.txtというファイルを監視する設定をしてみましょう。
1. サービスユニットの作成
サービスは実行するアクションを定義します。/etc/systemd/system/task-runner.serviceを作成し、以下の設定を貼り付けてください。
[Unit]
Description=ファイル変更時にスクリプトを実行
[Service]
Type=oneshot
ExecStart=/usr/local/bin/my-script.sh
2. Pathユニットの作成
このファイルはSystemdに何を監視するかを伝えます。自動的に連携させるために、サービスと同じベース名にする必要があります。/etc/systemd/system/task-runner.pathを作成します:
[Unit]
Description=/tmp/trigger.txt の変更を監視
[Path]
PathModified=/tmp/trigger.txt
[Install]
WantedBy=multi-user.target
3. ウォッチャーを有効化する
システムマネージャーをリロードし、Pathユニットを有効にします。注意点として、起動するのはサービスではなく、pathユニットの方です。
sudo systemctl daemon-reload
sudo systemctl enable --now task-runner.path
これで、echo "data" >> /tmp/trigger.txtを実行した瞬間に、Systemdが即座にスクリプトを実行するようになります。
適切なトリガーの選択
Systemdはファイルのアクティビティを監視するためのいくつかの方法を提供しています。不適切なものを選択することは、初心者が陥りやすいミスです。本番環境で各ディレクティブがどのように動作するかを説明します:
- PathExists: ファイルが存在するかどうかでトリガーされます。ファイルが新しいか10年前のものかは関係ありません。ファイルが存在する限り、サービスが実行されます。
- PathExistsGlob: 上記と同様ですが、ワイルドカードをサポートしています。
/uploads/*.pdfのようなパターンに使用します。 - PathChanged: ファイルへの書き込みが完了し、閉じられるまで待機します。ファイルアップロードの監視にはこれが最も安全な選択肢です。スクリプトがデータを処理する前に、書き込みプロセスが確実に終了していることを保証します。
- PathModified: より即時性が高い設定です。ファイルが閉じられる前であっても、書き込み操作が行われるたびにトリガーされます。大きなファイルの場合、スクリプトが不完全なデータを読み取る可能性があるため、使用は避けてください。
- DirectoryNotEmpty: 「監視フォルダ」に使用します。以前は空だったディレクトリに最初のファイルが現れた瞬間にトリガーされます。
経験上、データパイプラインにはPathChangedがほぼ常に最良の選択です。これは、書き込みプロセスとスクリプトの間の競合状態を防ぐ自然なバッファとして機能します。
高頻度イベントの処理
100個のファイルが同時にディレクトリに届いたらどうなるでしょうか?Systemdが100個のプロセスを同時に立ち上げて VPSがクラッシュするのではないかと心配になるかもしれません。幸いなことに、Systemdには保護機能が組み込まれています。
逐次実行のルール
すでに.serviceが実行中の場合、Systemdは2つ目のインスタンスを開始しません。現在のタスクが終了するのを待ちます。その間に新しいイベントが発生した場合、Systemdはそれらをキューに入れ、現在のタスク終了後にもう一度だけサービスを実行します。この自動バッチ処理により、急激なファイル変更によってサーバーが過負荷になるのを防ぐことができます。
複数の場所を監視する
1つのユニット内で複数のファイルを監視することも可能です。私はよく、複数の設定ファイルを同時に監視するためにこれを利用しています:
[Path]
PathModified=/etc/myapp/config.json
PathModified=/etc/myapp/users.db
Unit=reload-app.service
Unit=ディレクティブを使用することで、異なるPathファイルを単一のサービスに向けることができ、自動化設定を整理された状態に保てます。
本番環境から得た教訓
長年これらのユニットを運用してきた中で、straceとltraceでサイレントバグを追跡するような高度なデバッグ手法とはまた別に、Pathユニット特有のトラブルを避けるための具体的な戦略をいくつか見つけました。
1. Journalctlによるデバッグ
スクリプトが実行されない場合は、JournalctlによるモダンなLinuxログ解析を活用して、Pathとサービスの両方のログを確認してください。以下のコマンドで、連携の様子をリアルタイムで監視できます:
journalctl -u task-runner.path -u task-runner.service -f
2. アトミック・ムーブ(原子的な移動)パターン
監視対象のフォルダにファイルを移動する際は、そのフォルダに直接書き込まないでください。代わりに、/tmpなどの一時的な場所に書き込み、mvコマンドを使って監視ディレクトリに移動させます。Linuxにおいてmvはアトミックな操作であるため、Pathユニットが書き込み途中の破損したファイルに対してトリガーされるのを防ぐことができます。
3. 権限と所有者
Systemdはデフォルトでrootとして実行されます。スクリプトがWebサーバー所有のファイルを変更する必要がある場合は、.serviceファイルでユーザーを指定してください。より詳細な権限設定が必要な場合は、Linux ACLとChattrマスターガイドの活用も検討してください。これにより、ログでは気づきにくい「Permission Denied(権限拒否)」エラーを防ぐことができます。
[Service]
User=www-data
ExecStart=/var/www/scripts/process.sh
4. 無限ループの回避
注意点として、スクリプトが監視対象のファイル自体を変更すると、無限ループが発生します。以前、スクリプトが自身のトリガーファイルのタイムスタンプを更新してしまったために、1時間で10GBのログを生成してしまったことがあります。安全のため、出力やログは必ず別のディレクトリに書き出すようにしてください。
まとめ
Systemd Pathユニットは、従来のCronポーリングに代わる、よりクリーンでモダンな選択肢です。即座に反応し、CPUリソースを節約し、他のSystemdエコシステムと完璧に統合されます。これは、eBPF:Linuxカーネルのオブザーバビリティを実現する高パフォーマンスな手法と同様、効率的なシステム運用のための強力なツールとなります。自動バックアップツールを構築する場合でも、複雑なデータ取り込みシステムを構築する場合でも、これらのユニットはシステム管理者のツールキットに欠かせない要素となるでしょう。

