予定はもう保存しなくていい——予実テーブルの追悼

Kairos

あのテーブルを作ったことがある人へ

ジョブスケジューラの機能を作ったことがある人は、たぶんあのテーブルを持っている。名前は現場によって違う——スケジュールインスタンステーブル、実行予定表、コマンドリスト。中身は同じで、予定を先に展開して保存したものだ。毎晩のバッチが 3 ヶ月先まで予定行を生成し、期限が近づくとまた伸ばす。実行が済んだら消し込む。照合クエリが「予定にあって実績にない行」を探す。そして誰かが最初に決めたのだ——このテーブルは、未来永劫、記録し続けると。

なぜあれが要ったのか。cron 式は「次にいつ走るか」しか教えてくれないからだ。「あの日の予定は何だったか」を後から聞くには、予定を先に物質化して保存しておくしかなかった。予定は状態だった。だから設備が要った。

前回、止まっていた 36 時間は「ただの評価範囲である」と書いた。今回はその続きで、実装の側から同じことを言う——スケジュールが定義なら、予定側の保存はまるごと要らない

予定は保存するものではなく、導出するものになる

定義は、同じ範囲・同じデータで評価すれば必ず同じ答えを返す。つまり「あの日の予定」はいつでも決定的に再計算できる——保存しておく理由がない。永続化が要るのは実績ログだけで、それも監査に必要な期間だけローテートで持てばいい。予実照合は、こうなる:

照合 = 定義の評価 × 実績ログの grep

実演する。支払日ルール(5・15・25 日、祝日は除く)で、祝日データは external 宣言——式は静的・データは実行時解決の差し込み口だ:

premise HRDB {
  calendar-system: Gregorian
  tz: "Asia/Tokyo"
  wkst: Mon
  source: "hr-db/holidays"
  holidays = external(kind: dates)
}
@HRDB
gotobi = everyDay |> filter(d => dayNo(d) == 5 or dayNo(d) == 15 or dayNo(d) == 25)
gotobi \ holidays

祝日データは JSON で手元に置く(人事 DB から吐いたと思ってほしい):

{
  "holidays": {
    "dates": ["2026-01-01", "2026-01-12", "2026-02-11", "2026-02-23", "2026-03-20"],
    "covering": "2026-01-01..2026-03-31",
    "asof": "2026-01-05"
  }
}

CLI に両方を渡すと、第 1 四半期の「予定」がその場で導出される:

$ node src/cli.ts list payments.kairos --from 2026-01-01 --to 2026-04-01 --supply supply.json
2026-01-05
2026-01-15
2026-01-25
2026-02-05
2026-02-15
2026-02-25
2026-03-05
2026-03-15
2026-03-25
# 被覆サマリ
#   HRDB.holidays covering 2026-01-01..2026-03-31 asof 2026-01-05 残走路 0 日

▶ Playground で規則部を開く (供給データの注入は CLI だけの機能にしてある。手元の業務データをブラウザに送らない——式がブラウザの外に出ない Playground と、同じ思想の裏返しだ)

あとは実績ログと突き合わせるだけ。3 月 15 日の支払いが実行されていなかったとすると:

$ node src/cli.ts list payments.kairos --from 2026-01-01 --to 2026-04-01 \
    --supply supply.json | grep -v '^#' > planned.txt
$ comm -23 planned.txt actual.txt
2026-03-15

これが照合器の全実装である。 予定行の生成バッチも、期限延長も、消し込みも、専用テーブルもない。planned.txt は使い捨てで、消しても同じものがいつでも出てくる。

検査まで言語側に落ちる

もうひとつ、あのテーブルの周りには検査コードが住んでいた。取り込んだ祝日データは日付として正しいか、期間の主張と中身が食い違っていないか。external の解決値には、テーブルリテラルと同一の統治検査が契約として課される——たとえば供給パイプラインのバグで 2026-02-30 が 混入すると:

契約違反: 実在しない日付——external HRDB.holidays: 2026-02-30(黙ったロールオーバーの封止=ADR-43 の字句検査の解決値向け再執行。ADR-46)

3 月 2 日に読み替えられたりせず、言語境界で止まる。照合スクリプトに検査を写経する必要もない。そして出力の最後には毎回、被覆サマリが付いてくる——祝日データはいつ観測した版で、あと何日ぶんの主張が残っているか(第 5 弾に書いた「データの鮮度」だ)。予定の導出と、データの検査と、鮮度の申告が、全部同じ一回の評価から出てくる。

削除できる設備は、最高の設備である

まとめると、こうなる。

従来定義ベース
予定を N ヶ月ぶん展開して保存保存しない(いつでも導出)
生成バッチ・期限延長・バックフィルない
照合クエリ+取り込み検査コード評価 × grep(検査は言語の契約)
予実テーブルは未来永劫成長実績ログだけ・ローテートで消える

この連載を読んで「なんであの時これがなかったのか」と思った人がいたら、その苦さは正しい。私も同じものを作って、同じ照合クエリを書いて、同じテーブルを未来永劫伸ばしてきた。あれは当時の正解だった——予定が状態でしかあり得なかった時代の。

今からは、ある。予定は定義から導出するものになり、あのテーブルには墓標を立てていい。作らなくていい設備は、最高の設備である。

コメント

タイトルとURLをコピーしました