Skip to content

Add Invalidation for Lineage Cache during updates#27606

Merged
harshach merged 2 commits intomainfrom
invalidate-lineage-cache
Apr 22, 2026
Merged

Add Invalidation for Lineage Cache during updates#27606
harshach merged 2 commits intomainfrom
invalidate-lineage-cache

Conversation

@mohityadav766
Copy link
Copy Markdown
Member

@mohityadav766 mohityadav766 commented Apr 21, 2026

Describe your changes:

Fixes issue observer in AUTs failing with DataASsetLineageSpec.ts errors

I worked on ... because ...

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.

Summary by Gitar

  • Test coverage:
    • Added unit tests in GuavaLineageGraphCacheTest to verify selective cache eviction based on FQNs within graph nodes and edge endpoints.
    • Expanded AbstractLineageGraphBuilderTest to validate that invalidateLineageCacheForFqn correctly removes relevant cached results while preserving unrelated entries.

This will update automatically on new commits.

@mohityadav766 mohityadav766 self-assigned this Apr 21, 2026
Copilot AI review requested due to automatic review settings April 21, 2026 17:59
@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Apr 21, 2026
Comment on lines +1274 to +1288
private EntityReference resolveRefForCacheInvalidation(String entityType, String id) {
EntityReference ref = new EntityReference().withId(UUID.fromString(id));
if (nullOrEmpty(entityType)) {
return ref;
}
try {
EntityReference resolved =
Entity.getEntityReferenceById(entityType, UUID.fromString(id), Include.ALL);
return ref.withType(entityType).withFullyQualifiedName(resolved.getFullyQualifiedName());
} catch (Exception e) {
LOG.debug(
"Could not resolve FQN for {}:{} during lineage cache invalidation", entityType, id);
return ref.withType(entityType);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Performance: Repeated DB lookups + full cache scans per edge in bulk delete

In deleteLineageFromSearch(List<EntityRelationshipObject>) (line 1264), each relation triggers two resolveRefForCacheInvalidation calls (potential DB hit each) and then invalidateLineageCacheForEdge, which scans the entire cache twice (once per FQN). For a bulk delete of N edges this is O(N × CacheSize) cache scans plus up to 2N DB queries. With the default cache size of 100 this is acceptable for small batches, but for large lineage deletions (e.g., dropping a pipeline source with many edges) it could be noticeably slow. Consider collecting all distinct FQNs first, then doing a single cache scan pass, or simply calling invalidateAllLineageCache() when the relation count exceeds a threshold.

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds explicit lineage graph cache invalidation when lineage edges are added or removed, aiming to prevent stale lineage results being served after updates (addressing AUT failures related to lineage).

Changes:

  • Introduces a SearchClient.invalidateLineageCache(fqn) hook and implements it for Elasticsearch/OpenSearch clients.
  • Extends lineage cache abstractions with targeted invalidation (invalidateIfGraphContains) and implements selective eviction for the Guava lineage cache.
  • Triggers cache invalidation from LineageRepository after lineage updates/removals in search.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchClient.java Implements invalidateLineageCache delegation to the lineage graph builder
openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchClient.java Implements invalidateLineageCache delegation to the lineage graph builder
openmetadata-service/src/main/java/org/openmetadata/service/search/SearchClient.java Adds a default invalidateLineageCache API for cache eviction
openmetadata-service/src/main/java/org/openmetadata/service/search/lineage/LineageGraphCache.java Adds invalidateIfGraphContains to support targeted eviction (default falls back to full invalidation)
openmetadata-service/src/main/java/org/openmetadata/service/search/lineage/GuavaLineageGraphCache.java Implements selective eviction by scanning cached graphs for FQN references
openmetadata-service/src/main/java/org/openmetadata/service/search/lineage/AbstractLineageGraphBuilder.java Replaces TTL-only invalidation with targeted eviction via the cache
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java Calls cache invalidation after lineage add/delete operations; resolves FQNs for bulk deletions

Comment on lines +133 to +148
public void invalidateIfGraphContains(String fqn) {
if (fqn == null || fqn.isEmpty() || cache.size() == 0) {
return;
}
java.util.List<LineageCacheKey> toEvict = new java.util.ArrayList<>();
for (java.util.Map.Entry<LineageCacheKey, SearchLineageResult> entry :
cache.asMap().entrySet()) {
if (graphReferencesFqn(entry.getKey(), entry.getValue(), fqn)) {
toEvict.add(entry.getKey());
}
}
if (!toEvict.isEmpty()) {
cache.invalidateAll(toEvict);
LOG.debug("Cache INVALIDATE_FQN fqn={} evicted {} entries", fqn, toEvict.size());
}
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

invalidateIfGraphContains introduces new eviction behavior but there is no unit test coverage verifying that only cache entries whose root/nodes/edges reference the given FQN are evicted (and unrelated entries remain cached). Adding a focused test here would help prevent regressions, especially since eviction logic now inspects nodes and edge endpoints.

Copilot uses AI. Check for mistakes.
Comment on lines +137 to +170
java.util.List<LineageCacheKey> toEvict = new java.util.ArrayList<>();
for (java.util.Map.Entry<LineageCacheKey, SearchLineageResult> entry :
cache.asMap().entrySet()) {
if (graphReferencesFqn(entry.getKey(), entry.getValue(), fqn)) {
toEvict.add(entry.getKey());
}
}
if (!toEvict.isEmpty()) {
cache.invalidateAll(toEvict);
LOG.debug("Cache INVALIDATE_FQN fqn={} evicted {} entries", fqn, toEvict.size());
}
}

private boolean graphReferencesFqn(LineageCacheKey key, SearchLineageResult result, String fqn) {
if (fqn.equals(key.getFqn())) {
return true;
}
if (result == null) {
return false;
}
if (result.getNodes() != null && result.getNodes().containsKey(fqn)) {
return true;
}
return edgeMapReferencesFqn(result.getUpstreamEdges(), fqn)
|| edgeMapReferencesFqn(result.getDownstreamEdges(), fqn);
}

private boolean edgeMapReferencesFqn(
java.util.Map<String, org.openmetadata.schema.api.lineage.EsLineageData> edges, String fqn) {
if (edges == null || edges.isEmpty()) {
return false;
}
for (org.openmetadata.schema.api.lineage.EsLineageData edge : edges.values()) {
if (edge.getFromEntity() != null
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

New code uses fully-qualified java.util.* and org.openmetadata.* types inside method signatures and local declarations (e.g., java.util.Map, org.openmetadata.schema.api.lineage.EsLineageData). This is inconsistent with the rest of the file’s import style and makes the new logic harder to read/maintain; please add imports and use the simple class names instead.

Copilot uses AI. Check for mistakes.
Comment on lines +449 to +452
searchClient.invalidateLineageCache(from.getFullyQualifiedName());
}
if (to != null) {
searchClient.invalidateLineageCache(to.getFullyQualifiedName());
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

invalidateLineageCacheForEdge can end up calling invalidateLineageCache(null) when FQN resolution fails (e.g., resolveRefForCacheInvalidation returns a ref without fullyQualifiedName). It currently works for the ES/OS clients because the builder guards against null/empty, but it couples callers to that implementation detail; prefer skipping the call when fullyQualifiedName is null/empty to keep the API contract safer for other SearchClient implementations.

Suggested change
searchClient.invalidateLineageCache(from.getFullyQualifiedName());
}
if (to != null) {
searchClient.invalidateLineageCache(to.getFullyQualifiedName());
String fromFqn = from.getFullyQualifiedName();
if (!nullOrEmpty(fromFqn)) {
searchClient.invalidateLineageCache(fromFqn);
}
}
if (to != null) {
String toFqn = to.getFullyQualifiedName();
if (!nullOrEmpty(toFqn)) {
searchClient.invalidateLineageCache(toFqn);
}

Copilot uses AI. Check for mistakes.
Comment on lines +1274 to +1287
private EntityReference resolveRefForCacheInvalidation(String entityType, String id) {
EntityReference ref = new EntityReference().withId(UUID.fromString(id));
if (nullOrEmpty(entityType)) {
return ref;
}
try {
EntityReference resolved =
Entity.getEntityReferenceById(entityType, UUID.fromString(id), Include.ALL);
return ref.withType(entityType).withFullyQualifiedName(resolved.getFullyQualifiedName());
} catch (Exception e) {
LOG.debug(
"Could not resolve FQN for {}:{} during lineage cache invalidation", entityType, id);
return ref.withType(entityType);
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

resolveRefForCacheInvalidation performs a per-edge Entity.getEntityReferenceById(...) lookup inside a loop over relations. For large lineage deletions (e.g., deleting by source/pipeline), this becomes an N+1 pattern with potentially many DB calls and can noticeably slow down the operation. Consider resolving references in batch (group IDs by type and use Entity.getEntityReferencesByIds(...)) or changing the DAO query to also return FQNs when available, so cache invalidation doesn’t require per-row fetches.

Copilot uses AI. Check for mistakes.
@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented Apr 21, 2026

Code Review 👍 Approved with suggestions 0 resolved / 1 findings

Implements lineage cache invalidation during updates to ensure data consistency. Consider optimizing bulk delete operations to avoid repeated database lookups and redundant full cache scans per edge.

💡 Performance: Repeated DB lookups + full cache scans per edge in bulk delete

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java:1264-1272 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java:1274-1288

In deleteLineageFromSearch(List<EntityRelationshipObject>) (line 1264), each relation triggers two resolveRefForCacheInvalidation calls (potential DB hit each) and then invalidateLineageCacheForEdge, which scans the entire cache twice (once per FQN). For a bulk delete of N edges this is O(N × CacheSize) cache scans plus up to 2N DB queries. With the default cache size of 100 this is acceptable for small batches, but for large lineage deletions (e.g., dropping a pipeline source with many edges) it could be noticeably slow. Consider collecting all distinct FQNs first, then doing a single cache scan pass, or simply calling invalidateAllLineageCache() when the relation count exceeds a threshold.

🤖 Prompt for agents
Code Review: Implements lineage cache invalidation during updates to ensure data consistency. Consider optimizing bulk delete operations to avoid repeated database lookups and redundant full cache scans per edge.

1. 💡 Performance: Repeated DB lookups + full cache scans per edge in bulk delete
   Files: openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java:1264-1272, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java:1274-1288

   In `deleteLineageFromSearch(List<EntityRelationshipObject>)` (line 1264), each relation triggers two `resolveRefForCacheInvalidation` calls (potential DB hit each) and then `invalidateLineageCacheForEdge`, which scans the entire cache twice (once per FQN). For a bulk delete of N edges this is O(N × CacheSize) cache scans plus up to 2N DB queries. With the default cache size of 100 this is acceptable for small batches, but for large lineage deletions (e.g., dropping a pipeline source with many edges) it could be noticeably slow. Consider collecting all distinct FQNs first, then doing a single cache scan pass, or simply calling `invalidateAllLineageCache()` when the relation count exceeds a threshold.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (18 flaky)

✅ 3693 passed · ❌ 0 failed · 🟡 18 flaky · ⏭️ 89 skipped

Shard Passed Failed Flaky Skipped
✅ Shard 1 481 0 0 4
🟡 Shard 2 653 0 3 7
🟡 Shard 3 663 0 3 1
🟡 Shard 4 646 0 2 27
🟡 Shard 5 610 0 1 42
🟡 Shard 6 640 0 9 8
🟡 18 flaky test(s) (passed on retry)
  • Features/BulkEditEntity.spec.ts › Database service (shard 2, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/Glossary/GlossaryWorkflow.spec.ts › should start term as Draft when glossary has reviewers (shard 2, 1 retry)
  • Features/Workflows/WorkflowOssRestrictions.spec.ts › schedule-type-select is disabled in OSS (shard 3, 1 retry)
  • Flow/PersonaDeletionUserProfile.spec.ts › User profile loads correctly before and after persona deletion (shard 3, 1 retry)
  • Flow/PersonaFlow.spec.ts › Set default persona for team should work properly (shard 3, 1 retry)
  • Pages/Customproperties-part2.spec.ts › entityReferenceList shows item count, scrollable list, no expand toggle (shard 4, 1 retry)
  • Pages/Entity.spec.ts › Tier Add, Update and Remove (shard 4, 1 retry)
  • Pages/Glossary.spec.ts › Add and Remove Assets (shard 5, 2 retries)
  • Pages/Lineage/DataAssetLineage.spec.ts › verify create lineage for entity - Table (shard 6, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › verify create lineage for entity - Search Index (shard 6, 2 retries)
  • Pages/Lineage/DataAssetLineage.spec.ts › verify create lineage for entity - Api Endpoint (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: searchIndex (shard 6, 1 retry)
  • Pages/ODCSImportExport.spec.ts › Multi-object ODCS contract - object selector shows all schema objects (shard 6, 1 retry)
  • Pages/Users.spec.ts › Create and Delete user (shard 6, 1 retry)
  • Pages/Users.spec.ts › Permissions for table details page for Data Consumer (shard 6, 1 retry)
  • Pages/Users.spec.ts › Check permissions for Data Steward (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

@harshach harshach merged commit 557626f into main Apr 22, 2026
52 of 54 checks passed
@harshach harshach deleted the invalidate-lineage-cache branch April 22, 2026 04:38
mohityadav766 added a commit that referenced this pull request Apr 22, 2026
* Add Invalidation for Lineage Cache during updates

* Add test

(cherry picked from commit 557626f)
jatinmasaram pushed a commit to jatinmasaram/OpenMetadata that referenced this pull request May 2, 2026
* Add Invalidation for Lineage Cache during updates

* Add test
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.

3 participants