HOW TO TD(User Engagement)Treasure Data User Engagement

Workflowの複雑なスケジュール設定

ホーム » Workflowの複雑なスケジュール設定

テクニカルサポートエンジニアリングチームの伊藤 一樹です。
今回は、複雑なスケジュール設定をWorkflowで実現する方法について説明します。サンプルWorkflowも掲載しますが、参考にされる場合は動作確認の上お使いください。

Workflowのスケジュール設定概要

Workflowは基本的には定期的に実行することを目的で利用されていることが多く、1度だけの処理のために実装することは少ないでしょう。処理内容によって、日次、週次、月次など様々な間隔で実行する要件があるかと思いますが、基本的には ドキュメントにあるこちらの方法で十分かと思います。

記法 説明
hourly>: MM:SS MM:SSに毎事実行
daily>: HH:MM:SS HH:MM:SSに毎日実行
weekly>: DDD,HH:MM:SS 毎週曜日がDDDのHH:MM:SSに実行
monthly>: D,HH:MM:SS 毎月D日のHH:MM:SSに実行
minutes_interval>: M 毎M分ごとに実行

複雑なスケジュール設定をするには

複雑なスケジュール設定する場合は先述した方法では十分ではないため、下記にあるcron>:を利用する必要があります。

timezone: Asia/Tokyo

schedule:
  cron>: 42 4 1 * *


cronはTreasure Data CDP固有の考え方・方法ではありません。UnixやLinuxで定期実行のスケジュール管理するためのcrontabコマンドによって利用できる機能のため、cronやcrontabでWeb検索いただくと書き方やサンプルなどを見つけることができるでしょう。日本の方が多いと思うので、本記事はスケジュールに関してタイムゾーンはAsia/Tokyo、すなわち+09:00とさせていただきます。Workflow(digファイル)にtimezone: Asia/Tokyoと記載することでそのようにして取り扱われます。

cronについて

本記事でもcronについて簡単に説明をさせていただきます。まず基本的な書き方ですが、5つの数値を半角スペースで区切って記法します。その5つの数字は左から順に、分、時、日、月、曜日を表します。記載できる数値は下記の通りで、覚えづらい部分としては一番右側の曜日部分でしょう。0から7の数字を記載することで曜日を表すのですが、0と7は日曜日となり、1は月曜日、2は火曜日、・・・ということになります。曜日の最初の三文字(SUN, MON, ….)を利用することもできるので、こちらのほうが可読性が高いかもしれませんが、後述のリスト・範囲といった記法には利用できないので、要件に合わせてどちらで記載するか決めてください。

項目 数値
0から59
0から23
1から31
1から12
曜日 0から7

また、数値以外では*を指定することができ、これは特定の時刻ではなく毎回を意味しします。例えば1つめが*なのであれば毎分実行するスケジュールということになります。そのため、先述したcron>: 42 4 1 * *であれば、毎月1日の04:42に実行するスケジュールとなります。また、記法として下記2通りの方法が利用できます。これらを組み合わせることでdaily>:などでは実現できなかった複雑なスケジュール実行が可能になるでしょう。

記法 設定例 説明
リスト cron>: 0,10,20 * * * * 毎時 0分、10分、20分に実行
範囲 cron>: 0 0 1 1-5 * 1から5月の間、1日の00:00に実行

サンプル

では、ここからはcron>:を用いた具体的なユースケースとサンプルを作っていきます。cron>:は先述した通り他の記法と比較して複雑なスケジュール設定が可能ですが、後述するような複雑なスケジュール設定はcron>:だけでは実現できません。そのため、cron>:で最低限のスケジュール設定を実装しつつ、後続の処理で条件分岐するという対応が必要になります。

毎月最初の月曜日に実行

まず、毎月最初の月曜日の13:00に実行するユースケースを考えてみます。

cronとしては「毎月最初の」という決まった記法があるわけではないため、何かしらのロジックを考えて実装する必要があります。 曜日は7通りあり、かつ該当月で最初ということは1日から7日の間の何れかの日付となるのは確実なので、日が1〜7で月曜日という条件で実装できそうです。ここまでの情報では下記で実現できそうだと考えるかと思います。

## NGの例
schedule:
  cron>: 0 13 1-7 * 1


ですが残念ながら上記は期待したスケジュール設定とはなりません。下記のようにman 5 crontabコマンドでcronについて確認するとわかるのですが、日と曜日がどちらも設定されている場合はANDではなくORで判定されるという仕様になっています。 そのため、1日〜7日の13時と、全ての月曜日の13時に実行されてしまいます。

Note: The day of a command's execution can be specified by two fields -- day of month, and day of week. If both fields are restricted (ie, are not *), the command will be run when either field matches the current time. For example, ``30 4 1,15 * 5'' would cause a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday.


ではどうするのかというと、例えば月曜日の13時には毎回Workflowは実行するようにしておき、最初の処理で月の最初の月曜日かどうか判断させるという方法が考えられます。

具体的にはif>:オペレータを利用して、実行予定日の日付を取得し1〜7日の場合はtrueを返すように条件を記載すれば良いでしょう。 方法はいくつもありますが、Workflowの${...}の中では簡単なJava Scriptsであれば動作しMoment.jsがバンドルされているので、ビルトイン変数session_timeから日を取得して比較演算子>=と<=で大小判定されるなどが考えられます。

timezone: Asia/Tokyo

schedule:
  cron>: 0 13 * * 1

+check_day:
  if>: ${(moment(session_time).format('DD')>=1) && (moment(session_time).format('DD')<=7)}
  _do:
    +echo_sunday:
      # 最初の月曜日のときの処理
      echo>: Today is 1st Monday!!!
  _else_do:
    +echo_others:
      # 最初の月曜日以外の処理(echo>:でログに出力しておく程度で大丈夫でしょう)
      echo>: Nothing else... 


後は、最初の月曜日の場合とそうでない場合で処理をそれぞれ記載すれば完成です。

平日のみ(土日祝日は処理しない)

たまにお問い合わせを頂戴するのですが、平日のみ(土日祝日は処理させない)処理したいというユースケースを考えます。2021年のケースで考えるので祝日は下記に記載されているものを想定します。

https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html

土日に実行させないという点だけであればcron>: 0 13 * * 1-5で良いのですが、考えなければいけないのは祝日判定部分です。 残念ながらTreasure Data CDPでは祝日かどうか判定する仕組みもなければ祝日リストを保持しているわけではないので、何かしらの方法で祝日リストを格納しておき、月〜金曜日に実行した直後に祝日かどうかチェックする必要があります。具体的には、テーブルに事前に格納しておくか、下記サンプルのようにクエリ内に祝日リストを入れておくことで実現できるでしょう。

Workflowのビルトイン変数${session_date}とクエリ内に内包したholidayカラムをIN句で比較することで、実行予定日が祝日であればtrueが、そうでなければfalseが返ります。

WITH holiday_list AS (
SELECT holiday
  FROM (VALUES
  ('2021-01-01','元日'),
  ('2021-01-11','成人の日'),
  ('2021-02-11','建国記念の日'),
  ('2021-02-23','天皇誕生日'),
  ('2021-03-20','春分の日'),
  ('2021-04-29','昭和の日'),
  ('2021-05-03','憲法記念日'),
  ('2021-05-04','みどりの日'),
  ('2021-05-05','こどもの日'),
  ('2021-07-22','海の日'),
  ('2021-07-23','スポーツの日'),
  ('2021-08-08','山の日'),
  ('2021-08-09','休日'),
  ('2021-09-20','敬老の日'),
  ('2021-09-23','秋分の日'),
  ('2021-11-03','文化の日'),
  ('2021-11-23','勤労感謝の日')
  ) AS t(holiday, holiday_desc)
)
SELECT '${session_date}' IN (SELECT holiday
                               FROM holiday_list                            
                            ) AS result_holiday


このクエリの結果を後続のタスクで利用すれば、祝日かどうかの条件分岐が可能になります。 Workflow(digファイル)のサンプルとしては下記のようになります。

timezone: Asia/Tokyo

schedule:
  cron>: 0 13 * * 1-5

_export:
  td:
    database: kazzy_test

+check_holiday:
  td>: check_holiday.sql
  store_last_results: true

+judge_holiday:
  if>: ${td.last_results.result_holiday}
  _do:
    echo>: Today is holiday
  _else_do:
    echo>: Today is business day


下記のような流れで実装するサンプルです。

  • 月〜金曜日に実行するよう schedule: で設定
  • td>: オペレータで祝日のときはtrue、そうでないときはfalseを返すクエリを実行
  • if>: オペレータでクエリ結果がtrueのときとそうでないときの処理を分岐

隔週の月曜日実行

今度は隔週で実行するユースケースになります。 曜日は月曜日、時刻は13:00で考えてみます。週番号(その年で第n週なのか)を2で割った余りで判定させてもそれらしく動作するかと思いますが、年の切り替わりなどを考慮すると、ある基準日時(月曜日)から何日経過したかを算出し、14で割った余りが0の場合に処理させるなどで十分ではないかと思います。

時刻と曜日は cron>: で指定しておき、Moment.jsにて実行予定日時(session_time変数)と基準日(下記例では 2021-01-04)との差分を算出し、14で割った余りが0かどうかを if>: オペレータで判定するのが良いでしょう。下記サンプルになりますが、実行日を1週ずらしたい場合は基準日をずらすか14で割った余りが7かどうかで判定すれば良いかと思います。

timezone: Asia/Tokyo

schedule:
  cron>: 0 13 * * 1

+check_biweekly:
  if>: ${moment(session_time).diff(moment('2021-01-04 00:00:00'), 'days')%14==0}
  _do:
    echo>: Today is day for processing!

月末のみ実行

こちらは月末のみ実行するユースケースです。月初であれば当然1日にのみ実行するようにしていただければ良いのですが、月末の場合は月ごとに異なるということと、うるう年を考慮しなければいけません。いくつか方法はあるのではないかと思いますが、今回は毎月28〜31日に実行させ、翌日が1日かどうかで条件分岐するという方法を紹介します。

今回のユースケースでもMoment.jsを使います。実行予定日時はビルトイン変数のsession_timeを利用して、Moment.jsのadd関数にて1日追加、format関数で日部分のみを抽出し、Java Scriptsの比較演算子で比較しています。

timezone: Asia/Tokyo

schedule:
  cron>: 0 13 28-31 * *

+check_lastday:
  if>: ${moment(session_time).add(1, 'day').format('DD')=='01'}
  _do:
    echo>: Today is last day of this month.

第2月曜日と毎週水曜日のような複数スケジュール

最後に、第2月曜日と毎週水曜日の13時に実行させたいというユースケースについて考えてみます。1つのWorkflowで頑張って実装しても良いのですが、せっかくなので複数Workflow(digファイル)を用いる方法を使ってみましょう。まず、下記2つのWorkflowを作成します。前者は毎月最初の月曜日に実行のケースを参考にし、後者はcron>: 0 13 * * 3で良いでしょう。

  • 第2月曜日の13時に実行するWorkflow
  • 毎週水曜日の13時に実行するWorkflow

そして上記2つのWorkflowからcall>:オペレータを利用して、実施したい処理が記載されているWorkflowを呼び出します。そうすることで、1つのWorkflowで複雑な条件分岐を書かずとも簡単に実装することができます。

timezone: Asia/Tokyo

schedule:
  cron>: 0 13 * * 3

+call_main:
  call>: main.dig


timezone: Asia/Tokyo

schedule:
  cron>: 0 13 * * 1

+check_day:
  if>: ${(moment(session_time).format('DD')>=8) && (moment(session_time).format('DD')<=14)}
  _do:
    +call_main:
      call_task>: main.dig

さいごに

いくつかサンプルを紹介させていただきましたが、いかがでしたでしょうか?

内容を見ていただいたら感じていただけたのかと思うのですが、特別な方法があるわけではなく、要件を満たすようなロジックを考える部分が肝になっています。 そのため、まずロジックを考えていただき、それから実装方法を検討すると良いでしょう。

本記事がそのロジック及び実装方法の参考となると嬉しいです。

伊藤 一樹

Technical Support Engineeringチーム

大学院卒業後2012年に日本オラクル株式会社に入社。ITコンサルタントとしてOracle Databaseの設計から運用、チューニング、性能アセスメントなどに従事。また、顧客のDBAチームの立ち上げやスキルトランスファー、短いダウンタイムでのデータベース移行、テストツール・オプションに注力し、コンサルタントのナレッジの訴求のため外部向けの講演なども経験。縁あって2019年2月にトレジャーデータに入社。多くの問い合わせを解決することを喫緊の至上命題とし、最も多くの問い合わせ対応を行っている。

得意領域 : データベース、SQL/付随するアーキテクチャ、トラブルシューティング

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

Back to top button