ferruzzi commented on code in PR #66350:
URL: https://github.com/apache/airflow/pull/66350#discussion_r4039115161


##########
airflow-core/src/airflow/utils/db_cleanup.py:
##########
@@ -392,10 +418,32 @@ def _do_delete(
                 delete = source_table.delete().where(
                     and_(*[col == target_table.c[col.name] for col in 
source_table.primary_key.columns])
                 )
+            # Re-apply skip_if_referenced on the DELETE to guard against a 
race where a new
+            # referencing row is created after the archive INSERT committed 
but before the DELETE
+            # runs. Without this the DELETE would violate the ON DELETE 
RESTRICT FK and fail.
+            if skip_if_referenced:
+                pk_col = source_table.c[referenced_pk_column]
+                for referencing_table_name, fk_column in skip_if_referenced:
+                    referencing = table(referencing_table_name, 
column(fk_column))
+                    delete = delete.where(
+                        ~select(literal(1))
+                        .select_from(referencing)
+                        .where(referencing.c[fk_column] == pk_col)
+                        .correlate(source_table)
+                        .exists()
+                    )
             logger.debug("delete statement:\n%s", delete.compile())
-            session.execute(delete)
+            deleted = cast("CursorResult", session.execute(delete)).rowcount
             session.commit()
 
+            # A guarded DELETE (skip_if_referenced) may delete fewer rows than 
the SELECT
+            # found. That is fine: the SELECT already includes the same NOT 
EXISTS guard, so
+            # the skipped row is excluded on the next pass too, and the loop 
drains naturally.
+            # With --batch-size set, continuing here lets subsequent batches 
clean rows that
+            # were not affected by the race.
+            if deleted == 0:

Review Comment:
   I saw this in the last pass but didn't want to add another round of churn 
over a log message but now that you are back in there, I guess we may as well 
add it. 
   
   
   Right now when the DELETE removes nothing, `_do_delete` just continues 
silently.  With the default `skip_archive=False` that leaves the row in two 
places: still live in `dag_version`, and also sitting in the archive table that 
was just committed.  `export-archived` would then emit a row that was never 
deleted, and the next cleanup run silently archives it again.
   
   Could you log a warning before the `continue`?  
   
   ```python 
   num_rows = 
session.scalars(select(func.count()).select_from(limited_query.subquery())).one()
   if num_rows == 0:  # nothing left to delete
       break
   ```
     
   then in the zero-delete branch:
   
   ```python
   if deleted == 0:
     logger.warning(
         "%s rows from %s are still referenced and were not deleted; they 
remain in %s",
         num_rows,
         source_table_name,
         target_table_name if not skip_archive else "the archive, which is 
being dropped",
     )
     continue
   ```
     
   The wording is up to you, as long as a user can tell it happened. 
   



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