NhoSW opened a new issue, #76738:
URL: https://github.com/apache/airflow/issues/76738

   ### Apache Airflow version
   
   3.3.1
   
   ### What happened?
   
   When two or more task instances report success at the same moment and each 
emits an `AssetAlias` event for the **same asset that is not yet linked to that 
alias**, only one of them succeeds. The others are marked `FAILED` by the 
Execution API, without going through `up_for_retry`, although the task itself 
finished fine.
   
   This happens in two cases:
   
   1. The asset does not exist yet. Both requests go down the `event is None` 
branch of `TaskInstance.register_asset_changes_in_db` 
(`session.add(AssetModel.from_serialized(asset))` + `flush`). The loser fails 
with
      `UniqueViolation: duplicate key value violates unique constraint 
"idx_asset_name_uri_unique"`.
   2. The asset already exists (it is active because another DAG declares it as 
an inlet), but the alias is not linked to it yet. Both requests run 
`AssetManager._add_asset_alias_association`, which reads `asset_model.aliases` 
and then appends the alias. The loser fails with
      `UniqueViolation: duplicate key value violates unique constraint 
"asset_alias_asset_pkey"`.
   
   In both cases the `IntegrityError` is caught by the catch-all `except 
Exception` in `ti_update_state` (`execution_api/routes/task_instances.py`). It 
rolls back and sets the TI to `FAILED`. Retries configured on the task are not 
used, and the asset event for the losing TI is lost.
   
   Pre-creating the asset does not help, because case 2 still races on the 
alias association row.
   
   ### What you think should happen instead?
   
   Concurrent first emissions for the same asset/alias should all succeed and 
each produce an asset event. For example, the asset insert and the alias 
association insert could tolerate a concurrent insert (`INSERT ... ON CONFLICT 
DO NOTHING`, or catch the `IntegrityError` in a savepoint and re-read). At 
minimum, a failure while registering asset changes should not turn a successful 
task into a non-retryable `FAILED`.
   
   ### How to reproduce
   
   PostgreSQL metadata DB, `LocalExecutor` or `CeleryExecutor`. All tasks below 
finish at the same wall-clock second.
   
   ```python
   import time
   from datetime import datetime, timedelta
   
   import pendulum
   from airflow.sdk import DAG, Asset, AssetAlias, BaseOperator
   from airflow.providers.standard.operators.empty import EmptyOperator
   
   ALIAS = AssetAlias("race_alias")
   BARRIER = timedelta(minutes=2)
   
   
   class Writer(BaseOperator):
       def __init__(self, asset_name=None, **kwargs):
           super().__init__(outlets=[ALIAS], **kwargs)
           self.asset_name = asset_name
   
       def execute(self, context):
           at = pendulum.instance(context["dag_run"].start_date) + BARRIER
           time.sleep(max(0, (at - pendulum.now("UTC")).total_seconds()))
           name = self.asset_name or "new_" + "".join(c if c.isalnum() else "_" 
for c in context["run_id"])
           context["outlet_events"][ALIAS].add(Asset(name, 
uri=f"s3://bucket/{name}"))
   
   
   with DAG("asset_alias_race", schedule=None, start_date=datetime(2026, 1, 1), 
max_active_tasks=16,
            default_args={"retries": 3}):
       # case 2: the asset row exists (active via inlet), the alias link does 
not
       EmptyOperator(task_id="declare", inlets=[Asset("existing", 
uri="s3://bucket/existing")])
       for i in range(4):
           Writer(task_id=f"new_{i}")  # case 1: a new asset per run
           Writer(task_id=f"existing_{i}", asset_name="existing")  # case 2 
(first run only)
   ```
   
   The DAG above is a trimmed-down version of the one we ran (same barrier 
approach, more tasks). Observed on a single run, with every writer set to 
`retries=3`:
   
   | case | TIs reporting success together | success | FAILED (try 1, no retry) 
| error |
   |---|---|---|---|---|
   | asset missing | 2 | 1 | 1 | `idx_asset_name_uri_unique` |
   | asset exists, alias not linked | 4 | 1 | 3 | `asset_alias_asset_pkey` |
   
   Each asset ended up with exactly one event.
   
   ### Operating System
   
   Debian (official `apache/airflow:3.3.1-python3.11` based image) on Kubernetes
   
   ### Deployment
   
   Official Apache Airflow Helm Chart
   
   ### Anything else?
   
   It only happens on the first emission of an asset through an alias. That is 
common when the asset name is only known at runtime, for example a write task 
that discovers which tables it wrote. Several such tasks writing the same new 
table at about the same time is enough to hit it.
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's Code of Conduct
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to