Skip to content

Fixes #20341: add automation Workflow retention to the Data Retention app - #32620

Merged
mohityadav766 merged 16 commits into
mainfrom
workflow-retention-20341
Sep 11, 2026
Merged

Fixes #20341: add automation Workflow retention to the Data Retention app#32620
mohityadav766 merged 16 commits into
mainfrom
workflow-retention-20341

Conversation

@mohityadav766

@mohityadav766 mohityadav766 commented Sep 4, 2026

Copy link
Copy Markdown
Member

Describe your changes:

Fixes #20341

automations_workflow grows without bound. Reverse ingestion, test connection and query runner
runs each leave a Workflow entity behind and nothing ever removes them — reverse metadata just
made it visible by creating them in volume.

Adds a workflowRetentionPeriod setting (default 30 days, 0 to retain forever) that the
weekly Data Retention job drains through the app's existing batching, stats and failure-reporting
path.

Release note: with a default of 30, every existing install starts pruning automation
workflows on its next weekly run without the operator doing anything. That's intentional — the
issue is about unbounded growth, so an opt-in default of 0 (as activityCommentsRetentionPeriod
uses) wouldn't address it. Set 0 to opt out.

Type of change:

  • Improvement

High-level design:

Follows the built-in cleanup pattern already in DataRetention rather than the
DataRetentionExtension SPI — that SPI is for tables shipping outside OpenMetadata, and
automations_workflow is ours.

File Change
dataRetentionConfiguration.json new workflowRetentionPeriod, deliberately not required
src/utils/ApplicationSchemas/DataRetentionApplication.json same field on the UI form's source schema, plus the regenerated src/jsons/ copy
WorkflowDocStoreDAOs.WorkflowDAO listIdsBeforeCutoff — one @SqlQuery, unquoted identifiers so MySQL and Postgres share it
native/2.1.0/{mysql,postgres}/schemaChanges.sql index on updatedAt for the batch query
DataRetention cleanAutomationWorkflowsdrainInBatchesBatchDrain, under isRetentionEnabled

Decisions worth reviewing:

  1. Not required, so no config backfill migration. jsonschema2pojo emits
    private Integer workflowRetentionPeriod = 30; and Jackson leaves absent keys alone, so a config
    saved before this change reads 30. (The only migration here is the index below.) Pinned by
    workflowRetentionFallsBackToDefaultForConfigsSavedWithoutIt, and independently verified in
    review against the generated POJO.

  2. Repository delete, not bulk SQL. A Workflow holds the service connection it ran against;
    dropping the row alone would strand its secrets in an external secrets manager
    (WorkflowRepository.postDeletedeleteSecretsFromWorkflow) and orphan its owner rows.
    Deletes are non-recursive — a Workflow has no children, and recursive is what makes
    EntityRepository.delete take a per-row deletion lock.

  3. Selected by age alone, no status filter. Filtering to terminal statuses leaks every workflow
    stuck in Pending/Running — the same unbounded-growth bug one enum value over. A 30-day-stale
    updatedAt already means the run is dead, since any status transition bumps it.

  4. Drains on zero progress, not on a short batch. A batch can come back full and still delete
    fewer rows than it fetched, because a workflow that fails to delete is skipped rather than
    rethrown. Stopping on a short batch would end the run at the first failure — and since batches
    are ordered oldest-first, the same undeletable row would head every batch of every run. Stopping
    on zero progress continues past a partial failure and still halts on an all-poison batch instead
    of spinning to the iteration cap.

  5. A failed row is skipped, not escalated. Per-item failures go to the run's failedRecords
    only, matching cleanOrphanTestCases and the other entity cleanups; the run is escalated to
    ACTIVE_ERROR only when a batch had rows to attempt and deleted none of them. Flipping status
    per row would have marked every future run FAILED, since the same undeletable row heads every
    batch. Failed ids are tracked for the run so such a row is counted once and attempted once
    rather than once per batch.

  6. Capped at 100k deletions per run. Each delete costs a repository round-trip plus a
    deleteSecretsFromWorkflow call, which is a network call on AWS/Azure setups. A large first-run
    backlog spreads over several weekly runs rather than running for hours. The number is a starting
    point — see the review thread.

Backward compatibility. Additive schema field, no config backfill, no API change. The index
migration is idempotent in both dialects.

Not in scope. The issue notes "once this is captured we can remove the scheduled job for
Reverse Metadata." That job isn't in this repo — there's no REVERSE_INGESTION producer in OSS, it
lives on the Collate side. This change is the prerequisite; removing that job is a separate
follow-up there.

Tests:

Use cases covered

  • An automation Workflow untouched for longer than the retention period is hard-deleted by the next scheduled run; a recent one is retained.
  • An operator setting workflowRetentionPeriod: 0 retains automation Workflows forever.
  • A Data Retention app configuration saved before this field existed reads the 30-day default instead of null (the app reads it as an int).
  • A backlog containing a permanently undeletable row still drains, instead of stopping at that row on every run.

Unit tests

  • openmetadata-service/src/test/java/.../dataRetention/DataRetentionTest.java
  • zeroOrMissingRetentionMeansForever (pre-existing) — the isRetentionEnabled guard
  • workflowRetentionFallsBackToDefaultForConfigsSavedWithoutIt — the no-backfill claim
  • aPoisonRowDoesNotStopTheWorkflowDrain — models a 50-row backlog with one undeletable row: 9 rows drained under the old short-batch predicate, 49 under the new zero-progress one
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 -- in DataRetentionTest

Coverage, stated honestly: DataRetention.java is at 0.6% line coverage (2/336) from unit
tests — measured with mvn -P static-code-analysis test jacoco:report, not estimated. It was 0.6%
before this PR too. The class needs a live DB, the repository layer and a Quartz context, so
unit-testing the delete path would mean static-mocking Entity and stubbing the DAO — the
mock-wiring test CLAUDE.md warns against. The unit test above models the drain's contract
rather than running deleteExpiredWorkflows; the delete path itself is covered by the IT.
This class is under the 90% target and this PR does not move it.

Backend integration tests

  • openmetadata-integration-tests/src/test/java/.../DataRetentionAppIT.java
  • test_retentionRun_cleansOldAutomationWorkflows — creates two TEST_CONNECTION workflows, backdates one 90 days in automations_workflow, triggers the app, asserts the old row is gone and the recent one survives.

⚠️ Written but not executed locally — needs the full IT stack. Compiles clean. Please let CI run it.

Ingestion integration tests

  • Not applicable — no ingestion changes.

Playwright (UI) tests

  • Not applicable — no handwritten UI code. The config field is auto-rendered by
    ApplicationsClassBase from the app schema.

Manual testing performed

  1. mvn -pl openmetadata-service compile and -pl openmetadata-integration-tests test-compile → BUILD SUCCESS; spotless:check clean on both.
  2. Verified the generated POJO carries private Integer workflowRetentionPeriod = 30; — the no-backfill mechanism, confirmed in generated output rather than assumed.
  3. Ran the batch query against both live engines to prove the unquoted-identifier sharing works (Postgres folds updatedAtupdatedat): MySQL OK, Postgres OK.
  4. Ran each index migration twice against live MySQL and Postgres — second run is a no-op (SELECT 1 branch / already exists, skipping), so idempotent. Dev databases restored afterwards.
  5. Measured the index's effect on a 300k-row table:
    Limit -> Gather Merge -> Sort -> Parallel Seq Scan becomes Limit -> Index Scan using idx_automations_workflow_updated_at.
  6. Verified the regenerated app schema is byte-identical to source − $id (what parseSchemas.js produces), trailing newline included.

UI screen recording / screenshots:

Not captured. No handwritten UI code changed — the schema adds one number input plus its help text
to the existing Data Retention app config form, rendered automatically. Happy to attach one if a
reviewer wants it.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • My PR is linked to a GitHub issue via Fixes #<issue-number> above.
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: added the updatedAt index migration for both dialects; no config backfill needed — see decision 1.
  • For UI changes: explained above — no handwritten UI code.
  • I have added tests (unit / integration) and listed them above.
  • I have added tests around the new logic.
  • For connector/ingestion changes: not applicable.

🤖 Generated with Claude Code

… app

The `automations_workflow` table grows without bound: reverse ingestion,
test connection and query runner runs each leave a Workflow entity behind
and nothing ever removes them. Reverse metadata made this visible by
creating workflows in volume.

Adds a `workflowRetentionPeriod` setting (default 30 days, 0 to retain
forever) that the weekly Data Retention job drains through the existing
batching, stats and failure-reporting path.

Two deliberate choices:

- The field is not `required`, so no upgrade migration is needed.
  jsonschema2pojo emits an initialized default and Jackson leaves absent
  keys alone, so a configuration saved before this change reads 30.

- Deletion goes through the repository rather than a bulk SQL delete. A
  Workflow holds the service connection it ran against, so dropping the
  row alone would strand its secrets in an external secrets manager and
  orphan its owner relationship rows.

Rows are selected by age alone rather than by terminal status: a workflow
stuck in Pending or Running still leaks, and a 30-day-stale updatedAt
already means the run is dead. Per-workflow failures are counted and
skipped instead of rethrown, since batches are ordered oldest first and
aborting on one bad row would wedge the cleanup permanently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 4, 2026 18:59
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Sep 4, 2026

Copilot AI 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.

🟡 Changes recommended

The current batch-drain termination condition can stop workflow cleanup early on partial-delete failures, leaving additional expired workflows unprocessed.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR extends the Data Retention application to prevent unbounded growth of automations_workflow by introducing a configurable retention period and deleting expired Workflow entities via the repository deletion path (so related secrets/relationships are cleaned up).

Changes:

  • Added workflowRetentionPeriod (default 30 days, 0 = retain forever) to the Data Retention app configuration schema and surfaced it in the UI app schema/help.
  • Implemented automation-workflow cleanup in DataRetention using a DAO query to batch-select old workflow IDs and hard-delete them via Entity.deleteEntity.
  • Added unit tests for defaulting behavior and batch-drain termination, plus an integration test covering end-to-end deletion of an old automation workflow.
File summaries
File Description
openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java Adds the automation-workflow cleanup step driven by the new retention config.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowDocStoreDAOs.java Adds DAO query to list workflow IDs older than a cutoff, for batch cleanup.
openmetadata-spec/src/main/resources/json/schema/entity/applications/configuration/internal/dataRetentionConfiguration.json Adds the new workflowRetentionPeriod field to the internal Data Retention configuration schema.
openmetadata-service/src/main/resources/json/data/app/DataRetentionApplication.json Updates the seeded Data Retention app config to include the new retention setting.
openmetadata-service/src/test/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetentionTest.java Adds unit tests for schema-default fallback and drain termination behavior.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DataRetentionAppIT.java Adds an IT that creates workflows, backdates one, triggers the app, and asserts old workflow deletion.
openmetadata-ui/src/main/resources/ui/src/jsons/applicationSchemas/DataRetentionApplication.json Surfaces the new retention field in the UI-rendered app configuration schema.
openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/DataRetentionApplication.md Adds operator help text for the new field.
openmetadata-ui/src/main/resources/ui/src/generated/entity/applications/configuration/internal/dataRetentionConfiguration.ts Regenerated TS type to include workflowRetentionPeriod.
openmetadata-ui/src/main/resources/ui/src/generated/metadataIngestion/workflow.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/metadataIngestion/applicationPipeline.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/metadataIngestion/application.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/entity/services/ingestionPipelines/serviceProgressEvent.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/entity/services/ingestionPipelines/ingestionPipeline.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/entity/applications/marketplace/createAppMarketPlaceDefinitionReq.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/entity/applications/marketplace/appMarketPlaceDefinition.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/entity/applications/app.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
openmetadata-ui/src/main/resources/ui/src/generated/api/services/ingestionPipelines/createIngestionPipeline.ts Regenerated TS type(s) to include workflowRetentionPeriod in CollateAIAppConfig.
Review details
  • Files reviewed: 8/18 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

LOG.info("Initiating automation workflows cleanup: Retention = {} days.", retentionPeriod);
long cutoffMillis = getRetentionCutoffMillis(retentionPeriod);

executeWithStatsTracking("automation_workflows", () -> deleteExpiredWorkflows(cutoffMillis));
Comment on lines +231 to +234
@SqlQuery(
"SELECT id FROM automations_workflow WHERE updatedAt < :cutoffTs "
+ "ORDER BY updatedAt LIMIT :limit")
List<String> listIdsBeforeCutoff(@Bind("cutoffTs") long cutoffTs, @Bind("limit") int limit);
Copilot AI review requested due to automatic review settings September 4, 2026 19:04
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

✅ Generated Sources Auto-Updated

The generated TypeScript types (src/generated/) and dereferenced JSON
schemas (src/jsons/) have been automatically updated based on JSON schema
changes in this PR.

Copilot AI 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.

🔵 Needs a closer look

The new automation-workflow cleanup uses a drain condition based on “deleted < batch size”, but per-entity delete failures make that signal unreliable and can prematurely stop draining while expired workflows remain.

Review details

Suppressed comments (1)

openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:493

  • executeWithStatsTracking drains batches based on deleted < BATCH_SIZE, but deleteExpiredWorkflows returns only the count of successful per-entity deletes. If any workflow in a batch fails deletion, deleted becomes < BATCH_SIZE and the drain stops early even when there are still many expired workflows remaining (with BATCH_SIZE=10_000, this can leave a large backlog until future weekly runs). Use a drain condition based on "no progress" (e.g., stop only when a batch deletes 0) for this per-entity delete path.
  private void cleanAutomationWorkflows(int retentionPeriod) {
    LOG.info("Initiating automation workflows cleanup: Retention = {} days.", retentionPeriod);
    long cutoffMillis = getRetentionCutoffMillis(retentionPeriod);

    executeWithStatsTracking("automation_workflows", () -> deleteExpiredWorkflows(cutoffMillis));

  • Files reviewed: 10/20 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 4, 2026 19:10

Copilot AI 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.

🔵 Needs a closer look

The automation workflow cleanup can stop after the first batch when any per-entity delete fails, leaving additional expired workflows unpruned.

Review details

Suppressed comments (1)

openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:493

  • cleanAutomationWorkflows currently uses executeWithStatsTracking, which considers the table drained when a batch deletes fewer than BATCH_SIZE rows. Since deleteExpiredWorkflows deletes per ID and can skip failures, any single failed delete will make the batch delete count < BATCH_SIZE and prematurely stop the drain even when more expired workflows remain beyond the first batch.
  private void cleanAutomationWorkflows(int retentionPeriod) {
    LOG.info("Initiating automation workflows cleanup: Retention = {} days.", retentionPeriod);
    long cutoffMillis = getRetentionCutoffMillis(retentionPeriod);

    executeWithStatsTracking("automation_workflows", () -> deleteExpiredWorkflows(cutoffMillis));

  • Files reviewed: 10/20 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 70%
70.77% (96081/135759) 55.4% (57004/102893) 56.71% (18988/33477)

@mohityadav766 mohityadav766 self-assigned this Sep 7, 2026
Copilot AI review requested due to automatic review settings September 7, 2026 07:39

Copilot AI 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.

🔵 Needs a closer look

The current drain termination condition for workflow cleanup can stop early after partial per-row delete failures, leaving eligible workflows unprocessed.

Review details

Suppressed comments (2)

openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:493

  • The drain termination condition for automation workflows is currently based on the number successfully deleted (< BATCH_SIZE). If any deletion fails within a full batch (e.g., 9 deleted, 1 failed), deleteExpiredWorkflows returns 9 and executeWithStatsTracking will treat the table as drained and stop, leaving many eligible workflows unprocessed. The drain should only stop when a batch deletes 0 (no progress), since per-row failures can make the deleted count smaller than the batch even when more rows remain.
  private void cleanAutomationWorkflows(int retentionPeriod) {
    LOG.info("Initiating automation workflows cleanup: Retention = {} days.", retentionPeriod);
    long cutoffMillis = getRetentionCutoffMillis(retentionPeriod);

    executeWithStatsTracking("automation_workflows", () -> deleteExpiredWorkflows(cutoffMillis));

openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowDocStoreDAOs.java:234

  • This query orders by updatedAt and limits, but automations_workflow has no index on the generated updatedAt/updatedat column in either MySQL or Postgres schemas (only PK(id) and unique(nameHash)). On large catalogs this can force a full scan + sort every batch of a weekly job, which can become operationally expensive precisely when the table has grown large. Consider adding an index on the generated updatedAt/updatedat column (and validating with EXPLAIN) so the retention job can drain efficiently.
    @SqlQuery(
        "SELECT id FROM automations_workflow WHERE updatedAt < :cutoffTs "
            + "ORDER BY updatedAt LIMIT :limit")
    List<String> listIdsBeforeCutoff(@Bind("cutoffTs") long cutoffTs, @Bind("limit") int limit);
  • Files reviewed: 10/20 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

manerow
manerow previously approved these changes Sep 8, 2026
karanh37
karanh37 previously approved these changes Sep 8, 2026
chirag-madlani
chirag-madlani previously approved these changes Sep 8, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — merge_conflict (2026-09-10T02:42:45Z)

The entry left the queue before it was built, so no checks ran against it.

mohityadav766 and others added 2 commits September 10, 2026 14:32
Conflicts in the 2.1.0 migrations, both dialects: main appended three
audit_log_event index blocks while this branch appended the
automations_workflow.updatedAt index. On MySQL both sides use the same
`SET @ddl`/PREPARE/EXECUTE boilerplate, so the shared lines were treated as
common context and the two blocks were interleaved into invalid SQL.

Resolved by taking main's file verbatim and re-appending this branch's index
last, so main's content stays byte-identical. Verified by running the
resolved index section against live MySQL and Postgres: all four indexes
create, and a second run is a no-op in both dialects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔄 Playwright impact map auto-refreshed

This PR touched specs or UI source that changed the source→spec routing map. I regenerated .github/playwright/impact-map.generated.json and pushed the diff to this branch.

- source entries: 758 → 758
- 0 added, 0 removed, 13 changed spec-list

Entries whose spec list changed:
  openmetadata-ui/src/main/resources/ui/playwright/constant/config.ts
  openmetadata-ui/src/main/resources/ui/playwright/constant/service.ts
  openmetadata-ui/src/main/resources/ui/playwright/support/entity/service/DashboardServiceClass.ts
  openmetadata-ui/src/main/resources/ui/playwright/support/entity/service/DatabaseServiceClass.ts
  openmetadata-ui/src/main/resources/ui/playwright/support/entity/service/MessagingServiceClass.ts
  openmetadata-ui/src/main/resources/ui/playwright/support/fixtures/base.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/service.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/serviceIngestion.ts
  … and 3 more

What is this file? It is the auto-generated half of Playwright's PR planner. It routes "if source X changes, run specs Y" by walking spec imports and cross-referencing getByTestId strings. Hand-authored routing in impact-map.json always wins on conflict.

What if I want to regenerate locally instead? Run this before pushing your next change to skip the bot commit:

python3 .github/scripts/generate_playwright_impact_map.py
git add .github/playwright/impact-map.generated.json
git commit --amend --no-edit  # or a separate commit

karanh37
karanh37 previously approved these changes Sep 10, 2026
sonika-shah
sonika-shah previously approved these changes Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔄 Playwright impact map auto-refreshed

This PR touched specs or UI source that changed the source→spec routing map. I regenerated .github/playwright/impact-map.generated.json and pushed the diff to this branch.

- source entries: 759 → 759
- 0 added, 0 removed, 6 changed spec-list

Entries whose spec list changed:
  openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
  openmetadata-ui/src/main/resources/ui/playwright/support/fixtures/base.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts
  openmetadata-ui/src/main/resources/ui/playwright/utils/tier.ts

What is this file? It is the auto-generated half of Playwright's PR planner. It routes "if source X changes, run specs Y" by walking spec imports and cross-referencing getByTestId strings. Hand-authored routing in impact-map.json always wins on conflict.

What if I want to regenerate locally instead? Run this before pushing your next change to skip the bot commit:

python3 .github/scripts/generate_playwright_impact_map.py
git add .github/playwright/impact-map.generated.json
git commit --amend --no-edit  # or a separate commit

@gitar-bot

gitar-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Adds automation Workflow retention to the Data Retention app with a configurable workflowRetentionPeriod (default 30 days, 0 to retain forever) that drains expired workflows weekly. Resolves per-row delete failures that previously ended the drain prematurely and caused poison rows to mark runs FAILED — now failed rows are skipped, and the drain continues on zero progress across an entire batch instead of stopping at the first failure. No issues found.

✅ 3 resolved
Edge Case: Per-row delete failures end the workflow drain prematurely

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:506-520 📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:530-532
executeWithStatsTracking continues draining only while a batch reports deleted == BATCH_SIZE (drainedWhen = deleted < BATCH_SIZE). For every other cleanup the returned value is the count of rows a bulk SQL delete actually removed, so a full batch returns BATCH_SIZE. But deleteExpiredWorkflows returns only the success count: if a full 10k batch is fetched and even one Entity.deleteEntity throws (e.g. a workflow whose secret/relationship deletion fails), it returns <10k and the drain stops for the whole run. Because listIdsBeforeCutoff is ordered oldest-first, any permanently-undeletable ('poison') rows sit at the front of every batch on every run, so each weekly run deletes at most one batch and then stops — degrading the intended one-run drain to ~BATCH_SIZE rows/run and effectively re-introducing the unbounded-growth this PR targets when a backlog and a poison row coexist. Fix: drive the drain decision off rows fetched (ids.size()) rather than rows deleted, while stopping when a batch makes zero progress (deleted == 0) to avoid spinning on an all-poison batch.

Bug: One undeletable workflow marks every retention run FAILED

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:547-558 📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:99-102
A workflow that cannot be deleted (e.g. FK/constraint issue, or the external secrets manager being briefly unreachable during postDelete) is caught per-row at DataRetention.java:552-557, which sets internalStatus = ACTIVE_ERROR. At the end of the run startApp() (lines 99-102) throws on ACTIVE_ERROR and the whole DataRetention job is reported FAILED — even though the drain correctly skipped the row and every other cleanup succeeded. Because batches are ordered oldest-first, a single permanently-undeletable row heads every batch of every weekly run, so the app reports FAILED forever, defeating the 'counted and skipped, not rethrown' intent and causing alert fatigue that can mask genuine failures. Consider tracking these skipped-row failures separately from the run's terminal status (e.g. record them in stats/failed count without flipping internalStatus), or only escalating to ACTIVE_ERROR when a batch makes zero progress rather than on any individual skipped row.

Quality: Poison workflow row inflates failedRecords each batch

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/dataRetention/DataRetention.java:552-558
A permanently-undeletable workflow is re-fetched by listIdsBeforeCutoff at the head of every batch (it is never removed and stays oldest), and each batch charges it again via updateStats("automation_workflows", 0, 1) at DataRetention.java:554. The same row is therefore counted as a distinct failure once per batch, inflating failedRecords and totalRecords well beyond the number of actual problem rows and making the reported stats misleading. Consider de-duplicating failed ids within a run or counting a given id at most once.

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

@sonarqubecloud

Copy link
Copy Markdown

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Retention App] Add workflows retention

8 participants