Skip to content

fix(diagram): make rendered diagrams byte-reproducible across runs - #1606

Open
dimitri-yatsenko wants to merge 1 commit into
masterfrom
fix/diagram-svg-reproducible
Open

dimitri-yatsenko wants to merge 1 commit into
masterfrom
fix/diagram-svg-reproducible

Conversation

@dimitri-yatsenko

Copy link
Copy Markdown
Member

Python salts string hashes per process, so iterating a set of table names gives every interpreter a different order. Several places on the rendering path did that. The drawing graph was built with networkx subgraph(), which iterates a node subset smaller than half the graph in set order however the nodes were passed in. The entity clusters were emitted by looping over a set of part names. And to_pydot() wrote each node's primary-key set into the dot source with str(). Two identical runs therefore emitted nodes, edges and clusters in different orders: a committed SVG changed on every regeneration, and a drift check that compares a regenerated figure with the committed one could not tell a real change from reshuffling.

The drawing graph is now built directly, with nodes and edges in order of full table name and the primary key stored sorted, and every other set iterated while rendering is sorted first. The notation is unchanged; only the emission order is now fixed, so committed figures need one regeneration.

A new test renders the same diagrams in separate interpreters with different PYTHONHASHSEED values and asserts that the dot source, SVG and Mermaid output are identical, on both backends.

Closes #1551.

Python salts string hashes per process, so iterating a set of table names gives every interpreter a different order. Several places on the rendering path did that. The drawing graph was built with networkx `subgraph()`, which iterates a node subset smaller than half the graph in set order however the nodes were passed in. The entity clusters were emitted by looping over a set of part names. And `to_pydot()` wrote each node's primary-key set into the dot source with `str()`. Two identical runs therefore emitted nodes, edges and clusters in different orders: a committed SVG changed on every regeneration, and a drift check that compares a regenerated figure with the committed one could not tell a real change from reshuffling.

The drawing graph is now built directly, with nodes and edges in order of full table name and the primary key stored sorted, and every other set iterated while rendering is sorted first. The notation is unchanged; only the emission order is now fixed, so committed figures need one regeneration.

A new test renders the same diagrams in separate interpreters with different PYTHONHASHSEED values and asserts that the dot source, SVG and Mermaid output are identical, on both backends.

Closes #1551.
@dimitri-yatsenko dimitri-yatsenko added this to the v2.4 milestone Oct 17, 2026
@dimitri-yatsenko dimitri-yatsenko added the bug Indicates an unexpected problem or unintended behavior label Oct 17, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Indicates an unexpected problem or unintended behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dj.Diagram SVG output is not byte-reproducible: set iteration order leaks into node emission order

1 participant