How to Handle Edge Cases When Artifacts Share URIs but Have Different Names in CMF
When multiple CMF artifacts share the same URI but have different names, the framework silently selects the last matching record and logs a warning to stderr, which can cause lineage errors and metadata drift.
The Common Metadata Framework (CMF) uses URIs as the primary identifier for artifacts. Understanding how to handle edge cases when artifacts share URIs but have different names is essential for maintaining data integrity in your ML pipelines. This guide explains the collision resolution logic implemented in cmflib/metadata_helper.py and provides concrete strategies to prevent ambiguity.
Understanding URI-Based Artifact Identification in CMF
In CMF, an artifact is uniquely identified by its URI stored in the uri column of the artifact table. The schema definition in server/app/db/dbmodels.py establishes this constraint, making the URI the canonical lookup key for all artifact operations.
When you create or link artifacts using helper functions such as create_new_artifact_event_and_attribution, link_execution_to_artifact, or link_execution_to_input_artifact, CMF first queries the metadata store for any existing records matching the supplied URI:
artifacts = store.get_artifacts_by_uri(uri) # cmflib/metadata_helper.py L495-L505
How CMF Resolves Duplicate URIs
When get_artifacts_by_uri returns multiple rows, the current implementation in cmflib/metadata_helper.py handles the collision by selecting the last artifact in the list and emitting a warning:
if len(artifacts) > 1:
print('Error: Found multiple artifacts with the same URI. Using the last one..',
file=sys.stderr) # cmflib/metadata_helper.py L500-L502
This "last-one-wins" behavior means that if two logical artifacts share the same URI but have different name fields, downstream operations will unpredictably attach to whichever record happens to be returned last by the database query.
Risks of Artifacts Sharing URIs with Different Names
Relying on the default collision handling creates several data integrity risks:
- Name ambiguity – Lineage visualizations and the CMF UI (as documented in
docs/ui/artifacts.md) assume a one-to-one mapping between URIs and display names. Duplicate URIs cause the UI to show incorrect or duplicate labels. - Metadata drift – When you update artifact properties via
store.put_artifacts, the operation uses the artifact ID retrieved from the last match. This can unintentionally overwrite properties of a different logical artifact that happens to share the same URI. - User confusion – The
log_dataset_with_versionmethod incmflib/cmf_server.pycontains an explicit TODO comment: "What happens when uri is the same but names are different," indicating that this edge case is not yet fully resolved in the API design.
Strategies to Handle Edge Cases When Artifacts Share URIs
To prevent ambiguity and ensure reliable lineage tracking, implement one of the following strategies:
Make URIs Globally Unique
Include the artifact name or a content hash in the URI string to guarantee a one-to-one mapping. For example:
uri = f"{pipeline_id}/{artifact_name}:{hash}"
This approach works with the existing CMF codebase without modification and is recommended for most production pipelines.
Use Versioned URIs
Append a version suffix to the base path when storing multiple iterations of the same logical artifact:
uri = f"{base_path}:v{version}"
This strategy preserves a clean history while maintaining uniqueness, which is useful for iterative experiments where datasets are regenerated frequently.
Implement Strict Collision Detection
Modify the helper functions to raise an exception when multiple artifacts share a URI, forcing explicit disambiguation. Replace the warning block in cmflib/metadata_helper.py with:
if len(artifacts) > 1:
raise RuntimeError(
f"Multiple artifacts ({len(artifacts)}) share URI '{uri}'. "
"Disambiguate by using unique URIs or specifying the intended artifact ID."
)
This approach provides safety guarantees but requires additional error handling in client code.
Deduplicate at Ingestion
Before creating a new artifact, check for existing records and update the name field rather than creating a new row:
existing = store.get_artifacts_by_uri(uri)
if existing:
# Update existing artifact name instead of creating new
artifact = existing[0]
artifact.name = new_name
store.put_artifacts([artifact])
else:
# Create new artifact
artifact = create_new_artifact(...)
This strategy treats the name as mutable metadata and maintains a single row per URI.
End-to-End Example: Creating Unique URIs
The following example demonstrates how to construct collision-free URIs and safely link executions to artifacts:
from cmflib import cmf, metadata_helper as mh
# 1. Build a unique URI that includes the artifact name and content hash
pipeline = "my-pipeline"
artifact_name = "training-data"
hash_val = "a1b2c3d4" # e.g., computed content hash
uri = f"{pipeline}/{artifact_name}:{hash_val}"
# 2. Log the dataset (creates the artifact if it does not exist)
artifact = cmf.log_dataset_with_version(
url="/data/train.csv",
version=hash_val,
event="output",
props={"git_repo": "https://github.com/example/repo"},
custom_properties={"stage": "raw"},
)
# 3. Link the current execution to the artifact (safe lookup)
execution_id = cmf.get_current_execution_id()
linked_artifact = mh.link_execution_to_artifact(
store=cmf.store,
execution_id=execution_id,
uri=uri,
input_name="train.csv", # human-readable identifier for the edge
event_type=mh.mlpb.Event.Type.INPUT,
)
print(f"Linked execution {execution_id} → artifact {linked_artifact.id}")
By embedding the artifact name and hash into the URI, you ensure that store.get_artifacts_by_uri(uri) returns exactly one record, eliminating the ambiguity described in cmflib/metadata_helper.py.
Summary
- CMF identifies artifacts solely by URI, and the helper functions in
cmflib/metadata_helper.pyselect the last match when collisions occur, logging a warning to stderr. - Sharing URIs between artifacts with different names causes lineage visualization errors, metadata overwrites, and UI display issues documented in
docs/ui/artifacts.md. - To handle these edge cases, generate globally unique URIs by embedding artifact names or hashes, implement versioned URI schemes, or modify the collision logic to raise exceptions instead of defaulting to the last match.
Frequently Asked Questions
What happens if two artifacts have the same URI in CMF?
When two artifacts share a URI, the helper function get_artifacts_by_uri in cmflib/metadata_helper.py returns multiple rows. The current implementation selects the last artifact in the list and prints a warning to stderr stating "Found multiple artifacts with the same URI. Using the last one.." This means lineage links and metadata updates may attach to the wrong logical artifact.
How can I prevent URI collisions in my CMF pipeline?
Prevent collisions by constructing URIs that encode uniqueness. Embed the artifact name, pipeline ID, and content hash into the URI string, such as f"{pipeline}/{name}:{hash}". Alternatively, append version suffixes like :v1 or :v2 to distinguish between iterations of the same logical asset. These patterns ensure that store.get_artifacts_by_uri returns exactly one match.
Is it safe to modify the URI generation logic in CMF?
Yes, modifying URI generation is the recommended approach for handling edge cases. The log_dataset_with_version method in cmflib/cmf_server.py contains a TODO comment indicating that the development team recognizes the need to handle cases where "uri is the same but names are different." By implementing unique URI schemes at the application level, you avoid changing core CMF library code while ensuring data integrity.
How does the CMF UI handle duplicate URIs?
The CMF UI, documented in docs/ui/artifacts.md, assumes a one-to-one mapping between URIs and display entries. When multiple artifacts share a URI, the UI typically displays only the last record or a single merged entry, effectively hiding the duplicate names from the user. This can lead to confusion when the displayed artifact name does not match the expected logical artifact in your pipeline.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →