Skip to content

Add endpoint to rollback failed imports#613

Open
vish-cs wants to merge 1 commit into
datacommonsorg:masterfrom
vish-cs:rollback
Open

Add endpoint to rollback failed imports#613
vish-cs wants to merge 1 commit into
datacommonsorg:masterfrom
vish-cs:rollback

Conversation

@vish-cs

@vish-cs vish-cs commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new script, retry_failed_ingestion.py, along with its corresponding unit tests, to automate the rollback and retry process for failed Dataflow ingestion jobs. Feedback on the implementation highlights a critical correctness bug where the workflow could be retriggered even if a rollback fails, and a missing pagination implementation when fetching Dataflow job messages. Additionally, it is recommended to use posixpath instead of os.path for GCS path manipulations to avoid platform-specific path separator issues, and to clean up unused imports in the test file.

Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
Comment thread pipeline/scripts/retry_failed_ingestion_test.py Outdated
@codacy-production

codacy-production Bot commented Jul 6, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 medium · 28 minor

Alerts:
⚠ 30 issues (≤ 0 issues of at least minor severity)

Results:
30 new issues

Category Results
Documentation 19 minor
CodeStyle 9 minor
Complexity 2 medium

View in Codacy

🟢 Metrics 74 complexity

Metric Results
Complexity 74

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@vish-cs
vish-cs force-pushed the rollback branch 2 times, most recently from a3696c4 to 5bb4e0a Compare July 6, 2026 08:47
@vish-cs
vish-cs requested a review from gmechali July 6, 2026 08:48
Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
logging.error(f"Error parsing importList parameter: {e}")

logging.info(f"Fetching error messages for Dataflow job {job_id}...")
request = dataflow.projects().locations().jobs().messages().list(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems to only be checking dataflow for failures. What if the failures came from elsewhere in the worfklow (spanner embeddings, ingestion helper issue, bq federation).

What's the long term plan for this, should we be checking all the stages? Should the workflow be modified to propoagate errors from dataflow? (if that's even possible)

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.

Yes, we should be taking bq failures also into account. It will require us to track aggregation status into spanner. Looking into this.

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.

The new implementation is an endpoint within ingestion helper. It takes a workflow id as an argument and reverts all the imports for that workflow. It can be invoked manually or from the ingestion workflow in the failure handing logic for any type of failures.

Comment thread pipeline/scripts/retry_failed_ingestion.py Outdated
"""
status_results = transaction.execute_sql(
status_sql,
params={'importName': short_name},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How would this work for a multi-provenance import?
Like if I import WHO and ILO at once?

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.

The script checks for the dataflow logs to see if it finds a specific failed import and reverts it. Else, it falls back to reverting all the imports in that batch.

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.

I am also thinking that it may be simpler to revert the whole batch as a v1 for the rollback flow.

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.

The new implementation reverts the whole batch

@vish-cs vish-cs changed the title Add script to rollback failed import Add endpoint to rollback failed imports Jul 15, 2026

@gmechali gmechali left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hey Vishal, overall I think the major concern I have is that I think the ingesiton helper is being used for too much. It shouldn't contain such major implementation logic, I think all that should live within the workflow, and the ingestion helper should just give the workflow the information it needs (like the last successful graphpath).

Happy to talk this through if you want. lmk what you think

logging.error(f"Error querying IngestionHistory for workflow '{workflow_id}': {e}")
return []

def revert_import(self,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you move most of this logic in a separate file in util/rollback_helper.py or something?

And just keep the spanner code in here, so get_imports_for_workflow, fetch_import_version_history, set_rollback_status etc..

I think this spanner client should just be simple helper functions to execute spanner queries. The actual logic for how these helpers are called belong elsewhere.

logging.error(f'Error checking for retry imports: {e}')
return True

def get_imports_for_workflow(self, workflow_id: str) -> list[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ultra nit: "from" instead of for?


if not rows_history:
logging.warning(
f"No version history found in ImportVersionHistory for '{short_name}'. Cannot revert.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So what should happen here to the database?

do we leave the run in FAILED state, the staleness timestamp should read before this fialed run start. Ideally they get alerted of this failuyre and have to manually intervene, is that right?

latest_version = rows_history[0][0]
latest_comment = rows_history[0][1] if len(rows_history[0]) > 1 else ""

if latest_comment and ("Reverted due to Dataflow failure" in latest_comment or "Reverted batch workflow" in latest_comment):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Using these string comments seems super dangerous to me. Shouldn't we use the Status instead?

It's really not transparent that if you modify the comment, you ll modify actual workflow behavior. You know this now, but whoever is working on this in 9 months won't. Can't we just fetch the Status on IngestionHistory row, add a new rollback status and use that to determine the state?

logging.error(
f"Import '{short_name}' was already reverted recently. Aborting to prevent infinite loop."
)
return "ALREADY_REVERTED", None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What exactly is this function supposed to return? I see you returning a True/False or now thing "ALREADY_REVERTED" string.


class RollbackResponse(BaseResponse):
revertedImports: Dict[str, str] = Field(default_factory=dict, description="Map of import names to their reverted versions")
dryRun: bool = Field(default=False, description="Whether this was executed in dry run mode")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need the dry run in the response?

)
return "ALREADY_REVERTED", None

previous_version = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I kind of disagree with the architecture of this. I think the rollback shouldn't be triggered by the ingestion helper. I think the ingestion helper should be called to give the worfklow the information it needs to do the rollback.

I think we can use a subworkflow to package everything from updating the ingesiton_history, to dataflow, to aggregations.

Then the worfklow, on failure, can aks the ingestion Helper for the graphPath of the last successful ingestions for these imports. Then the worfklow can re-update the ingestionHistory/Importversionhistory statuses, re-laucnh dataflow with new params, relaunch postprocessing etc...

Otherwise, with this implementation, we're just complete obfuscating the process for the rollback.


logging.info(f"Retriggering workflow '{workflow_name}'...")
try:
service = build('workflowexecutions', 'v1', cache_discovery=False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I dont think we should make this a separate workflow, but a subworkflow.

The first workflow acquires the lock, I believe it should NOT be allowed to release the lock until the database is back in a good state post rollback. That's why I think it should also be a subworkflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants