ataulmujeeb-cyber opened a new issue, #76979:
URL: https://github.com/apache/airflow/issues/76979

   Changing a partitioned consumer from `A` to `A & B` can leave it without 
usable input for a partition even when both assets have published that 
partition. Republishing B currently queues work for other B consumers too. An 
explicit recovery operation should allow queueing an event only for the 
affected consumer and partition.
   
   There are two distinct cases to account for. The source behavior below is 
from main at `29331a8325698266e380a88e6aece1dad7669210`; runtime verification 
is recorded separately below.
   
   ### Reproduction sketch
   
   ```python
   from airflow.sdk import DAG, Asset, IdentityMapper, PartitionedAssetTimetable
   from airflow.providers.standard.operators.empty import EmptyOperator
   
   
   a = Asset(name="reconcile-a", uri="test://reconcile/a")
   b = Asset(name="reconcile-b", uri="test://reconcile/b")
   
   with DAG(
       dag_id="target",
       schedule=PartitionedAssetTimetable(
           assets=a,  # Later change this to a & b.
           default_partition_mapper=IdentityMapper(),
           batch_asset_events=True,
       ),
       catchup=False,
   ):
       EmptyOperator(task_id="consume")
   
   with DAG(
       dag_id="other-b-consumer",
       schedule=PartitionedAssetTimetable(
           assets=b,
           default_partition_mapper=IdentityMapper(),
       ),
       catchup=False,
   ):
       EmptyOperator(task_id="consume")
   ```
   
   Deploy and unpause both consumers, and wait for the definitions to be 
serialized and the assets to be active. Publish events through `POST 
/api/v2/assets/events` using the registered asset IDs and bodies such as 
`{"asset_id": <B_ID>, "partition_key": "k"}`.
   
   **Case 1: B published before the dependency existed.**
   
   1. Publish B/k and allow `other-b-consumer` to consume it.
   2. Change `target` from A to A & B, then wait for the new definition to be 
serialized.
   3. Publish A/k and let the scheduler evaluate it repeatedly.
   4. Inspect the pending partition run and its input links. B's historical 
event has no link to this consumer's pending run, so reevaluation alone cannot 
supply B.
   5. Republishing B/k supplies a new event to `target`, but also queues work 
for `other-b-consumer` again.
   
   **Case 2: a pending partition run predates the dependency change.**
   
   Starting from a clean state with `target` depending only on A, publish A/k 
and retain the pending partition run before the scheduler claims it. Change the 
definition to A & B and serialize it, then run the scheduler. Adding B changes 
the rollup fingerprint, so stale-fingerprint cleanup removes the old pending 
run and its input links. This case is discarded progress, not a pending run 
that simply waits forever. The underlying global asset events remain.
   
   ### Source findings and recovery requirements
   
   The scheduler evaluates inputs from [`PartitionedAssetKeyLog` rows belonging 
to each pending 
run](https://github.com/apache/airflow/blob/29331a8325698266e380a88e6aece1dad7669210/airflow-core/src/airflow/jobs/scheduler_job_runner.py#L2640),
 rather than searching all historical asset events. The [fingerprint includes 
each referenced asset and 
mapper](https://github.com/apache/airflow/blob/29331a8325698266e380a88e6aece1dad7669210/airflow-core/src/airflow/timetables/base.py#L553),
 so a dependency addition reaches the [stale-run cleanup 
path](https://github.com/apache/airflow/blob/29331a8325698266e380a88e6aece1dad7669210/airflow-core/src/airflow/jobs/scheduler_job_runner.py#L2566).
   
   The proposed recovery is an optional `consumer_dag_id` on `POST 
/api/v2/assets/events`. It would create a new asset event and queue it only for 
the selected eligible consumer. The event would retain the usual global 
history, listeners, and metrics; it would not reuse a historical event's 
identity or bypass access controls, asset-team restrictions, paused/draining 
state, or partition mapping. The example uses `catchup=False` and does not 
assume automatic historical replay is already promised.
   
   For case 1, publishing B/k with the selected consumer would supply the 
missing input. For case 2, first clear the affected consumer's old queued 
inputs, then publish scoped events for all inputs needed under the new 
definition. Queue deletion matches source partition keys, which can differ from 
target keys under a mapper. Partial clearing retains the remaining pending 
progress, so recovery must account for every contributing source key.
   
   A blanket history scan would be unsafe. Queued partition inputs can now be 
intentionally deleted through the API (#73119); silently restoring their links 
would undo that operation. Previously consumed events, changed partition 
mappings, and inactive assets also need defined treatment.
   
   Related work: #76706 wakes pending partition runs when their inputs or 
definitions change. Reevaluation of existing links does not supply a missing 
historical link, so that work alone does not cover this recovery case.
   
   ### Verification
   
   Reproduced against unmodified production code at the main commit above with 
PostgreSQL 16, psycopg 3.3.8 and Python 3.13. The controls use actual Dag 
serialization/dependency updates, API-style event registration through 
`AssetManager`, and real scheduler ticks.
   
   - In case 1, repeated ticks leave the target pending. Republishing B creates 
runs for both target and sibling; their consumed event IDs identify the 
replacement B event.
   - In case 2, a fresh B event attaches to the old candidate before the tick. 
The tick removes that candidate and both input links, creates no target run, 
and retains both global events.
   
   I am preparing a PR for consumer-scoped event publication. It will provide 
explicit recovery rather than changing automatic historical catchup behavior.
   


-- 
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]

  • [I] Recover partitioned asset i... ataulmujeeb-cyber (via GitHub) via commits

Reply via email to