Exclude runId from DatasetVersion hash to prevent version explosion - #3112
Open
kalra-mohit wants to merge 1 commit into
Open
Exclude runId from DatasetVersion hash to prevent version explosion#3112kalra-mohit wants to merge 1 commit into
kalra-mohit wants to merge 1 commit into
Conversation
…arquezProject#3082) Utils.newDatasetVersionFor(DatasetVersionData) included the run's UUID as an input to the SHA/UUID-based hash used to compute a dataset's Version. Since a DatasetVersion is meant to identify the *content* of a dataset (namespace, source, physical name, schema fields, lifecycle state), including runId meant a brand new DatasetVersion (and corresponding dataset_versions / dataset_versions_field_mapping rows) was created on every single run that wrote to a dataset - even when the dataset's schema/content was completely unchanged from the previous run. For datasets written by high-frequency jobs this causes unbounded growth of dataset version rows and the query timeouts described in the issue. This method is the single choke point used by all three DatasetVersion-hashing call sites (OpenLineageDao, RunDao, and DatasetVersionDao), so removing runId from the hash here fixes the issue everywhere a DatasetVersion is minted, without needing to touch any call sites or public method signatures. Adds a regression test, testDatasetVersionEqualAcrossDifferentRunsWithSameDatasetContent, which asserts that two DatasetVersions computed from identical dataset content but different runIds are equal. Verified this test fails before the fix (two different UUIDs) and passes after (same UUID). Fixes MarquezProject#3082 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Mohit Kalra <mohit2494@gmail.com>
kalra-mohit
force-pushed
the
fix/3082-remove-runid-from-dataset-version-hash
branch
from
July 22, 2026 01:35
83d00a7 to
c890432
Compare
kalra-mohit
marked this pull request as ready for review
July 22, 2026 02:30
Author
|
@merobi-hub whenever you get a chance to take a look, happy to address any feedback! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DatasetVersionis supposed to identify the content of a dataset at a point in time — namespace, source, physical name, schema, lifecycle state. But the hash that produces its UUID (Utils.newDatasetVersionFor(DatasetVersionData), the single private method backing all three public overloads used byOpenLineageDao,RunDao, andDatasetVersionDao) also folded indata.getRunId():Since
runIdis unique per run, every single run that writes to a dataset mints a brand newDatasetVersion— and newdataset_versions/dataset_versions_field_mappingrows — even when the dataset's content is byte-for-byte identical to the previous run. High-frequency jobs writing the same dataset over and over blow this table up, which is exactly the query timeouts described in #3082.flowchart TD W["Run writes dataset X (same schema/content as the previous run)"] W --> H1["Old hash input: namespace + source + name + physicalName + schema + fields + lifecycleState + runId"] H1 --> V1["Different UUID every run dataset_versions row explosion"] W --> H2["New hash input: namespace + source + name + physicalName + schema + fields + lifecycleState"] H2 --> V2["Same UUID as previous run ON CONFLICT(version) DO UPDATE fires"] V2 --> T["Trade-off: dataset_versions.run_uuid flips to the most recent writer of that content"]Fix: dropped
data.getRunId()from the hash. It's now purely content-based, matching what the docs sayDatasetVersionis, and matching the sibling methodnewDatasetSchemaVersionFor, which never includedrunIdin the first place. BecausenewDatasetVersionFor(DatasetVersionData)is the single choke point for all three overloads, this fixes it everywhere aDatasetVersiongets minted — no call sites changed,runIdis still accepted as a parameter and still recorded viadataset_versions.run_uuid, it's just out of the content hash now.Testing: added
UtilsTest#testDatasetVersionEqualAcrossDifferentRunsWithSameDatasetContent— builds twoDatasetVersions from identical content but two different, randomly generatedrunIds and asserts they're equal. Fails pre-fix (two different UUIDs), passes post-fix. Full unit suite is green:./gradlew :api:testUnit, 119 tests.I wasn't able to run the Postgres-backed DAO/integration tests (
RunDaoTest,DatasetDaoTest, etc.) locally — Testcontainers in my sandbox can't negotiate with the local Docker Engine (the bundleddocker-javaclient defaults to API v1.32, the engine here needs >= v1.40). Not related to this change, just a local environment gap. Would appreciate CI running those.One trade-off I want to flag explicitly
DatasetVersionDao.upsert(...)already hasON CONFLICT(version) DO UPDATE SET run_uuid = EXCLUDED.run_uuid, .... Before this fix,runIdbeing in the hash guaranteed every write produced a uniqueversion, so that conflict branch never actually fired for OpenLineage-ingested writes.After this fix, two different runs writing identical content now collide on
versionand hit thatDO UPDATE. Sincedataset_versionshas one row per distinct version, the update reassigns that row'srun_uuidto whichever run most recently wrote the content.That matters because
RunDao'soutput_versions(surfaced viaGET /runs/{id}) is essentially a one-to-one join ondv.run_uuid = r.uuid. So: Run A writes content X, itsoutput_versionsincludes that version. Run B later writes the exact same content X — the shared row'srun_uuidflips to Run B, and Run A'soutput_versionssilently loses that dataset, even though Run A genuinely produced it at the time.I think this is an acceptable trade-off — deduping identical content is the whole point of the fix, and keeping
runIdin the hash is the bug we're fixing here. It only affects "which run(s) produced this exact version" provenance for content that's genuinely unchanged across runs, not the correctness of the version itself or its schema/fields.A complete fix would need
dataset_versions.run_uuidto model a proper one-to-many relationship (one version legitimately "produced" by many runs that happened to write the same content) — something like a many-to-many run-to-dataset-version join table, mirroring howruns_input_mappingalready works for inputs, plus updates toRunDao.output_versionsand the API contract. That's bigger than this PR; happy to scope it as a follow-up if maintainers want it.Question for maintainers: is the shrinking-
output_versions-over-time behavior an acceptable trade-off given what this fixes, or would you rather pair this with a minimal many-to-many link now?Fixes #3082.