Repository navigation
perf: build queries up to 2.7x faster by reusing ones already built - #40
Conversation
Merging this PR will improve performance by 8.66%1 warning, 1 note
Performance Changes
Tip Curious why performance improved? Comment Comparing |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e320c2a to
6883126
Compare
… their JSON (#43) ## Summary Follow-up to #39/#40. Upstream 1.11.3 normalizes ASTs at every builder step, which made the query path slower. This wins it back on the query endpoint, mostly by not doing the same work twice for a query the builder already interned. ```diff handle_query_request handler(name, args) # interned query, often the same object as last time - map_ast(query.ast, mapper) + mapped_ast(query, mapper) # kept on the Query, keyed by mapper identity starlette /query - js_stringify_bytes(response.to_json()) # orjson, then _may_differ scans ~200 KB + query_response_bytes(response) + QuerySuccess.to_json_bytes() + TransformedQuery.to_json_bytes() + AST.to_json_bytes() # js_stringify_bytes(to_json()) once, kept on the AST js_stringify_bytes._may_differ - 10 substring scans, one per digit, for an array-index key + digits deleted with translate, one scan for '"":' builder - _derive(("related_base"/"exists_base", rel), lambda: ...) + _find, then _keep only on a miss (no closure per call) + _condition_key: column-vs-scalar condition keyed as one flat tuple ``` ASTs are already immutable by contract (`ast.py`: "no code changes a node once built"), so caching their mapped form and their text is safe. `AST._text` is a `compare=False`, `init=False` slot and is reset in `_copy`. ## Evidence Wall clock, `python -m bench`, same machine, this branch against `main` (ca665da): | case | main | this PR | Py/TS main | Py/TS now | |---|---:|---:|---:|---:| | query.simple.b1 | 6.9 µs | 5.2 µs | 1.71 | 1.26 | | query.simple.b100 | 541.5 µs | 388.0 µs | 2.96 | 2.15 | | query.complex.b1 | 31.9 µs | 20.3 µs | 3.12 | 1.97 | | query.complex.b100 | 2.86 ms | 1.81 ms | 3.94 | 2.33 | | query_json.simple.b1 | 15.4 µs | 8.0 µs | 2.56 | 1.34 | | query_json.simple.b100 | 1.05 ms | 543.8 µs | 3.53 | 1.64 | | query_json.complex.b1 | 68.8 µs | 23.5 µs | 3.82 | 1.31 | | query_json.complex.b100 | 6.14 ms | 2.05 ms | 4.63 | 1.57 | | build.complex | 14.6 µs | 13.7 µs | 6.54 | 5.68 | | mutate_cpu.put.n1 | 16.3 µs | 14.6 µs | 4.63 | 3.84 | | parse, parse_json, map, z2s | | | | unchanged (noise) | `query_json` now times `query_response_bytes`, which is what the Starlette route sends. - New differential assertion in `test_queries_match_upstream`: `query_response_bytes(actual) == JSON.stringify(upstream response)`, byte for byte, over the Hypothesis program corpus (floats, surrogates, errors, `userID` set/unset). - `uv run pytest`: 784 passed, 100% coverage. `python -m tests.compat`: 54 upstream modules, every port module fully exercised. ruff, ty clean. The caches pay off when a query repeats (same name, same args), which interning already makes the common case. A query with args that vary per request still maps and stringifies once per distinct query. ## Merge Danger **Door:** two-way **Blast Radius:** query endpoint Response bytes are unchanged (proven against upstream). Mapped ASTs are now shared between responses for the same interned query, so code that mutates a returned AST would corrupt later responses. Nothing in the SDK does that. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/agent-labs-dev/codesmith/zero-python/pr/43?autoLogin=true&ref=codesmith_pr_footer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png" width="162" height="28"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1795815534&installation_model_id=434381&pr_number=43&ref=codesmith_pr_footer&repository=agent-labs-dev%2Fzero-python&return_to=https%3A%2F%2Fgithub.com%2Fagent-labs-dev%2Fzero-python%2Fpull%2F43&signature=f86f3e3910688eb52df613c24891cd33d35dd6ca56f408c4b37e894ede9e5955"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png" width="190" height="28"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…benchmarks (#46) ## Summary The docs had fallen behind #39 (1.11.3), #40 (interning) and #43 (cached mapped ASTs). This brings them level and adds current benchmark ratios. ```diff README.md + "Queries are shared": interned queries, mapped AST and JSON reused; treat as immutable - dangling "wire and SQL tiers run against every adapter" line + **Adapters.** bullet in the compatibility list + build/map in the CPU case list + Py/TS ratio table at ec0768a, 1.11.3, Apple Silicon laptop docs/compat.md + known difference: logging (stdlib `logging`, not upstream's LogSink) re-wrap the interned-queries paragraph docs/plan.md - "small bun runner" -> Node runner in harness/ - "first pin is 1.10.0" -> was 1.10.0; current pin in ZERO_VERSION + status: phases 1-7 done; phase 8 publishes on tag, npm watch not built CONTRIBUTING.md "Bumping Zero" + README version, docstring tag/commit, compat_scope line remap, + `python -m bench.capture`, record new differences bench/__init__.py + build and map groups ``` ## Evidence Nothing on screen changes; docs only. - Every API name the README names is exported from `zero_server`. The version is 1.11.3 everywhere it's pinned (`ZERO_VERSION`, both `package.json` files, the compose image, the docstrings). - The ratio table comes from a full local `uv run python -m bench` (exit 0) at ec0768a. Postgres cases: Python within 10% of npm or faster. Query endpoint with JSON: 1.3–1.7x. `build`/`map`: 6–14x, but microseconds per call. - `ruff check` and `ruff format --check` pass on `bench/`. ## Merge Danger **Door:** two-way **Blast Radius:** docs README is the PyPI long description, so the next release's page shows the new table. The ratios are from one laptop and will drift; the table names the commit and the machine. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/agent-labs-dev/codesmith/zero-python/pr/46?autoLogin=true&ref=codesmith_pr_footer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png" width="162" height="28"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1795816999&installation_model_id=434381&pr_number=46&ref=codesmith_pr_footer&repository=agent-labs-dev%2Fzero-python&return_to=https%3A%2F%2Fgithub.com%2Fagent-labs-dev%2Fzero-python%2Fpull%2F46&signature=c4188e5be40817be60bd25370789884b1853df2c8efe7ef6c8707f41d6f4f79f"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png" width="190" height="28"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bumps the version to 0.0.4 in `pyproject.toml`, `uv.lock` and `zero_server.__version__`. Nothing else changes. Since 0.0.3, `main` has gained support for `@rocicorp/zero` 1.11.3 (#39), orjson for request parsing and response writing (#37), query reuse that builds queries up to 2.7x faster (#40), reused mapped ASTs and JSON that send query responses up to 3x faster (#43), updated docs (#46) and dependency bumps. After merge, tag `v0.0.4` on main to publish to PyPI. Local run: 786 passed, 100% coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/agent-labs-dev/codesmith/zero-python/pr/47?autoLogin=true&ref=codesmith_pr_footer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png" width="162" height="28"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1795817623&installation_model_id=434381&pr_number=47&ref=codesmith_pr_footer&repository=agent-labs-dev%2Fzero-python&return_to=https%3A%2F%2Fgithub.com%2Fagent-labs-dev%2Fzero-python%2Fpull%2F47&signature=bf55280b5bd9ccc8d24e9874ca54b3b0973db3ee2b748438e15b920b024f233f"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png" width="190" height="28"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Ports upstream 1.11.3's query interning to the builder. When a step repeats one taken before, the builder returns the query it built then instead of building it again.
Subqueries (
related,exists) are keyed by the identity of their query's AST. The newCorrelatedSubquery.sourcefield keeps that query alive. A two-hopexistsbuilds its junction AST new every time, so it's keyed by its junction condition instead. Python's keys are exact where upstream's aren't: upstream folds aflipof'yes'intotrue.Stacked on #39, which this wins back the builder slowdown for.
Evidence
Wall-clock, Python median, same machine:
main(1.10.0)test_rebuilt_queries_match_upstream: the same programs three times in one batch through one builder, compared with the npm package. They cover every step kind: scalar and floatwhere,whereFnwith a static parameter andexists, two-hopwhereExists, a raw condition,relatedwith and without a callback,orderBy, nestedstart,limitandone.scalar: truethenfalsereturned the first query.uv run pytest: 784 passed, 100% coverage.uv run python -m tests.compat: fully exercised.Merge Danger
Door: two-way
Blast Radius: builder
Queries built from the same builder now share objects. Their ASTs equal what fresh building gives, including when a caller later mutates a list it passed in, which upstream does the same way. Memory is bounded: children are held weakly, plus at most 4096 recently built queries.
Reviewed with sol-loop: 2 rounds with gpt-6.1-sol. Sol verdict: approved
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.