0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作の死活監視は、一番困るときに一番静かになる —— ノートPCで24時間監視をやめるまで

0
Last updated at Posted at 2026-09-08

常駐プロセスが落ちたら気づきたい。素直に書くとこうなる。

# 5分ごとに動かす
if not process_is_alive():
    notify("プロセスが落ちています")

これは動く。そして一番困る状況で、一番静かになる。

障害 この監視 デッドマン装置
プロセスだけ落ちた 検知できる 検知できる
マシンがスリープした 検知できない 検知できる
マシンが落ちた・ネットが切れた 検知できない 検知できる

監視スクリプトが同じ機械に載っている以上、機械ごと死んだら監視も死ぬ。
そして監視が死ぬと、通知は来ない。静かになる。

この記事は、その素直な監視をノートPCに24時間載せて失敗し、
実測で稼働率33%だと分かってから設計を直すまでの話。

① 向きを反転する —— 「落ちたら鳴らす」ではなく「生きていたら送る」

デッドマン装置(dead man's switch)にする。

自分 : 正常なときだけ、外部へ ping を送る
外部 : 予定の ping が来なかったら通知する

判定を外に置くのがすべてで、これで「自分が死んだこと」を自分で言わなくてよくなる。

import urllib.request

PING_URL = "https://hc-ping.com/<uuid>"   # healthchecks.io など

def watchdog() -> int:
    if not process_is_alive():
        return 1                      # **異常なら、何もしない。** pingを送らない
    try:
        urllib.request.urlopen(PING_URL, timeout=10)
    except OSError as e:
        # ping が送れないこと自体は自分では解決できない。**黙らない**
        log(f"ping送信に失敗: {e}")
        return 1
    return 0

ここで大事なのは 異常時に「異常です」と送らないこと。
送る仕組みに依存すると、送れない状況(=一番困る状況)で通知が消える。
沈黙そのものを信号にする。

② ノートPCに24時間監視は成立しない(実測)

反転させて満足していたら、今度は通知が止まらなくなった。

原因はマシンが寝ることだった。フタを閉じれば寝るし、AC電源でも寝る。
sleep 0 はアイドルスリープの設定で、クラムシェルスリープには効かない。

5日ぶんを実測した数字がこれ。

実測
ping の稼働率 33%
15分を超える断絶 55件
「監視が動いていない」通知(このままなら) 110通

最初は「ミュートが効かない」問題に見えた。だが調べると、通知は全部正しかった。
監視は本当に止まっていた。バグは通知側ではなく、「24時間動いている」という前提の側にあった。

通知がうるさいとき、まず黙らせ方を探さない。
その通知が正しいなら、直すのは受け手ではなく期待のほう

③ 期待を実態に合わせる —— 監視する窓を絞る

監視に意味がある時間帯だけを窓にする。私の場合、対象プロセスが要るのは
特定の時間帯と、月のうち数日だけだった。

WATCH_START, WATCH_END = 22, 7      # JST 22:00 〜 翌07:00
WATCH_DAYS = range(1, 7)            # **カレンダー日**の 1〜6日

def in_window(now: datetime) -> bool:
    if now.day not in WATCH_DAYS:
        return False
    h = now.hour
    return h >= WATCH_START or h < WATCH_END    # 日付をまたぐ窓

def watchdog(now: datetime) -> int:
    if not in_window(now):
        return 0        # 接続もせず、pingも送らず、**ログも書かない**
    ...

窓の外でログも書かないのは、書くと日に190行たまって、
夜間の記録が押し流されるからだ。監視のログが監視の役に立たなくなる。

これで実行回数が年3.3万回から約20分の1になった。
月中に落ちても構わない。次に困るのは翌月で、その前の窓で捕まる。
失うのは長期の稼働データだけ。

④ ここで踏んだ罠 —— カレンダー日と「セッション日」

窓を日付でも絞るとき、「セッション日」で実装して外した。

対象は夜からの時間帯なので、「7時より前は前日扱い」にしたくなる。
だが cron はカレンダー日で撃つ。 ずれる。

時刻 cron が ping を期待するか セッション日で実装した場合
6日 22:00 する 前日扱いにならず対象外 → 送らない → アラート
1日 00:30 する 前月末扱い → 送らない → アラート

cron 側とコード側で「日」の定義が違うと、必ずどこかで噛み合わない。
撃つ側に合わせる。つまりカレンダー日

送りすぎは無害、送らなすぎだけがアラートになる

デッドマン装置の性質として、余分な ping は害がない(外部のタイマーが延びるだけ)。
一方で ping が1つ足りないと通知が飛ぶ。非対称なので、迷ったら広い側に倒す。

これはテストで固定できる。1年分を総当たりして、
cron が期待する時刻を1つも落としていないことを確かめる。

from datetime import datetime, timedelta

def cron_fires(t: datetime) -> bool:
    """crontab: */5 22,23,0-6 1-6 * *  を素直に書き下したもの。"""
    return (t.day in range(1, 7) and t.minute % 5 == 0
            and (t.hour >= 22 or t.hour <= 6))

def test_窓はcronが撃つ時刻を1つも落とさない():
    t = datetime(2026, 1, 1)
    missed = []
    while t < datetime(2027, 1, 1):
        if cron_fires(t) and not in_window(t):
            missed.append(t)            # cronは撃つのに送らない = アラートになる
        t += timedelta(minutes=5)
    assert missed == [], f"送らない時刻が {len(missed)}件: {missed[:3]}"

逆(窓の中なのに cron が撃たない)は落とさない。 送りすぎは無害だからだ。
テストの非対称性が、設計の非対称性と一致している。

⑤ 監視側と外部側のスケジュールは「対」

窓を変えたら、外部サービス側のスケジュールも同時に変える。
片方だけ変えると、どちらに倒しても悪くなる。

変えたほう 起きること
コードだけ 日中の ping が来ないので、外部が障害と判定して通知が増える
外部だけ 窓の外の ping で「生きている」と誤判定される

healthchecks.io なら Simple スケジュールでは表現できないので Cron にする。

Schedule : */5 22,23,0-6 1-6 * *
Timezone : Asia/Tokyo
Grace    : 20 minutes

この2つが対になっていることは、コード側からはテストできない。
向こう側は外部サービスの設定だからだ。
だから**「片方だけ変えない」をコメントとREADMEに書いて、人間の側で守る。**
テストで守れないものは、守れないと認めて別の手を打つ。

⑥ 監視を止める手段も、監視と対で要る

うるさいときに黙らせる口は要る。ただし必ず期限付きにする。

def muted_until() -> datetime | None:
    """**無期限ミュートを作らない。** 消し忘れると監視が永久に死ぬ。"""
    ...

def notify(msg: str) -> None:
    until = muted_until()
    if until and datetime.now() < until:
        log(f"ミュート中({until:%H:%M}まで): {msg}")   # **捨てずに記録は残す**
        return
    send(msg)

無期限のミュートは、外れていることを教えてくれない安全装置になる。
前に書いた「その安全装置、一度も作動したところを見ていないのでは」
と同じ形だ。

まとめ

  1. 監視は自分の外に置く。「落ちたら鳴らす」ではなく「生きていたら送る」。
    沈黙そのものを信号にする
  2. 異常時に通知を送ろうとしない。 送る仕組みごと死ぬのが一番困る状況
  3. 「24時間動いている」を、寝る機械に前提として載せない。 実測したら33%だった
  4. 通知がうるさいとき、黙らせ方から探さない。 その通知が正しいなら、直すのは期待のほう
  5. cron とコードで「日」の定義を揃える。 撃つ側(カレンダー日)に合わせる
  6. 送りすぎは無害、送らなすぎだけがアラート。 非対称なら広い側に倒し、テストもそう書く
  7. コードと外部設定は対。 テストで守れないものは、守れないと認めて文書と手順で守る
  8. ミュートは必ず期限付き。 無期限は「外れていることを教えてくれない安全装置」

監視を作った日に、一度スリープさせてみるといい。
何も鳴らなかったら、それが答えだ。


note でも書いています

このシリーズ以外に、AIエージェントの運用から広がった政治・経済・メディア構造の話を
note で書いています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?