はじめに
Raspberry Pi 上で IoT アプリケーションを構築しているのですが、その IoT アプリケーションでは、センサーから定期的に温度データを集めて保存しています。
放っておくと、データが溜まり続けてストレージの限界が来るという問題があったので、保持期間を過ぎた古いデータを、定期的に自動削除する仕組みを入れることにしました。
今回は systemd の timer unit を使うことにしたので、それについて書きます。
systemd の timer unit とは
systemd の timer unit は、指定したタイミングで別の unit を起動する役割を持つ systemd の unit です。
多くの場合、起動対象は同じ名前の service unit になります。
timer unit には基本的にスケジュール情報だけを書き、実際に何を実行するのかは対になる service unit に書きます。
- 実行内容とスケジュールを別の unit ファイルで管理することができる
- 「いつやるか」と「何をやるか」を分離できる
- ログが
journalctlに統合される -
systemctl list-timersを使うと次回実行予定や前回の実行結果が一覧できる - 実行が長引いた場合の多重起動防止や、システム停止中に実行タイミングを逃した場合の埋め合わせができる
あたりが嬉しいところです。
実装例
「保持期間を過ぎた古いデータを、定期的に自動削除する」シナリオを例に、service unit と timer unit を書いてみます。
service unit (purge-expired-samplings.service)
[Unit]
Description=Expired sampling data purge
After=network-online.target mysql.service
Requires=mysql.service
[Service]
Type=oneshot
WorkingDirectory=/opt/sample-app
User=sample-app
Group=sample-app
ExecStart=/home/sample-app/.local/bin/uv run python -m db.purge_expired_samplings
Environment=PYTHONUNBUFFERED=1
Type=oneshot は「1 回実行して終了するプロセス」を表す type です。常駐サービスではなく、決まった処理を実行して終わるバッチ処理などに使います。
データを削除するには、DB が起動している必要があるので、Requires=mysql.service で MySQL が起動していることを前提にし、After= で起動順序を制御しています。
ExecStart= に 「保持期間を過ぎた古いデータを、定期的に自動削除する」ロジックを書いたスクリプトの実行を指定します。
timer unit (purge-expired-samplings.timer)
[Unit]
Description=Run expired sampling purge daily
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
[Timer] セクションには、いつ起動するかを表すディレクティブを書きます。
代表的なのは次の2つです。
-
OnCalendar=-
dailyやMon *-*-* 09:00:00のように、日時ベースでスケジュールを指定
-
-
OnBootSec=,OnUnitActiveSec=- 「起動してから何秒後」、「前回の起動から何秒後」というように相対時間でスケジュールを指定
ここでは OnCalendar=daily で毎日 0 時に実行するよう指定しています。OnCalendar= の書き方が合っているかは、実際に unit を配置しなくても systemd-analyze calendar で確認することができます
$ systemd-analyze calendar daily
Original form: daily
Normalized form: *-*-* 00:00:00
Next elapse: Tue 2026-07-07 00:00:00 JST # (実行時点から見て次に発火する日時)
(in UTC): Mon 2026-07-06 15:00:00 UTC # (UTC換算)
From now: 22h left # (次の発火までの残り時間)
$ systemd-analyze calendar weekly
Original form: weekly
Normalized form: Mon *-*-* 00:00:00
Next elapse: Mon 2026-07-13 00:00:00 JST
(in UTC): Sun 2026-07-12 15:00:00 UTC
From now: 6 days left
-
RandomizedDelaySec=- 指定時刻からランダムに最大 N 時間の遅延を加えることができます
- 同一マシンで timer ユニットによる処理が複数重なる場合、負荷が集中する可能性があるので、それを避けるために発火時間をランダムにずらすことができます
-
Persistent=true- マシンの電源が落ちていた等の理由で実行タイミングを逃した場合に、次回起動時に不足分を実行してくれます
- これはだいぶ嬉しいと思います
起動
$ sudo systemctl enable purge-expired-samplings.timer
$ sudo systemctl start purge-expired-samplings.timer
$ systemctl list-timers purge-expired-samplings.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-07-07 00:31:33 JST 22h left - - purge-expired-samplings.timer purge-expired-samplings.service
1 timers listed.
Pass --all to see loaded but inactive timers, too.
参考