見出し画像

繰り返しイベントを扱うデータ構造

🎄この記事は HRBrainアドベントカレンダー2023 6日目の記事です🎄

「毎月第二月曜日の9時にメールを送信する」というような定期的に実行される処理を管理するためのデータ構造について検討した内容をまとめてみました。

cron

単に定期実行したい場合に真っ先に思いつくのはcron(及びCloud Schedulerなどそれに類するもの)だと思います。

通常のcrontabの指定では毎月5日というような指定しかできず、毎月第二月曜日というようなことは表現できませんが、以下のようにシェルスクリプトと組み合わせることで実現可能です。

0	9	8-14	*	*		test $( date '+\%w' ) -eq 1 && echo "second monday" > example.txt

社内システムや全ユーザー共通のタイミングの処理であればこの方法でも問題ないかもしれませんが、

ユーザーが自分で指定したタイミングで実行したいとなった場合はやはり何かしらの方法で繰り返しの頻度を永続化したくなります。

データ構造

まず保存したい情報を整理すると

  • 実行したい処理はなにか

  • どういう頻度で実行するか

  • 実行する時間

です。

これをRDBで表現すると

eventテーブル

というようなテーブルがあるとして

event_scheduleテーブル

のように定義できます。

last_executed_atについてはevent_executed_historyのようなテーブルを作成して、

そこから算出するというやり方もあります。

このデータ構造で「毎月第二月曜日9時」を表現すると


となります。

また「隔週火曜日17時」は

です。

判定ロジック

実際に処理を実行するかどうか判定するロジックは以下のような疑似コードで、この関数を1時間に1回走らせてチェックします。

type EventSchedule = {
  frequency: 'monthly' | 'weekly',
  interval: number,
  dayOfWeek: number,
  ordinalNumberOfWeek: number | null,
  hour: number,
  lastExecutedAt: Date | null
}

function shouldExecuteEvent(eventSchedule: EventSchedule, executionDate: Date): boolean {
  const { frequency, interval, dayOfWeek, ordinalNumberOfWeek, hour, lastExecutedAt } = eventSchedule;

  // 曜日
  if (dayOfWeek !== executionDate.getDay()) {
    return false
  }

  // 時間
  if (hour !== executionDate.getHours()) {
    return false
  }

  // 第何週
  if (frequency === 'monthly' && ordinalNumberOfWeek) {
    if (ordinalNumberOfWeek === 1) {
      if (executionDate.getDate() > 7) {
        return false;
      }
    } else if (ordinalNumberOfWeek === 2) {
      if (executionDate.getDate() <= 7 || executionDate.getDate() > 14) {
        return false;
      }
    }
    // ...以下4週まで続く
    // まとめて
    // if(Math.ceil(executionDate.getDate() / 7) !== ordinalNumberOfWeek) ともかける
  }

  // 実行間隔
  if (lastExecutedAt) {
    const nextExecutedDate = frequency === 'monthly' ? addMonths(lastExecutedAt, interval) : addWeeks(lastExecutedAt, interval);
    if (executionDate < nextExecutedDate) {
      return false;
    }
  }

  return false;
}

毎時間チェックするのを避けたければ、

次回の実行タイミングを計算してそれをデータとして持っておくというアプローチもあります。

補足

今回は週次または月次の曜日指定という前提でしたが、発展として

  • 日付指定(毎月10日)

  • ものによっては定期実行ではなく1回だけ実行したい

  • 繰り返しの開始日、終了日を指定したい

などの要求が増えることも考えられます。

また、カレンダーに繰り返しの予定を登録したいようなケースでは、そもそも繰り返しのルールをDBに持たずに、ルールによって予定される今後のイベントをすべて登録してしまうという方法もあります。

この場合はルールの変更時に今後の予定も合わせて変更するのかどうかや、無限に今後の予定を登録するわけにはいかないのでどこまで設定するかなどが考慮すべき事項となります。

参考


この記事が気に入ったらサポートをしてみませんか?