恥ずかしながら最近になって知ったワークフローエンジン Apache Airflow。日本語の紹介記事もちらほら出てきていますが、公式ドキュメントをちょっとずつ抄訳しながら読んでいこうと思います。
15回目の今回はタイムゾーン(Time Zones)。
バージョン2.3.3時点のものです。
タイムゾーン(Time Zones)
タイムゾーン・サポートはデフォルトで有効です。Airflowは日時情報を内部的にUTCで管理します。これによりタイムゾーンに依存したDAG実行が可能となります。今のところ、Airflowは日時情報をUI上でエンドユーザーのタイムゾーンへと変換することはなく、いつでもUTCで表示されます〔註:古い解説?実際とは異なる。少しあとのサブセクションで解説あり〕。オペレーター内で使用されるテンプレートも同様に変換を行いません。タイムゾーン情報をどう利用するかはDAG作成者が決めます。
あなたが運用するAirflowのユーザーが複数のタイムゾーンに属しており、各ユーザーの現地時刻に即して日時情報を表示したいという場合に便利です。
Airflowが単一のタイムゾーンで利用されているとしても、日時情報をUTCで管理するのはよいプラクティスです(Airflowがタイムゾーンに対応するまではこれは推奨ないし必須の設定でもありました)。主な理由は多くの国地域で夏時間(DST)を利用しており、現地時刻は春には前進し秋には後退するためです。ローカル時間で物事を考えていると、この時刻の遷移により、たぶん年に2回エラーに見舞われるわけです。(この件については、Airflowが利用する日時情報を扱うためのライブラリpendulumとpytzのドキュメントにより詳細な議論が掲載されています。)シンプルなDAGでは問題にならないかもしれませんが、例えば一日の終りの時刻を厳守しなくてはならない金融業界のシステムでは問題となるでしょう。
タイムゾーンはairflow.cfg
で設定します。デフォルトではUTCが指定されていますが、システム時刻のタイムゾーンや任意のタイムゾーン(例えば Europe/Amsterdam
)を指定することもできます。Airflowのこの仕組はpendulum
に依拠しています。このライブラリはpytz
よりも正確に働きます。pendulum
はAirflowとともにインストールされます。
Web UI
デフォルトではWeb UIは日時をUTCで表示します。時刻表示は上方右側のメニュー(時計をクリックすると表示される)から変更できます:
「Local」はブラウザのタイムゾーンから判断されます。「Server」はAirflowの設定ファイルの[core]
セクションで設定されたdefault_timezone
に基づくものです。
ユーザーが選択したタイムゾーンはブラウザのLocalStorageに保存されます。したがってブラウザごとの設定になります。
Note
Airflowシステム全体のデフォルト・タイムゾーンの設定を変更し、かつ同じタイムゾーンでUIも利用したいという場合、設定ファイルの[webserver]
セクション内にあるdefault_ui_timezone
を空文字列にするか、あるいは同じタイムゾーン名を記述してください。
(UIの動作に関してポイント・リリース間で整合性を持たせるため、現時点ではデフォルトはUTCになっています。)
概念
awareな日時とnaiveな日時
Pythonのdatetime.datetime
オブジェクトはtzinfo
属性を持っています。これはdatetime.tzinfo
のサブクラスのインスタンスにより表現されるタイムゾーン情報を保持するための属性です。この属性に値が設定されUTCからの時刻のオフセットが示されているとき、当該のdatetime
オブジェクトは aware(アウェア)であると言い、そうでない場合はnaive(ナイーブ)であると言います。
日時オブジェクトがいずれであるかは timezone.is_localized()
や timezone.is_naive()
を使って確認できます。
Airflowはタイムゾーンを考慮した日時(awareな日時)を利用します。Airflowに関連するPythonコードで日時オブジェクトを作るときはタイムゾーンを考慮した日時である必要があります。
from airflow.utils import timezone
now = timezone.utcnow()
a_date = timezone.datetime(2017, 1, 1)
naive日時の解釈
Airflowはタイムゾーンを考慮した日時(awareな日時)を使って処理をしますが、DAG定義のstart_dates
やend_dates
にタイムゾーンを考慮しない日時(naiveな日時)を指定することも可能です。これは後方互換性のために残されているものです。タイムゾーンを考慮しない日時を使用した場合、デフォルトのタイムゾーンにおける日時として解釈されます。例えば、デフォルト・タイムゾーン設定がEurope/Amsterdam
である場合、 start_date
にタイムゾーンを考慮しない日時 datetime(2017, 1, 1)
を指定すると、これは「アムステルダム現地時刻における2017年1月1日」とみなされます。
dag = DAG(
"my_dag",
start_date=pendulum.datetime(2017, 1, 1, tz="UTC"),
default_args={"retries": 3},
)
op = BashOperator(task_id="dummy", bash_command="Hello World!", dag=dag)
print(op.retries) # 3
生憎、夏時間(DST)の遷移中はある日時が存在しなかったり、存在はしても曖昧であったりします。このような状況下ではpendulum
は例外を発生させます。可能なときはいつでもタイムゾーンを考慮した日時オブジェクトを使用すべきです。
実際のところこの制約が問題になることはほとんどありません。AirflowはDAGその他のコード内でタイムゾーンを考慮した日時オブジェクトを提供しており、ほとんどの場合、それらの日時オブジェクトをもとにて時刻差演算をすることで新しい日時オブジェクトが作成されます。それらのオブジェクトはあらかじめオフセットを持ちます。そうではなく一から日時オブジェクトを作成するのはふつう現在時刻を求めるためであり、そのために利用されるtimezone.utcnow()
はタイムゾーンを考慮したオブジェクトを生成します。
デフォルト・タイムゾーン
デフォルト・タイムゾーンは設定ファイルの[core]
セクションにあるdefault_timezone
で指定します。インストール直後はutc
が設定されておりこれが推奨値です。システム時刻のタイムゾーンを示すsystem
や任意のタイムゾーン名(例えば、Europe/Amsterdam
)も指定できます。DAGとその時刻情報はAirflowのワーカー群からアクセスされるので、すべてのノードでこの設定を同期しておく必要があります。
[core]
default_timezone = utc
Note
Airflowの設定について詳しくは設定オプションを参照ください。
タイムゾーンを考慮したDAG
タイムゾーンを考慮したDAGを作るのは難しいことではありません。pendulum
のAPIを利用してstart_date
の値を指定するだけです。標準ライブラリのtimezoneを使用しないでください。このライブラリには既知の制約があるので、DAGの中では使用できないようにしてあります。
import pendulum
dag = DAG("my_tz_dag", start_date=pendulum.datetime(2016, 1, 1, tz="Europe/Amsterdam"))
op = EmptyOperator(task_id="empty", dag=dag)
print(dag.timezone) # <Timezone [Europe/Amsterdam]>
タスクにstart_date
とend_date
は指定できますが、データ区間(Data Interval)の計算には常にDAGのタイムゾーン設定もしくはグローバルなタイムゾーン設定(前者の方が優先)が使用される点に注意をしてください。start_date
とend_date
はまずそれらに関連付けられたタイムゾーン情報を使ってUTCに変換され、その後の処理ではこれらのタイムゾーン情報は無視されます。
###テンプレート
Airflowはテンプレートの中でもタイムゾーンを考慮した日時オブジェクトを返します。それらはローカル時刻に変換されません。その時刻情報をどのように処理するかはDAGに任されます。
import pendulum
local_tz = pendulum.timezone("Europe/Amsterdam")
local_tz.convert(logical_date)
cronスケジュール
cronスケジュールを使用するタイムゾーンを考慮したDAGは夏時間(DST)を踏まえて動作します。例えば、US/Eastern
タイムゾーンで0 0 * * *
というスケジュールで開始日時が指定されているDAGは、夏時間の期間中、UTC時刻で毎日04:00に実行される一方、夏時間の期間外にはUTC時刻で毎日05:00に実行されます。
###時差
timedelta
もしくはrelativedelta
を使用するタイムゾーンを考慮したDAGは、開始日時に関して夏時間(DST)を踏まえて動作します。後続の実行スケジュールを決めるにあたって夏時間の調整を行いません。例えばstart_date
がpendulum.datetime(2020, 1, 1, tz="UTC")
で schedule_interval
がtimedelta(days=1)
であるとき、当該DAGは夏時間を顧慮せずUTC時刻で毎日05:00に実行されます。
日本国内で利用する場合、(今のところ)夏時間はあまり気にしなくてよいでしょうが、そもそものUTCとタイムゾーンの部分はきちんとユーザーに理解してもらう必要がありそうです。