Skip to content

fix(render): stop an oversized chart from aborting the build - #129

Merged
kannandreams merged 1 commit into
mainfrom
fix/mark-budget
Sep 3, 2026
Merged

fix(render): stop an oversized chart from aborting the build#129
kannandreams merged 1 commit into
mainfrom
fix/mark-budget

Conversation

@kannandreams

Copy link
Copy Markdown
Collaborator

Closes the crash found while measuring render cost: glyf build did not fail on a chart too large to draw, it died.

The problem

glyf hands every mark to a renderer that holds them all in memory at once. When that memory runs out the process is killed rather than returning an error — exit 133, a stack trace from inside the renderer where a traceback should be, no build output, and in a multi-chart build no way to tell which query was responsible.

Measured on v0.5.0, one-series line chart, 800x400:

rows result render SVG
400,000 ok 11.5 s 110.7 MB
500,000 ok 17.7 s 138.8 MB
600,000 abort

execution.max_rows is unset by default, so nothing stood in front of it.

The fix

render.max_marks, default 500000, checked after execution and before rendering — beside the existing max_rows check in render_project, which is the same shape of guard:

visualisations/events.ggsql would draw 612000 marks, more than the 500000 glyf
will render. The renderer holds every mark in memory at once and the whole
build dies when it runs out, so glyf stops first. Aggregate the query, or raise
render.max_marks if this machine can take it.

Unlike max_rows it applies with nothing configured, which is the point — the projects that hit this are the ones that never set a bound. null removes it, and the docs say plainly what that gives up.

The default is the largest single-series chart observed to render, on one machine. The real limit belongs to the renderer's memory rather than to glyf, so it is documented as movable rather than as a constant.

Not in this PR

No downsampling. A chart over the budget fails; it is not redrawn smaller. That is deliberate — reducing marks changes what the artifact publishes (aggregates instead of rows), which is a separate decision.

Testing

uv run pytest — 404 passed, 6 skipped. 13 new tests covering the guard firing, the message naming the chart and the setting, no artifacts written when it fires, a chart exactly at the budget still rendering, the default applying with no config, validate mode never tripping it, and config parsing including null versus absent.

No test renders a chart large enough to actually crash: that costs minutes and a 138 MB artifact, and bench/downsampling.py ceiling already covers it.

Verified through the CLI as well — exit 1 with the named error above the budget, exit 0 and artifacts written at it.

Docs

render.max_marks in the Render table, a section explaining why it defaults on where max_rows does not, and a table contrasting the two bounds so they are not mistaken for each other.

`glyf build` did not fail when a chart was too large to draw -- it died.
glyf hands every mark to a renderer that holds them all in memory at
once, and when that memory runs out the process is killed rather than
returning an error: exit 133, a stack trace from inside the renderer
where a traceback should be, no build output, and in a multi-chart build
no way to tell which query was responsible. Nothing stood in front of it,
because `execution.max_rows` is unset by default.

`render.max_marks` bounds what a chart may draw and, unlike `max_rows`,
applies to a project that has configured nothing. A chart over the budget
now fails the way anything else does, naming the chart and saying what to
change. The check sits beside the `max_rows` one, after execution and
before rendering, so nothing is drawn and no artifact is left behind.

The default of 500,000 is the largest single-series chart observed to
render, on one machine. The real limit belongs to the renderer's memory
rather than to glyf, so it moves with the platform and the chart: it is
raisable, and `null` removes it along with the only thing standing in
front of the abort.

Measured with `bench/downsampling.py ceiling`, which is also why no test
renders a chart that large -- proving the crash costs minutes and a
138 MB artifact. The tests prove the guard fires, that it fires before
the renderer is reached, and that validate mode never trips it.
@kannandreams
kannandreams merged commit b65a101 into main Sep 3, 2026
7 checks passed
@kannandreams
kannandreams deleted the fix/mark-budget branch September 3, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant