Skip to content

Fixes 30119: Stabilize integration-test CI#30120

Open
harshach wants to merge 45 commits into
mainfrom
harshach/stabilize-flaky-integration-tests
Open

Fixes 30119: Stabilize integration-test CI#30120
harshach wants to merge 45 commits into
mainfrom
harshach/stabilize-flaky-integration-tests

Conversation

@harshach

@harshach harshach commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes:

Fixes #30119

This change makes Java, Playwright, and Python integration-test outcomes deterministic without reducing parallel throughput.
It builds each Java runtime once before four concurrent isolation lanes, classifies assertion, setup, infrastructure, missing-report, cancellation, and hung-test outcomes, and retains fail-closed diagnostics.
It also fixes hard-delete cache consistency, stale context/search updates, bulk-update fallback ordering, restore/test-suite update races, and retry-dependent Oracle, Trino, and Playwright readiness checks.
The Playwright zero-retry policy remains shadow-only while stable retry signatures are collected.

Type of change:

  • Bug fix

High-level design:

  • Immutable, checksummed, source-SHA-bound Maven reactor artifact shared by profile lanes
  • Concurrent parallel, global-state, multi-node, and retry-queue Failsafe lanes with unchanged required check names
  • Shared JUnit and Playwright classifiers plus always-uploaded reports, environment data, Maven logs, dumps, and bounded container logs
  • Authoritative hard-delete markers and compare-and-delete invalidation across local/Redis cache layers
  • Conditional context processing, non-overlapping search fallback, durable per-entity relationship revisions for logical test suites, and exact-state polling
  • Transactional per-TestCase and per-logical-TestSuite revisions reject late search publications without database advisory locks
  • No blanket test reruns; shadow telemetry before any zero-retry enforcement

Tests:

Use cases covered

  • Concurrent reads cannot repopulate entities after hard delete
  • Deleted context files reject late background updates
  • Timed-out bulk indexing closes before safe individual fallback
  • Concurrent logical-suite membership updates converge in both TestCase and TestSuite search documents
  • Edited test cases publish fenced logical-suite relationship state instead of stale incremental changes
  • Java lanes fail closed for assertions, setup failures, missing reports, cancellation, and hangs
  • Playwright shards and Python suites preserve their real test outcome and diagnostics
  • Glossary term navigation waits for the populated tree state before deciding whether to expand
  • Oracle lineage, Trino rows, and Lineage UI entities wait for the exact published state

Unit tests

  • Added and updated tests for cache markers, Elasticsearch/OpenSearch bulk sinks, context extraction, entity restore, search updates, and both outcome classifiers
  • Classifier tests: 21 passed
  • Focused Java tests: 258 passed
  • Latest TestCaseRepositoryTest and SearchRepositoryTest: 22 passed
  • Coverage on changed classes: not measured separately

Backend integration tests

  • Added HardDeleteCacheConsistencyIT
  • Full MySQL/Elasticsearch parallel lane: 14,956 tests, 0 failures, 1,380 skipped
  • Logical-suite ordering regressions: 3/3 passed on MySQL/Elasticsearch and PostgreSQL/OpenSearch
  • Edited-TestCase logical-suite regression: prior behavior deterministically failed with Missing fenced relationship update; fixed behavior passed 1/1 against MySQL and Elasticsearch
  • Context Center upload/search regression: 21/21 passed on MySQL/Elasticsearch
  • Hard-delete race regression: 50 repetitions for every Include mode under parallel workers

Python integration tests

  • Latest PR head passed every MySQL and PostgreSQL integration lane: shard 1 and shard 2 on Python 3.10, 3.11, and 3.12
  • Unit/static checks passed on Python 3.10, 3.11, and 3.12
  • Branch workflow validation: MySQL/Elasticsearch and PostgreSQL both passed every lane

Ingestion integration tests

  • Updated Oracle lineage and Trino fixtures with bounded exact-state readiness

Playwright (UI) tests

Manual testing performed

  • actionlint on all 16 changed workflows
  • ShellCheck, bash -n, YAML parsing, and targeted Docker-storage helper validation
  • Maven Spotless apply/check, service build, integration-test compilation, and focused repository suites
  • UI organize-imports, ESLint, and Prettier checks
  • Real MySQL/Elasticsearch positive and negative regression runs for edited logical-suite members
  • Seven local workflow/selector/artifact/gate harnesses

UI screen recording / screenshots:

Not applicable — test synchronization changed, not product UI behavior.

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 documented non-obvious behavior without adding redundant comments.
  • Not applicable: no JSON Schema changes.
  • Not applicable: no product UI changes requiring a recording or screenshots.
  • I have added tests and listed them above.
  • I have added tests that cover the exact race and outcome-classification scenarios being fixed.

Greptile Summary

This PR stabilizes Java, Playwright, and Python integration-test CI outcomes by making test outcomes deterministic without reducing parallel throughput. The core backend changes fix hard-delete cache consistency, stale context/search updates, bulk-update fallback ordering, restore/test-suite update races, and retry-dependent readiness checks.

  • Adds claim-token\u2013based optimistic concurrency to the search retry queue so that concurrent workers cannot delete each other's PENDING records; staleness is detected and metrics are emitted for superseded claims.
  • Introduces durable testSuitesRevision / testsRevision counters in entity_extension and a revision-fenced Painless script so concurrent test-case membership changes converge instead of overwriting each other in Elasticsearch/OpenSearch.
  • Refactors CachedEntityDao.getBase to return Optional and gates Redis writes behind an epoch equality check, preventing a stale loader from re-populating L1/Redis after a hard-delete or concurrent write.

Confidence Score: 5/5

Safe to merge. The claim-token guards, revision-fenced Painless scripts, and propagation-context preservation all work together correctly.

The three findings are quality-of-life concerns rather than defects that produce wrong data. The PR includes 258 focused unit tests, full MySQL/Elasticsearch parallel lane results with zero failures, and targeted regression coverage for every race addressed.

SearchIndexRetryWorker.java (double-write pattern and stability-loop exhaustion path) and SearchRepository.addConfirmedPropagationCandidates (propagation skip on under-reported failures) are the areas worth revisiting before the next increment.

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java Major refactor: adds ScriptedPartialUpdate record, RelationshipRevisionSpec for revision-fenced Painless scripts, updateEntitiesIndex overload with relationship revisions, enqueueEntityRetry with propagation context capture, and propagateEntityAfterRetry. Logic is correct; see comment on propagation being skipped when failure count is under-reported.
openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexRetryWorker.java Adds claim-token–based completion guard, propagateAfterRetry, loadStableEntitySnapshot with 3-attempt stability loop, and double-write to bulk sink for entities with relationship revisions.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java Adds claimToken column to search_index_retry_queue, claim-token–guarded delete/update, and incrementRevisions batch SQL. Propagation context is preserved on conflict upsert in both MySQL and PostgreSQL dialects.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java Moves DB writes into flushInOneTransaction (which retries deadlocks), separates revision increment + snapshot from post-commit search publication, and introduces LogicalSuiteRelationshipChange record.
openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexRetryQueue.java Adds PROPAGATION_CONTEXT_MARKER, Base64-encoded propagation context embedding/extraction, preservePropagationContext, and visibleFailureReason.
openmetadata-service/src/main/java/org/openmetadata/service/cache/CachedEntityDao.java Refactored getBase to return Optional<String> (cache-only lookup, no DB fallback). DB fetch and Redis population are now in the caller guarded by an epoch check, eliminating the stale-entry race.
openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/ElasticSearchBulkSink.java Adds scripted partial update support with addScriptedPartialUpdate and updateScriptedDocumentDirectly, correctly forwarding oversized-document failures to the failure callback.
bootstrap/sql/migrations/native/2.0.0/mysql/schemaChanges.sql Adds claimToken VARCHAR(36) via a guarded prepared-statement ALTER TABLE, safe for re-execution on an already-migrated schema.
bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql Adds claimToken VARCHAR(36) NULL with an idempotent ADD COLUMN IF NOT EXISTS. Clean and safe.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java Adds epoch bump before invalidation, fixes markEntityNotFound to use cacheNameKey-normalized FQN, gates Redis writes behind epoch equality checks, and adds updateIfCurrent with optimistic-locking variant.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant TC as TestCaseRepository
    participant DB as Database
    participant SR as SearchRepository
    participant ES as Elasticsearch
    participant RQ as SearchIndexRetryQueue
    participant RW as SearchIndexRetryWorker

    TC->>DB: flushInOneTransaction() incrementRevisions
    DB-->>TC: revisionN
    TC->>SR: updateEntitiesIndex(testCases, revisionMap)
    SR->>ES: bulkSink scripted partial update
    ES-->>SR: success or failure
    alt succeeded
        SR->>SR: propagateEntitiesAfterBulkFlush()
    else failed
        SR->>RQ: enqueueWithPropagation(entity, changeDesc)
    end
    RW->>RQ: claimPending with claimToken
    RW->>DB: loadStableEntitySnapshot 3-attempt
    DB-->>RW: entity + revision
    RW->>ES: upsertEntitiesInBulk Write1+Write2
    RW->>SR: propagateAfterRetry
    RW->>RQ: deleteClaimed with claimToken
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant TC as TestCaseRepository
    participant DB as Database
    participant SR as SearchRepository
    participant ES as Elasticsearch
    participant RQ as SearchIndexRetryQueue
    participant RW as SearchIndexRetryWorker

    TC->>DB: flushInOneTransaction() incrementRevisions
    DB-->>TC: revisionN
    TC->>SR: updateEntitiesIndex(testCases, revisionMap)
    SR->>ES: bulkSink scripted partial update
    ES-->>SR: success or failure
    alt succeeded
        SR->>SR: propagateEntitiesAfterBulkFlush()
    else failed
        SR->>RQ: enqueueWithPropagation(entity, changeDesc)
    end
    RW->>RQ: claimPending with claimToken
    RW->>DB: loadStableEntitySnapshot 3-attempt
    DB-->>RW: entity + revision
    RW->>ES: upsertEntitiesInBulk Write1+Write2
    RW->>SR: propagateAfterRetry
    RW->>RQ: deleteClaimed with claimToken
Loading

Comments Outside Diff (1)

  1. openmetadata-service/src/main/java/org/openmetadata/service/drive/ContextFileExtractionService.java, line 286-296 (link)

    P1 Retry Exhaustion Leaves Stuck State

    Three rapid optimistic-lock conflicts make this helper return false, after which callers abandon the remaining extraction transition. In the result and failure paths, the context file can remain in Analyzing indefinitely instead of reaching a terminal or retryable state.

    Context Used: CLAUDE.md (source)

Reviews (35): Last reviewed commit: "test(drive): isolate search upload fixtu..." | Re-trigger Greptile

Context used:

  • Context used - CLAUDE.md (source)

@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Jul 16, 2026
Comment thread .github/workflows/playwright-integration-tests-mysql.yml Fixed
Comment thread .github/workflows/playwright-integration-tests-postgres.yml Fixed
@harshach
harshach marked this pull request as ready for review July 16, 2026 07:45
@harshach
harshach requested review from a team, akash-jain-10 and tutte as code owners July 16, 2026 07:45
Copilot AI review requested due to automatic review settings July 16, 2026 07:45

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

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.

Copilot AI review requested due to automatic review settings July 16, 2026 08:23

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (110 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sonarqubecloud

Copy link
Copy Markdown

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gitar-bot

gitar-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 9 resolved / 10 findings

Stabilizes integration tests through deterministic outcome classification and robust cache consistency, while addressing search retry races and relationship revision fencing. Consider addressing the potential for stuck states in the context file extraction service due to retry exhaustion.

💡 Edge Case: Embedding generation failure indexes doc without vector

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/ElasticSearchBulkSink.java:1132-1146

When an entity's state has changed (no reusable cached embedding), enrichWithEmbedding calls generateEmbeddingFields; on a transient failure (e.g. embedding provider unavailable) the catch block returns the original json unchanged and the entity is still indexed via a full index op — stripping/omitting the vector for that document, as the surrounding comment itself warns. The failure is only surfaced in getVectorStats (counted as vectorFailed), not as a reindex failure, so affected docs remain vector-less until the next reindex. Impact is bounded (self-heals on subsequent reindex since no cached embedding will be found), but consider tracking these failures more prominently or preserving any prior vector rather than silently continuing.

✅ 9 resolved
Bug: Unbounded conditional-update retry can spin indefinitely

📄 openmetadata-service/src/main/java/org/openmetadata/service/drive/ContextFileExtractionService.java:266-280
updateFile/updateContent changed from a bounded 3-attempt loop to while (true). The loop only exits on success, a deleted/stale entity (null), or thread interruption. Under sustained write contention on the same context file/content, the extraction worker will retry forever with 10ms sleeps, permanently consuming a thread from the bounded extraction executor and starving other extraction work. Reintroduce an attempt cap (e.g. a larger bound than the old 3) so the loop terminates and the caller can fail-close instead of spinning.

Edge Case: failedRecords==0 branch ignores recorded PROCESS-stage failures

📄 openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java:1903-1908
In addConfirmedPropagationCandidates, the failedRecords == 0 early-return branch propagates every entity as confirmed without consulting failedEntityIds/failedEntityFqns. getStats().getFailedRecords() reflects only SINK-stage failures, while the failure callback also fires (and enqueues to SearchIndexRetryQueue) for PROCESS-stage doc-build failures, which are tracked separately (getProcessStats()). If the catch path is entered (e.g. a flushAndAwait timeout) while sink failedRecords is 0 but a doc-build failure was recorded, that entity is both fanned out via propagation and enqueued for retry — inconsistent with the careful gating used in the failedRecords > 0 branch. Consider excluding entities already present in failedEntityIds/failedEntityFqns even when failedRecords == 0.

Bug: Advisory lock ties up a pooled connection, risks pool starvation

📄 openmetadata-service/src/main/java/org/openmetadata/service/lock/DatabaseAdvisoryLock.java:48-57 📄 openmetadata-service/src/main/java/org/openmetadata/service/lock/DatabaseAdvisoryLock.java:59-73 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java:1221-1234
withLock() opens a JDBI Handle (a pooled connection) and holds it for the entire acquire-poll window (up to LOGICAL_SUITE_LOCK_TIMEOUT = 30s) plus the whole operation. The operation itself (addTestCasesToLogicalTestSuiteLocked → flushInOneTransaction) opens a SECOND pooled connection via Entity.getJdbi().inTransaction(...). So every in-flight logical-suite publication needs two connections, and every waiter busy-polls while holding one. Under a burst of concurrent logical-suite add/delete requests this can exhaust the HikariCP pool; the lock holder may then be unable to obtain the connection it needs to commit its transaction, producing a self-deadlock/starvation rather than clean serialization. Consider acquiring the lock on the same connection/transaction that does the work, or ensuring the pool is sized well above 2×(max concurrent logical-suite ops + waiters), and shortening the wait window.

Bug: Session advisory lock can leak to pooled connection on release failure

📄 openmetadata-service/src/main/java/org/openmetadata/service/lock/DatabaseAdvisoryLock.java:78-86
MySQL GET_LOCK and Postgres pg_try_advisory_lock are SESSION-scoped and are only auto-released when the physical session ends. Because Jdbi here is backed by a pooled DataSource, handle.close() returns the still-open connection to the pool. If release() throws, the RuntimeException is only logged and swallowed, so the advisory lock stays held on that connection when it is handed to the next borrower — leaking the lock onto an unrelated operation and potentially making the key permanently unacquirable until the connection is physically closed. On release failure, evict/discard the underlying connection (or force it closed) instead of returning it to the pool, and treat a false return / exception as a reason to invalidate the connection.

Performance: Postgres lock key hashing can over-serialize distinct suites

📄 openmetadata-service/src/main/java/org/openmetadata/service/lock/DatabaseAdvisoryLock.java:90-93
The Postgres path derives the advisory lock id via hashtextextended(:lockKey, 0), collapsing the string key into a 64-bit space. Two different logical-suite ids that hash to the same value would serialize against each other unnecessarily (correctness is unaffected, only throughput). Collisions are unlikely but worth a comment noting the intentional trade-off, so the behavioral difference from the MySQL named-lock path is documented.

...and 4 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Stabilizes integration tests through deterministic outcome classification and robust cache consistency, while addressing search retry races and relationship revision fencing. Consider addressing the potential for stuck states in the context file extraction service due to retry exhaustion.

1. 💡 Edge Case: Embedding generation failure indexes doc without vector
   Files: openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/ElasticSearchBulkSink.java:1132-1146

   When an entity's state has changed (no reusable cached embedding), `enrichWithEmbedding` calls `generateEmbeddingFields`; on a transient failure (e.g. embedding provider unavailable) the catch block returns the original `json` unchanged and the entity is still indexed via a full index op — stripping/omitting the vector for that document, as the surrounding comment itself warns. The failure is only surfaced in `getVectorStats` (counted as vectorFailed), not as a reindex failure, so affected docs remain vector-less until the next reindex. Impact is bounded (self-heals on subsequent reindex since no cached embedding will be found), but consider tracking these failures more prominently or preserving any prior vector rather than silently continuing.

Options

Display: compact → Showing less information.

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

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

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 skip-pr-checks Bypass PR metadata validation check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stabilize integration-test CI outcomes and recurring races

5 participants