回填的dag运行不会在用户界面中显示

时间:2020-10-12 07:34:09

标签: airflow airflow-scheduler

我有一个项目始于2018年,但我会定期为其添加一些新的DAG。因此,我的所有DAG在2018年都有start_date,每天有schedule_interval,但是catch_up设置为False,因为当我现在添加新的DAG时,我不希望它运行自2018年以来的每一天(就目前而言,也许我将不得不在所有这些天一直运行它)。

但是,在大多数情况下,我希望它在添加日期之前运行几周。 我希望dag在start_dateadded_date(我添加dag的日期)之间运行,在DAG Tree View UI中显示为白色圆圈,因此,我可以手动触发它。最后两个星期。 但是此视图中什么也没有出现...

因此,我手动运行了回填(从命令行... UI中的回填界面会很不错),但是回填执行的所有运行都不会出现在UI中。因此,如果一次回填失败,我仍然无法从用户界面重新运行。

是“未显示start_dateadded_date之间的可能的dag运行。气流的预期行为?有什么办法可以克服这个问题?还是有更好的方法来处理此用例:“添加DAG,并在过去的某些日期手动运行它。”


[编辑]编程dagrun失败

根据Philipp的建议,一种解决方案是打开追赶,并将start_dateadd_date之间的所有运行标记为成功(或失败,无论如何)。

我最终得到了这样的东西:

import datetime

from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.utils.state import State


start_date = datetime.datetime(2020, 10, 1, tzinfo=datetime.timezone.utc)
add_date = datetime.datetime.now(datetime.timezone.utc)

# Define a DAG with just one task
with DAG("test_catchup", schedule_interval="@daily", catchup=True, start_date=start_date) as dag:
    BashOperator(
            task_id="print",
            bash_command="echo {{ ds }}"
            )

    # For all dates between start_date and add_date, create a dagrun and mark it as failed.
    for d in [start_date + datetime.timedelta(n) for n in range(int((add_date - start_date).days))]:
        print("Create dagrun for ", d.isoformat())
        try:
            dag.create_dagrun(run_id="programmatic_fill_{}".format(d.isoformat()), state=State.FAILED, execution_date=d, start_date=add_date, external_trigger=False)
        except Exception as e:
            print("Error:", e)
            pass

如您所见,首先,您必须将dagrun创建嵌套在try-except块中,因为Airflow每次读取此文件时,它将尝试在dagrun数据库中添加相同的条目,并因某些主键而失败冲突。

这大致可行。我所有的dagruns出现:

All dagruns appearing in UI

但是,我不能(重新)运行其中任何一个。 清除一个错误时,出现以下错误:

没有要清除的任务实例

我设法将一个标记为成功(将圆形和正方形变为绿色),然后清除它以将DAG(圆形)变为运行状态,但是将任务变为None状态,然后它永远不会执行...


[编辑]仅最新的运算符

Philipp的另一个好主意开始,我尝试了LatestOnlyOperator

import datetime

from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.utils.state import State
from airflow.operators.latest_only_operator import LatestOnlyOperator


start_date = datetime.datetime(2020, 10, 1)
with DAG("test_latest_only", schedule_interval="@daily", catchup=True, start_date=start_date) as dag:
    LatestOnlyOperator(
            task_id="latest_filter"
    ) >> BashOperator(
            task_id="print",
            bash_command="echo {{ ds }}"
    )

结果(我已经手动重新运行了第一个dagrun):

Latest and arbitrary past dag runs successful

优点:

  • 它实现了我的尝试

缺点:

  • 还需要一个运算符
  • 引导速度很慢。运行我的12个任务大约花了5分钟,而只是停在第一项任务上(我是用它来“回填” 2年的日常工作吗?)
  • 您不能清除DAG,而只能清除LatestOnlyOperator下的第一个任务,否则它将继续阻止下游任务的执行。

最后,在使用catchup=False选项之前,此运算符似乎是一个老技巧,并且由于已经讨论过弃用它,因此我不确定它的可持续性。

1 个答案:

答案 0 :(得分:0)

我终于设法从“ Programmatic DAG run”的想法中得到启发,找到合适的解决方案。

该错误消息指出存在No task instances to clear。 因此,解决方案是在创建DAG运行时创建任务实例。

这是我的工作解决方案:

import datetime

from airflow import DAG
from airflow.models import TaskInstance
from airflow.operators.bash_operator import BashOperator
from airflow.utils.state import State
from airflow.utils.timezone import parse

start_date = parse(datetime.datetime(2020, 11, 1).isoformat())
add_date = datetime.datetime(2020, 11, 8, tzinfo=datetime.timezone.utc)

# Create your dag
with DAG("test_task_instance", schedule_interval="@daily", catchup=False, start_date=start_date) as dag:
    # And its tasks
    BashOperator(
            task_id="print",
            bash_command="echo '{{ ti }}' > /tmp/{{ ds }}"
            )

    # For each expected execution date
    for d in dag.date_range(dag.start_date, end_date=add_date)[:-1]:
        # If no DAG run already exists
        if dag.get_dagrun(d) is None:
            # Create a new one (marking the state as SKIPPED)
            dagrun = dag.create_dagrun(run_id="programmatic_fill_{}".format(d.isoformat()),
                                       state=State.SKIPPED,
                                       execution_date=d,
                                       start_date=add_date,
                                       external_trigger=False)
            # And create task instance for each task for this DAG run
            for t in dag.tasks:
                TaskInstance(task=t, execution_date=dagrun.execution_date)

如您所见,在创建新的try...except块之前,我们没有要求。 SKIPPED状态似乎更适合于这些DAG运行,因为实际上没有任何运行。

然后,在气流UI中查看DAG时。

All dags are marked as failed, tasks have no state

最后,您可以清除并运行调度程序来挑选要运行的dag。

Scheduler queued tasks

Selected DAGs were successfully runned


编辑:

我编辑了下面的代码,该代码取代了调度程序,在调度程序创建它们之前创建了“赶上”的dags。

当前的解决方案(不是我认为最好的解决方案)是删除最后一个预期的执行日期。

- for d in dag.date_range(dag.start_date, end_date=add_date):
+ for d in dag.date_range(dag.start_date, end_date=add_date)[:-1]: