Conversation
Two memory guards, each fixed after it failed on the same 170,935,101-triple graph. The shape-layer gate measured the wrong thing. pyshacl is in-memory, so the wrapper size-gates it -- but on the PACKAGED artifact's bytes, while what pyshacl pays for is the graph. One graph therefore landed on both sides of one 512 MiB gate purely by packaging: cottas 390,728,158 B under -> shapes attempted -> OOM nt.gz 756,594,166 B over -> skipped hdt 1,182,206,289 B over -> skipped The COTTAS run was SIGKILLed at 32.2 GB RSS on a 31 GB machine, after COTTAS decoding and rapper had both succeeded on every triple -- nothing was wrong with the artifact or the engine. The better a format compresses, the likelier it was to exhaust memory, so the guard inverted exactly where it was needed most. The authoritative gate now lives in the runner, which is the only place that knows what pyshacl will load, and counts triples, which no packaging can change. --shacl-max-triples defaults to 50M: above the largest graph the published campaign validated with shapes (17.1M) and below the one that exhausted 31 GB. A skip is recorded with its reason rather than being silent. Node chose a ceiling the machine did not. On the same graph the HDT endpoint aborted with "Reached heap limit Allocation failed - JavaScript heap out of memory" on the sample-level query while ~25 GB was free; the kernel OOM killer was never involved. Node does not size its old-space from the host, and nothing in the image, runner or wrapper set NODE_OPTIONS. --node-heap-mb now sets it for the three Comunica-backed engines. It is unset by default, so every published measurement keeps the conditions it was produced under. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI failed on five tests from the already-merged query-selection feature: 'Namespace' object has no attribute 'node_heap_mb' run_validation is driven by hand-built argparse.Namespace objects in several places -- that test suite and the mutation harness among them -- so reading a new option by plain attribute access breaks those callers at runtime rather than at import. The file already guarded progress_path and quiet with getattr for exactly this reason; --shacl-max-triples and --node-heap-mb now do the same, and the query-selection fixture carries both fields so it still exercises the real path. The regression test is behavioural rather than textual. The first version asserted on the source of run_validation and failed twice on formatting -- once on a wrapped getattr, once on the space in "getattr( args," -- which constrains how the guard is written rather than that it works. It now drives run_validation with a Namespace deliberately missing both fields and asserts the run never dies with "has no attribute". Verified by reintroducing the bug: the test fails, and passes again once restored. A regression test that has never been seen to fail has not been tested. Full suite: 958 tests, OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This branch has not been deployed
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.
Two memory guards, each fixed after it failed on the same 170,935,101-triple graph during a scale experiment.
1. The shape-layer gate measured the wrong thing
pyshaclis in-memory, so the wrapper size-gates it — but on the packaged artifact's bytes, while whatpyshaclpays for is the graph. One graph therefore landed on both sides of one 512 MiB gate purely by packaging:cottasnt.gzhdtThe COTTAS run was SIGKILLed at 32.2 GB RSS on a 31 GB machine — after COTTAS decoding and
rapperhad both succeeded on all 170,935,101 triples. Nothing was wrong with the artifact or the engine.The perverse part: the better a format compresses, the likelier it was to exhaust memory. COTTAS's headline advantage (0.52× the gzipped N-Triples) is precisely what defeated the guard. The existing comment says the gate exists because "silently trying would turn a safety net into an OOM" — which is what happened.
Fix: the authoritative gate moves into the runner, the only place that knows what
pyshaclwill actually load, and counts triples — which no packaging can change.--shacl-max-triplesdefaults to 50M: above the largest graph the published campaign validated with shapes (17.1M, the 100,000-record HG005 slice) and below the one that exhausted 31 GB (170.9M). A skip is recorded with its reason inshacl.jsonrather than being silent.The wrapper's byte gate stays as a cheap pre-filter, now documented as non-authoritative.
2. Node chose a ceiling the machine did not
On the same graph the HDT endpoint died on the sample-level query:
q01–q04had passed (~36 min each);q05returnedIncompleteRead, andq06–q13then failed instantly withConnection refused. The kernel OOM killer was never involved — ~25 GB was free. Node does not size its old-space from the host, and nothing in the image, runner or wrapper setNODE_OPTIONS.Fix:
--node-heap-mbsets--max-old-space-sizefor the three Comunica-backed engines (comunica,hdt,cottas— all viaComunicaHttpEndpointMixin). It appends to any existingNODE_OPTIONSrather than replacing it.Compatibility
--node-heap-mbis unset by default, so every published measurement keeps the conditions it was produced under. The SHACL gate is the one behaviour change: a graph above 50M triples that previously attempted shapes now records a skip instead — which is the intended outcome, since attempting it is what caused the OOM.Testing
test/test_shacl_gate_and_node_heap_unit.py, 15 tests. The two worth reading:test_the_packaging_no_longer_decides— the property the bug violated: all three packagings of one graph must reach one verdict.test_the_old_byte_gate_did_disagree_across_packagings— not a test of current behaviour but a record of the defect in the numbers that produced it, so nobody reinstates the byte gate.Plus: the campaign's largest validated graph still passes the gate, an unknown count is refused (skipping is recoverable, OOM is not),
0disables it, the boundary is inclusive, and the default provably sits between the graph that worked and the graph that died.The existing
test_validation_engines_unit.pysuite (43 tests) passes unchanged.🤖 Generated with Claude Code