perf(pypangraph): validate loaded graphs with jsonschema-rs - #201
perf(pypangraph): validate loaded graphs with jsonschema-rs#201ivan-aksamentov wants to merge 3 commits into
Conversation
Schema validation dominates the cost of loading a graph: on a mid-sized graph the pure-Python jsonschema pass takes seconds while parsing takes tens of milliseconds, because the validator walks every node, edit and position in interpreted Python. Validate with jsonschema-rs, a Rust-backed validator for the same schema, compiled once at import and reused for every load. The accepted and rejected graphs are unchanged; only the engine differs. This makes the load bounded by decompression and parsing rather than validation.
Lock in which malformed graphs the loader rejects (missing required fields, wrong types, out-of-range values, bad strand enum), so the accept/reject contract holds independently of the validation engine.
Document how a graph is parsed, validated and constructed, and why validation uses jsonschema-rs. Add a benchmark that times each load phase and every validation engine present, so the numbers can be reproduced on one machine.
Heads up: CI is red here, and it's not something we can pin our way out of. What's happening:
Two ways forward:
Given |
Alternative PRs (mutually exclusive, merge one):
Problem
Pangraph.from_jsonvalidates every loaded graph against the JSON schema generated from the Rust types before building the object. On real graphs that validation, not parsing, dominates load time. Issue #200 reports the slowdown.On the benchmark graph below, parsing the JSON takes about 40 ms while the pure-Python
jsonschemapass takes about 2 seconds: the validator walks every node, edit and position in interpreted Python.This change
Validate with
jsonschema-rs, a Rust-backed validator for the same schema, compiled once at import and reused for every load. Nothing else moves: the parsed value stays a dict, the collections and the whole public API are untouched, and the set of accepted and rejected graphs is identical. The load becomes bounded by decompression and parsing instead of validation.Why this is the one to merge
Pangraph, its collections, and every downstream consumer keep working against dicts exactly as before. The only observable difference is speed.jsonschema-rsenforces the identical schema (required fields, types, thestrandenum, non-negative integers), so a graph that loaded before still loads, and a graph that was rejected before is still rejected.If the goal is to remove the bottleneck now with the least surface area, this is the strongest choice.
Benchmark
Fixture:
packages/pypangraph/tests/data/staph.json.gz(664 blocks, 6817 nodes, 15 paths; 1.81 MB compressed, 9.72 MB decoded). Median of 7 runs on one machine in the project Python container, measured bypackages/pypangraph/benchmarks/bench_load. Correctness parity was verified for every engine: each accepts the valid graph and rejects missing-field, wrong-type, negative-value and bad-strand mutations.Full load (parse and validate together):
json+jsonschema)Per-phase breakdown. Decompression and JSON parsing are shared; this PR changes only the validation step.
Shared (unchanged by this PR):
Validation step (what this PR changes):
Methodology notes:
jsonschemavalidator does not help; the cost is the interpreted traversal, not validator construction.fastjsonschemawas rejected: it errors on the schema'sformat: uintannotation.format: uintis a decorative annotation; the non-negative range is enforced byminimum: 0. No engine asserts on the format string.Conclusion: validation is about 98% of load time, and every candidate removes it.
jsonschema-rsgives the largest single-step reduction (about 200x on the validation step) while keeping the loader and its data model unchanged.The three alternatives
All three PRs branch from
feat/mergeand rewrite the same loader; they cannot be combined.model_validate_json. Typed internals from the most widely used validation library, about 22x, same small model layer.Pick jsonschema-rs for the minimal, lowest-risk fix; pick a typed option if typed internals are worth a model layer.
Work items
jsonschema-rsvalidator compiled once at import, inpypangraph/class_graph.py.jsonschema-rsinpyproject.toml,requirements.txt, and the Python container.benchmarks/bench_loadand a graph-loading doc.Verify