Skip to content

Fix short circuit operator re teardowns#32538

Merged
dstandish merged 11 commits into
apache:mainfrom
astronomer:fix-short-circuit-operator
Jul 14, 2023
Merged

Fix short circuit operator re teardowns#32538
dstandish merged 11 commits into
apache:mainfrom
astronomer:fix-short-circuit-operator

Conversation

@dstandish

@dstandish dstandish commented Jul 12, 2023

Copy link
Copy Markdown
Contributor

Short circuit operator should by default not skip teardowns for which the object task is in scope.

So for example if you have s1 >> w1 >> w2 >> t1 and t1 is teardown for s1, then if w1 is short circuit, w2 should be skipped but not t1.

A more interesting scenario is

s1 >> op1 >> s2 >> op2 >> [t1, t2]

or

s1 >> op1 >> s2 >> op2 >> t2 >> t1

In both of these cases, if op1 is short circuit (and s1 >> t1, s2 >> t2), then tasks s2, op2, t2 should all be skipped but t1 should not! BUT... importantly, we don't need to do the skipping for t2 in short circuit operator. This will be handled automatically by the scheduler when applying the trigger rules (if the setup was skipped, the teardown will be skipped). Handling it here would be more complexity and duplication of logic than we need. Particularly in weirder examples like s1 >> w1 >> t1, w2 >> t1 where w2 is short circuit.

@vatsrahul1001

Comment thread airflow/operators/python.py Outdated
Comment thread airflow/operators/python.py Outdated
Comment on lines +276 to +278
to_skip = [
x for x in context["task"].get_direct_relatives(upstream=False) if not x.is_teardown
]

@uranusjr uranusjr Jul 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder instead of getting the tasks again (which is surpringly not that trivial), it may make more sense to simply filter from downstream_tasks

to_skip = [t for t in downstream_tasks if self.task_id in t.upstream_task_ids]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when you say not that trivial isn't it just doing dag.task_dict[x] for x in t.upstream_task_ids which is not much different?

@dstandish dstandish Jul 12, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a different way to optimize would to not create the downstream_tasks list at all in the ignore_downstream_trigger_rules is False case. then we could just pass to skip x for x in context["task"].get_direct_relatives(upstream=False) if not x.is_teardown as a generator since skip method can handle that -- i.e. there's not really a need for selectively calling skip.

but stepping back, just to be clear, i'm not adding any new call to either get_flat_relatives or get_direct_relatives -- both calls were there and i don't increase their likelihood of calling or frequency. i just happened to move to a variable cus it was handy to debug something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK @uranusjr i went with that optimization... PTAL

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: i removed a debug log which emitted the full list of short circuited tasks so that way we could use generator but if you think that's no bueno LMK.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok pushed the optimization / refactor a little further here. hopefully i don't break something. lemme know what you think. restored the debug logging, and now it will debug log the actual lists in either case

Comment thread airflow/operators/python.py Outdated
Comment on lines +261 to +263
execution_date = dag_run.execution_date
if TYPE_CHECKING:
assert isinstance(execution_date, DateTime)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can do execution_date = context["logical_date"], that one should be guaranteed to be a pendulum DateTime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logical date is typed as datetime too it seems.

And worse, it does not exist on DagRunPydantic, so mypy complains about that. i'm just keeping the insert

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok actually... at least we can use cast("DateTime", dag_run.execution_date) which is a little cleaner than the assert i suppose


# this let's us avoid an intermediate list unless debug logging
if self.log.getEffectiveLevel() <= logging.DEBUG:
self.log.debug("Downstream task IDs %s", to_skip := list(get_tasks_to_skip()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushing the boundary of the walrus I see…

But should line 274 be removed?

@dstandish dstandish Jul 12, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushing the boundary of the walrus I see…

yes, hehe

But should line 274 be removed?

No because on 274 it is a generator. only if we need to log.debug is there a need to "materialize" it in the form of a list, because then it needs to be logged and passed to skip, which makes another list out of it anyway.

So it's maybe a silly optimization but hey you started it 😛

But so generally it will just be generator here, passed to skip as generator. But if user enables debug logging, then when we log it we render it into a list at that log line, and then pass the same list down to skip.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah OK get_tasks_to_skip() is an iterator. Maybe move the line to below this block instead then?

Personally I’d maybe rename the function or to_skip to be clearer there’s an iterator involved (either iter_tasks_to_skip() or tasks_to_skip_iter)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah OK get_tasks_to_skip() is an iterator. Maybe move the line to below this block instead then?

don't follow -- what would that do?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just going to merge this... if you want to golf it further, you can tag me in review

@dstandish
dstandish requested a review from uranusjr July 12, 2023 18:41
@dstandish dstandish added the AIP-52 Automatic setup and teardown tasks label Jul 12, 2023
@dstandish
dstandish merged commit bb53078 into apache:main Jul 14, 2023
@dstandish
dstandish deleted the fix-short-circuit-operator branch July 14, 2023 00:52
@jedcunningham jedcunningham added the changelog:skip Changes that should be skipped from the changelog (CI, tests, etc..) label Aug 2, 2023
@dstandish dstandish added this to the Airflow 2.7.0 milestone Aug 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AIP-52 Automatic setup and teardown tasks changelog:skip Changes that should be skipped from the changelog (CI, tests, etc..)

Projects

No open projects

Development

Successfully merging this pull request may close these issues.

3 participants