Repository navigation
Fixes #34904: Keep the lineage tab responsive with very large lineage responses - #35476
mohityadav766 wants to merge 11 commits into
Conversation
… responses The table Lineage tab downloads the full getLineage response and parses it on the main thread. Large responses blocked the tab for minutes: - parseLineageData copied the edge array once per edge, counted pagination edges per node by scanning every edge, and deep-compared every pair of nodes. getUpstreamDownstreamNodesEdges scanned all nodes and edges for each visited node. Both are now linear and return the same output. - ELKLayout.layoutGraph now passes one edge per pair of nodes. The column layer passed one edge per column mapping: at 151 tables with 50 mapped columns per link, ELK exhausted a 4 GB heap, and smaller graphs spread layers thousands of pixels apart. - Lineage.tsx claims the fetch key before the request, so effects that re-run while it is in flight do not fetch and parse the same response again. It also drops a response that a newer request has superseded. With a 52,550-edge response, the longest main-thread block went from 35 s to 0.5 s, and the tab makes one getLineage call instead of three.
|
| Count | Rule |
|---|---|
| 4 | openmetadata-imports/no-impure-pure-utils |
| 1 | react-hooks/exhaustive-deps |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/Lineage/EntityLineageCanvas/EntityLineageCanvas.tsx:132:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'updateEntityData'. Either include it or remove the dependency array. |
| 🟡 | src/utils/EntityLineagePureUtils.ts:17:1 |
openmetadata-imports/no-impure-pure-utils |
Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer. |
| 🟡 | src/utils/EntityLineagePureUtils.ts:19:1 |
openmetadata-imports/no-impure-pure-utils |
Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer. |
| 🟡 | src/utils/EntityLineagePureUtils.ts:24:1 |
openmetadata-imports/no-impure-pure-utils |
Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer. |
| 🟡 | src/utils/EntityLineagePureUtils.ts:45:1 |
openmetadata-imports/no-impure-pure-utils |
Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer. |
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changedThere was a problem hiding this comment.
Let's keep this for nightly only
There was a problem hiding this comment.
Done in d630f11: pull-request and merge-queue runs (PW_ENTITY_MATRIX=representative) no longer register this suite. The nightly full suite runs it, and so does the gating run of a PR that edits the spec, through the same isRepresentativeEntityRun exemption entity-matrix specs use.
I kept it out of playwright/e2e/nightly/ on purpose: no OSS Playwright project picks up that folder, and --list returns 0 tests for the specs already there, so it would never run.
Building and parsing a 52,550-edge response is too heavy for every pull request and merge-queue run. The suite now registers only outside gating runs (PW_ENTITY_MATRIX=representative). The nightly full suite runs it, and so does the gating run of a pull request that edits the spec. Moving the spec to playwright/e2e/nightly/ was not an option: no OSS Playwright project picks up specs in that folder, so they never run.
🚦 Removed from the merge queue —
|
The entity Lineage tab now draws the getLineage graph on EntityLineageCanvas, which mounted every node. Each node subscribes to the lineage store, so a graph of 2,651 nodes took 20 s to first paint, with a 13.8 s task while store updates re-rendered every mounted node. With onlyRenderVisibleElements, as LineageMap already sets, the same graph paints in 0.9 s and the longest task is 1.0 s. Edges are drawn on the canvas layer and are unaffected.
The PNG export clones the canvas DOM, and both lineage canvases now leave off-screen nodes out of it, so an export taken while zoomed in had edges that ended at missing nodes. ExportData gains optional beforeCapture and afterCapture hooks around the capture. Lineage uses them to set renderAllNodes in the store, and both canvases drop onlyRenderVisibleElements while it is set.
After a saved edge the canvas recenters on the root, and it mounts only the nodes in view, so the new node can be off-screen and absent from the DOM.
A response that fails to parse leaves the page idle, so the heartbeat gap check alone still passed. Wait for the minimap to hold every node of the mocked response, then measure two seconds of heartbeats from there.
🚦 Removed from the merge queue —
|
Code Review ✅ Approved🟡 Medium risk · Changing lineage parsing, canvas virtualization, and ELK edge collapsing could misrepresent graph contents or omit off-screen nodes from PNG exports if handling diverges. Speeds up the table Lineage tab for large responses by linearizing parsing and graph traversal, giving ELK one edge per node pair, rendering only visible nodes on the canvas, and mounting all nodes during PNG export. No issues found. VerificationRules No rules evaluated Functional validation Not enabledAuto-approval Not enabledFor more verification details, comment OptionsDisplay: compact → Showing compact verification results. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|



Describe your changes:
Fixes #34904
The table Lineage tab downloads the full
/lineage/getLineageresponse, parses it on the main thread and, since #34551, draws it withEntityLineageCanvas. Four parts of that path scaled badly with a large response:parseLineageDatacopied the edge array once per edge, counted pagination edges for each node by scanning every edge, and deep-compared every pair of nodes.getUpstreamDownstreamNodesEdgesscanned all nodes and edges for each visited node. Both are now linear and return the same output.ELKLayout.layoutGraphreceived a parallel edge for every mapped column. At 151 tables with 50 mapped columns per link, elkjs exhausted a 4 GB heap and the tab crashed. Smaller graphs still spread layers thousands of pixels apart. ELK now gets one edge per pair of nodes; callers only read node positions.EntityLineageCanvasrendered every node, on screen or not, and each mounted node subscribes to the lineage store. With a 2,651-node response, first paint took 20 s on the Vite dev server. The canvas now setsonlyRenderVisibleElements, asLineageMapalready did, and first paint takes 0.9 s.The first version of this PR also stopped
Lineage.tsxfrom fetching and parsing the same response two or three times. #34551 fixed that onmainthe same way, by claiming the fetch key before the request and dropping superseded responses. After mergingmain, this PR keeps only its two integration tests for that behavior.Type of change:
High-level design:
getLineage?upstreamDepth=2&downstreamDepth=2&size=50returns 24 MB in 0.8 s, which matches the issue.sizeper node and keeps the last layer's edges to tables it never fetched. A busy table can therefore return about 2,500 nodes and tens of thousands of edges. I used captured responses scaled up to those sizes for the measurements below.utils/EntityLineagePureUtils.ts: pipeline lookup through a map, pagination counts in one pass over each edge map, and deep-equal dedupe only among nodes that share an id. This dedupe is equivalent touniqWith(nodes, isEqual).utils/EntityLineageNodeUtils.ts: the upstream and downstream walk indexes nodes and edges by FQN once. The DFS order and output are unchanged.utils/Lineage/Layout/ELKUtil/ELKUtil.ts:layoutGraphcollapses parallel edges. Both the legacy layout andLineageMapgo through it. The knowledge graph callsgetElk().layoutdirectly and is unaffected.components/Lineage/EntityLineageCanvas/EntityLineageCanvas.tsx:onlyRenderVisibleElements={!renderAllNodes}.LineageMap.component.tsx, which was already virtualized, reads the same flag.hooks/useLineageStore.ts: arenderAllNodesflag,falseby default.utils/Export/ExportUtils.tsandEntityExportModalProvider.interface.ts:ExportDatatakes an optionalbeforeCapture, awaited before the PNG capture, and an optionalafterCapture, which runs in afinallyso a failed capture still restores the canvas. Exports that don't set them are unchanged.components/Lineage/Lineage/Lineage.tsx: the PNG export setsrenderAllNodesinbeforeCapture, waits two animation frames for React Flow to mount the nodes, and clears the flag inafterCapture.playwright/utils/lineage.ts:connectEdgeBetweenNodesfits the view before it asserts that the new node is visible. A node that lands off-screen is no longer mounted, which broke 15DataAssetLineagetests.parseLineageDataandgetUpstreamDownstreamNodesEdgeson 33 graphs: cycles and self-loops, random graphs with missing or duplicate FQNs, duplicated node entries, pipelines whose FQN is missing, dangling edges, both pipeline view modes, the real 24 MB response and a 12.6k-edge one. Parse output was deep-equal and traversal output was reference-identical, in the same order.getLineagefetch from the table tab. Since Fixes #33865: Inline lineage editing via node and column menus #34551, the table tab renders from it.connectEdgeBetweenNodesnow does.Tests:
Use cases covered
getLineageresponse has 2,551 nodes and 52,550 edges draws every node and keeps the page responsive: no main-thread block of 5 s or more.Unit tests
I added unit tests for the new/changed logic.
Files added/updated:
src/utils/EntityLineagePureUtils.test.ts: dedupe, pipeline routing, load-more counts, and a 2,551-node / 52,550-edge parse.src/utils/EntityLineageNodeUtils.test.ts: a cycle, edges to tables that weren't fetched, and a 52,550-edge walk.src/utils/Lineage/Layout/ELKUtil/ELKUtil.test.ts: elkjs receives one edge per node pair.src/utils/Lineage/Layout/ELKUtil/ELKUtil.integration.test.ts(new): real elkjs, 50 parallel column edges per link, checks the layer gap.src/utils/Export/ExportUtils.test.tsx: the capture starts only afterbeforeCaptureresolves and is followed byafterCapture, andafterCapturealso runs when the capture fails.src/components/Lineage/Lineage/Lineage.integration.test.tsx: in-flight dedupe and the superseded response.src/utils/EntityLineageUtils.test.tsx: removed two tests that only asserteduniqWithwas called. The new dedupe test covers that behavior with real lodash.Each new test fails on the old code:
stale.tableshown¹ Measured against
mainbefore Fixes #33865: Inline lineage editing via node and column menus #34551 landed.mainpasses both now.Coverage on changed lines: 100%, 137 of 137 executable changed lines in the six source files with logic changes:
EntityLineagePureUtils.ts60,EntityLineageNodeUtils.ts38,ELKUtil.ts14,Lineage.tsx13,ExportUtils.ts9 anduseLineageStore.ts3. The two canvas components only pass the store flag to a React Flow prop; the Playwright runs below cover them.Jest suites run locally: 47 suites, 708 tests passed. These cover
src/utils/Export,src/components/Entity/EntityExportModalProvider,src/components/Lineage,src/components/Entity/EntityLineage,src/utils/EntityLineage*,src/utils/Lineage,src/hooks/useLineageStore.test.tsandsrc/rest/lineageAPI.test.ts.tsc --noEmitonsrcand on the Playwright project: no new errors. The only error in a changed file,playwright/utils/lineage.tsline 645 inconnectEdgeBetweenNodesViaAPI, is onmaintoo..github/scripts/tests/test_playwright_ci_planning.py: 123 passed, 3 failed. The same three fail withmain's impact maps.Backend integration tests
Ingestion integration tests
Playwright (UI) tests
I added Playwright E2E tests under
openmetadata-ui/.../ui/playwright/for UI changes.Files added:
playwright/e2e/Pages/Lineage/LineageLargeResponse.spec.ts(@basic). It serves a 2,551-node, 52,550-edgegetLineageresponse to a real table's Lineage tab. It first waits until the minimap holds all 2,551 nodes, so a response that fails to parse fails the test. It then fails if the longest gap between 50 ms main-thread heartbeats reaches 5 s.Nightly only, per review. Pull-request and merge-queue runs (
PW_ENTITY_MATRIX=representative) do not register the suite. The nightly full suite runs it, and so does the gating run of a pull request that edits the spec, through theisRepresentativeEntityRunexemption that entity-matrix specs use.The spec stays out of
playwright/e2e/nightly/on purpose: no OSS Playwright project picks up specs in that folder, so they never run.--listreturns 0 tests for the specs already there.Results on the local stack:
parseLineageDatathrow (temporary copy of the spec)maincanvas, not virtualizedmainbefore this PR and Fixes #33865: Inline lineage editing via node and column menus #34551playwright/utils/lineage.ts:connectEdgeBetweenNodesfits the view before checking the new node. With it,DataAssetLineagepasses again: 100 passed, and its 1 failure is the sample-data test below..github/playwright/impact-map.json: the lineage mapping now also coverssrc/utils/EntityLineage*,src/utils/Lineage/**andsrc/hooks/useLineageStore.ts. A new export mapping sendsEntityExportModalProvider/**andsrc/utils/Export/**to the six specs that export:ClassificationImportExport,LineageExportPNGSnapshot,SampleDataTableOperations,CSVImportWithQuotesAndCommas,GlossaryImportExportandUsers, in thechromiumandImportExportprojects. Without these, the files were unmapped, and CI would have escalated them to the full suite.impact-map.generated.jsonwas regenerated.--checkpasses, also on this branch merged with today'smain.Local Playwright run: FAILED (4 failures: 3 need sample data that this local stack does not load, and 1 flake passed on rerun; see below)
323d44dd6904(baseorigin/main). The only uncommitted file was the untrackedpaseo.json, which is not part of this PR.--workers=2playwright/e2e/Features/ClassificationImportExport.spec.tsplaywright/e2e/Features/Dashboards.spec.tsplaywright/e2e/Features/LineageExportPNGSnapshot.spec.tsplaywright/e2e/Features/SampleDataTableOperations.spec.tsplaywright/e2e/Flow/LineageSettings.spec.tsplaywright/e2e/Flow/Navbar.spec.tsplaywright/e2e/Pages/CSVImportWithQuotesAndCommas.spec.tsplaywright/e2e/Pages/DataMarketplace.spec.tsplaywright/e2e/Pages/GlossaryImportExport.spec.tsplaywright/e2e/Pages/Lineage/DataAssetLineage.spec.tsplaywright/e2e/Pages/Lineage/LineageControls.spec.tsplaywright/e2e/Pages/Lineage/LineageFilters.spec.tsplaywright/e2e/Pages/Lineage/LineageInteraction.spec.tsplaywright/e2e/Pages/Lineage/LineageLargeResponse.spec.tsplaywright/e2e/Pages/Lineage/LineageNodePagination.spec.tsplaywright/e2e/Pages/Lineage/LineageRightPanel.spec.tsplaywright/e2e/Pages/Lineage/PlatformLineage.spec.tsplaywright/e2e/Pages/Policies.spec.tsplaywright/e2e/Pages/Users.spec.tsplaywright/e2e/auth.setup.tsplaywright/e2e/entity-data.setup.tsplaywright/e2e/entity-data.teardown.tsplaywright/e2e/Flow/Tour.spec.tsFailures and gaps:
DataAssetLineage.spec.ts› Temp lineage table nodes: throwsSample entity 'sample_data.ecommerce_db.shopify.raw_order' not found.Dashboards.spec.ts› Data Model › expand / collapse after updating nested fields: waits for asample_supersetdata model row.LineageExportPNGSnapshot.spec.ts› exported PNG includes edge lines between nodes: its lineage URL points at a sample-data table, sogetLineagenever fires locally, and the test times out waiting for it. I checked the same export path by hand (Manual testing, step 5). The merge queue loads sample data and runs it.LineageFilters.spec.ts› Impact Analysis › downstream count: a flake. It passed on rerun, and the spec passed 15 of 15 in the previous run.Flow/Tour.spec.tsshows "not run" because it is quarantined (@quarantine), so no project selects it.Also run, for canvas specs outside the impacted set:
Features/ImpactAnalysis,Features/IncidentManager,Features/DomainIsolation/DomainLineageIsolationandPages/ExplorePageRightPanel, with 264 passed, 1 failed. The failure is inIncidentManager: its test-suite pipeline never produced a run, because this local stack has no ingestion runner. With these, every spec I found that drivesEntityLineageCanvasorLineageMapran, except the quarantined Tour. The specs that match canvas selectors but were not run use other canvases (threeWorkflowsspecs andInputOutputPorts), or create lineage through the API and never open the canvas (OntologyDataExploreRdf, andPagination, which pages the Impact Analysis list).After this run,
aa3026824dchanged onlyLineageLargeResponse.spec.ts, adding the minimap check. Run alone afterwards, it passed.Environment: this branch's UI on Vite, against a server built from this branch at
c5c83a4ae0, after the first merge ofmain.mainhas had no lineage backend changes since.Command
cd openmetadata-ui/src/main/resources/ui npx playwright test playwright/e2e/Features/ClassificationImportExport.spec.ts playwright/e2e/Features/Dashboards.spec.ts playwright/e2e/Features/LineageExportPNGSnapshot.spec.ts playwright/e2e/Features/SampleDataTableOperations.spec.ts playwright/e2e/Flow/LineageSettings.spec.ts playwright/e2e/Flow/Navbar.spec.ts playwright/e2e/Flow/Tour.spec.ts playwright/e2e/Pages/CSVImportWithQuotesAndCommas.spec.ts playwright/e2e/Pages/DataMarketplace.spec.ts playwright/e2e/Pages/GlossaryImportExport.spec.ts playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts playwright/e2e/Pages/Lineage/LineageControls.spec.ts playwright/e2e/Pages/Lineage/LineageFilters.spec.ts playwright/e2e/Pages/Lineage/LineageInteraction.spec.ts playwright/e2e/Pages/Lineage/LineageLargeResponse.spec.ts playwright/e2e/Pages/Lineage/LineageNodePagination.spec.ts playwright/e2e/Pages/Lineage/LineageRightPanel.spec.ts playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts playwright/e2e/Pages/Policies.spec.ts playwright/e2e/Pages/Users.spec.ts --workers=2Manual testing performed
openmetadata/server:2.0.5, and a main-equivalent server (build74a8d70e0d, whose lineage code matchedmainat the time) on a copy of the same database.getLineageat depth 2/2 withsize=50returned 24 MB (151 nodes, 1,375 edges, 625 of them to tables that weren't fetched).getLineagewith scaled copies of the captured response. These runs predate Fixes #33865: Inline lineage editing via node and column menus #34551:getLineagefetched twicegetLineagecallsmainwith Fixes #33865: Inline lineage editing via node and column menus #34551, I moved to a server built from this branch atc5c83a4ae0and served a 2,651-node response to the table tab, with the UI on Vite:main, not virtualizedbeforeCapturehook disabled, only that 1 node was mounted during the capture.UI screen recording:
Not applicable. This is a performance fix with no visual change. The canvas looks the same; it only leaves off-screen nodes out of the DOM, and a PNG export still holds every node (Manual testing, step 5). The measurements above stand in for a recording.
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: not applicable, no schema changes.
For a UI feature or feature task: not applicable, this is a bug fix (see UI screen recording).
I have added tests (unit / integration / Playwright as applicable) and listed them above.
Decision check: no record needed. The new contracts are documented where they are defined:
ELKLayout.layoutGraphreturns collapsed edges (on the method), andExportData.beforeCapture/afterCapture(on the interface).I have added a test that covers the exact scenario we are fixing. For complex issues, comment the issue number in the test for future reference.
🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no new actionable issues were established.Summary
This PR reduces work when parsing, walking, laying out, and displaying large lineage graphs.
Reviews (2) · Last reviewed commit: "Fail the large-lineage spec when the gra..." · Reviewed by Greptile