Repository navigation
perf(ast): fix release-path defects and stop ReleaseAST pooling node types the parser never draws (#555, #556) - #581
Conversation
…#555) Three defects in the AST release path: 1. Double Put on derived-table JOINs. The parser builds Joins[0].Left as a value copy of From[0], so PutSelectStatement released the derived-table subquery twice and two later GetSelectStatement calls returned the same object. Joins[i].Left is now documented as non-owning and is never released; the whole JoinClause is zeroed (also clearing Pos). 2. Incomplete resets. Pooled nodes came back with stale Not/CustomOp/Pos (BinaryExpression), Table/Pos (Identifier), NullTreatment/OrderBy/ WithinGroup/Pos (FunctionCall), Try (CastExpression), etc., and FunctionCall.Over/Filter/OrderBy/WithinGroup children were dropped unreleased. Every loop case now pushes all children and resets the node with a whole-struct assignment that keeps only hot slice capacity. The standalone PutX wrappers delegate to PutExpression so there is a single reset path. Pooled children of Rollup/Cube/GroupingSets/NamedArgument/ VariantPath (no pool of their own) are released and detached. Select/Insert/Update/Delete reset Pos; ReleaseAST clears Comments. 3. Work-queue pinning. putExpressionImpl cleared workQueue[:cap] on every call and returned queues of any size to the pool, so after one 20,000-element IN list every later Put paid for clearing it. It now clears only up to the high-water mark, drops queues with cap > 1024 (maxPooledWorkQueueCap), and Identifier/LiteralValue take a leaf fast path that skips the queue. benchstat (count=6, Apple M4, shared noisy machine; allocs/op and B/op unchanged for every benchmark, all new benchmarks 0 allocs/op, 0 B/op): PutLeafAfterBigList 2995ns -> 19.1ns (-99.4%) PutBinaryAfterBigList 3183ns -> 47.3ns (-98.5%) PutExpression/PutIdentifier 39.9ns -> 23.5ns (-41%) An interleaved old/new A/B run shows no significant ns/op change on ASTPool, SelectStatementPool, IdentifierPool, BinaryExpressionPool or Parallel. Fixes #555 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
…from (#556) ReleaseAST walked every tree and Put each Identifier, LiteralValue, BinaryExpression, FunctionCall, AliasedExpression, Case/In/Between/... expression and Select/Insert/Update/Delete statement into its sync.Pool, although the parser builds all of them as heap literals and never Gets them. Those pools only ever received objects, so release was pure cost (tree walk, resets, Puts and poolChain growth). An unexported releaseMode is now threaded through the existing walk: - releaseAll: used by the exported Put* functions (PutExpression, PutSelectStatement, PutInsert/Update/DeleteStatement, ...). Unchanged semantics: every reachable pooled node is reset and Put. - releaseDrawn: used by ReleaseAST and ReleaseStatements. The walk and child pushes are byte-identical (pooled Tuple/ArrayConstructor/ ArraySubscript/ArraySlice nodes can be nested under any parent, and the work-queue/overflow-drain invariants from #555 are kept), but only the types the parser draws are reset and Put: those four expressions plus Unsupported/Alter/Show/Describe/Replace/sequence and the other DDL statements (releaseStatement cases untouched). Identifier/LiteralValue leaves return immediately; Select/Insert/Update/Delete fields are still cleared but the statement itself is left to the GC. No exported signature changes (go doc func list identical). Godoc on ReleaseAST, ReleaseStatements, PutExpression and doc.go documents the policy and that hand-built trees should be released with Put*. TestReleaseAST_DerivedTableJoin_NoDoublePut now passes trivially on the ReleaseAST path because SelectStatements are no longer Put there; the hand-built PutSelectStatement path still guards #555. Tests: pool_release_policy_test.go pins both contracts (drawn nodes reset and undrawn left alone after ReleaseAST; undrawn pointers never come back from Get*; hand-built trees under ReleaseAST vs PutSelectStatement / PutExpression; drawn-pool contamination across different tuple/array arities compared with a fresh parse). Benchmarks BenchmarkParseRelease and BenchmarkReleaseASTOnly added. benchstat (count=6, Apple M4; allocs/op and B/op are the hard signal): ReleaseASTOnly/SampleJoin 242 -> 24 B/op (-90%), 155ns -> 81ns (-48%) ReleaseASTOnly/MediumCTE 1399 -> 39 B/op (-97%), 768ns -> 391ns (-49%) ReleaseASTOnly/DrawnTypes 551 -> 209 B/op (-62%) ParseRelease/SampleJoin/Release 3.632Ki -> 3.458Ki (-4.8%) ParseRelease/MediumCTE/Release 17.02Ki -> 15.60Ki (-8.3%), -22% ns/op ParseRelease/DrawnTypes/Release 4.604Ki -> 4.270Ki (-7.3%), -19% ns/op allocs/op unchanged everywhere (MediumCTE/Release 122 -> 121); NoRelease rows, exported Put* benchmarks and BenchmarkParserComplexity unchanged in B/op and allocs/op. DrawnTypes still benefits from pooling (27 allocs with release vs 38 without). Fixes #556 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
…nger double-Put test Reword the ReleaseAST and package docs to list exactly which types ReleaseAST Puts, and to state that DDL statements and the query inside CREATE VIEW / CREATE MATERIALIZED VIEW are still fully recycled by their Put* functions. Document the shared-parent hazard in putExpressionImpl. Extend TestReleaseAST_DerivedTableJoin_NoDoublePut with ARRAY and tuple expressions and assert the ArrayConstructor and Tuple pools never hand out the same pointer twice. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @ajitpratap0's task in 30s —— View job Review of PR #581 (AST pool release fixes, #555 and #556)I read the Overall: The design is sound and the invariants are well documented. I found no blocking issues. What looks good
Suggestions and risks (non-blocking)
Process notes
Verdict: Approve-worthy once CI is green, including |
…ow paths Add TestReleaseDrawnPolicy_MatchesParser, which parses pkg/sql/parser with go/parser and compares the pool constructors it references with the new releaseDrawnConstructors list, plus a behavioural check that every listed type is reset in releaseDrawn mode. Add overflow tests for both release modes: trees above MaxWorkQueueSize, the MaxCleanupDepth truncation (via putExpressionBounded, which takes the budget and depth as parameters; production callers pass the constants), and subquery nesting far deeper than MaxCleanupDepth. They assert no panic, reset pooled nodes, no oversized pooled work queue, and the exact leak-counter delta. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @ajitpratap0's task in 14s —— View job Review of PR #581 (AST pool release fixes, #555 and #556)I read the test file list and the earlier diff review. I did not run the tests, benchmarks or race gate in this pass. Update since the previous review: the latest commit (
Overall: The design is sound, the invariants are documented, and I found no blocking issues. Both of my earlier concerns are covered by tests. Remaining non-blocking notes
Verdict: Approve-worthy once CI is green, including |
|
Claude finished @ajitpratap0's task in 14s —— View job Review of PR #581 (AST pool release fixes, #555 and #556)I did not run the tests, benchmarks or race gate in this pass. The sandbox denied Overall: The design is sound and the invariants are documented. I found no blocking issues. The parser-drift and overflow tests from What looks good
Non-blocking suggestions
Verdict: Approve-worthy once CI is green, including |
The list has no production use, so golangci-lint's unused check failed on it. Keep it next to the test that compares it with the parser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @ajitpratap0's task in 16s —— View job Re-review of PR #581 after
|
) The second merge commit in PR #526 (b8a1924, "Merge remote-tracking branch 'origin/main' into HEAD") resolved a conflict by truncating CHANGELOG.md to zero bytes, dropping all 1,825 lines of release history. Nothing since has touched the file, so main has shipped with an empty changelog since 2026-10-10. Restore the file to its last good state (acc3d23, which already carries the #525 "Fixed" entry) and re-add the "Added" entry for the --dialect CLI flag that PR #526's own feature commit (4d75b95) contributed but the merge lost. Entries for the perf PRs merged since (#568-#578, #581) are deliberately left for the consolidated changelog/baselines follow-up tracked in #566. Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini-2655.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…resh performance baselines (#566) CHANGELOG [Unreleased] now covers every PR merged since v1.14.0, with a Behavior changes section for the observable changes in #597, #595, #584, #585, #588, #581, #582/#570, #517/#518 and #511, plus Added, Changed, Fixed, Performance, Security and Internal entries. The two existing entries (#526 and the tokenizer-pool dialect fix) are kept verbatim. performance_baselines.json is re-measured on an Apple M4 with BenchmarkPerformanceBaseline -count=10 -benchmem (median). Baselines are set at about 2.5x the M4 median rather than 1.5x: the CI Benchmark job on ubuntu-latest measures these queries about 1.5-3.2x slower than M4 (last 9 Tests runs on main, 0715bb9..61ca6c8), so 1.5x with the unchanged tolerances would fail CI's slowest runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
…xes; refresh performance baselines (#566) (#598) * docs: consolidate CHANGELOG for the perf audit and batch-2 fixes; refresh performance baselines (#566) CHANGELOG [Unreleased] now covers every PR merged since v1.14.0, with a Behavior changes section for the observable changes in #597, #595, #584, #585, #588, #581, #582/#570, #517/#518 and #511, plus Added, Changed, Fixed, Performance, Security and Internal entries. The two existing entries (#526 and the tokenizer-pool dialect fix) are kept verbatim. performance_baselines.json is re-measured on an Apple M4 with BenchmarkPerformanceBaseline -count=10 -benchmem (median). Baselines are set at about 2.5x the M4 median rather than 1.5x: the CI Benchmark job on ubuntu-latest measures these queries about 1.5-3.2x slower than M4 (last 9 Tests runs on main, 0715bb9..61ca6c8), so 1.5x with the unchanged tolerances would fail CI's slowest runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve * docs(changelog): add batch-3 follow-up fixes (#599-#620) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve * docs(changelog): record #632 render-injection hardening and #624 quote guards Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve * docs(changelog): correct batch-3 entries after review; record measured costs - #628: SQLite still rejects count(*) [cnt]; only SQL Server keeps it. - #631: bare FORMAT / FORMAT <name> rules apply after SETTINGS. - Performance: record the measured costs of the batch-3 correctness fixes (Identifier and TableReference growth, quoting in SQL(), lineStarts reallocation, sorted Extract results). - #632: note the lıke, WITH CHECK OPTION and MINUS rendering changes. - Fix the [Unreleased] compare link (v1.14.0...HEAD) and add [1.14.0]. - Keep each section a single tight list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve * docs(changelog): describe the release as source-compatible with runtime behaviour changes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
#555 perf(ast): fix release-path defects: double Put via JoinClause.Left, incomplete node reset, unbounded work-queue clearing
Fixes three defects in the AST release path (#555): PutSelectStatement no longer releases through Joins[i].Left (a non-owning value copy of From[0]), which previously Put the derived-table subquery twice and made two later GetSelectStatement calls return the same object; every pooled expression node is now reset by whole-struct assignment (keeping only hot slice capacity) and FunctionCall's Over/Filter/OrderBy/WithinGroup plus the children of Rollup/Cube/GroupingSets/NamedArgument/VariantPath are released instead of dropped; the PutExpression work queue is cleared only to its high-water mark, queues with cap > 1024 are not returned to the pool, and Identifier/LiteralValue take a queue-free fast path. Measured effect (benchstat, count=6): PutLeafAfterBigList 2995ns -> 19ns and PutBinaryAfterBigList 3183ns -> 47ns, both now within 1.1x of the no-big-list case; allocs/op and B/op are unchanged on every benchmark. Validated independently: race gate green across 44 packages, golangci-lint 0 issues, no exported API change, and the new regression tests fail on the pre-fix code and pass on this branch. CHANGELOG.md and performance_baselines.json are intentionally left for the consolidated follow-up PR.
#556 perf(ast): stop ReleaseAST Putting node types the parser never draws from (or make the parser draw them)
ReleaseAST/ReleaseStatements now thread an unexported
releaseModethrough the unchanged release walk and Put only the node types the parser actually draws (Tuple/ArrayConstructor/ArraySubscript/ArraySlice; Unsupported/Alter/Show/Describe/Replace/sequence statements), leaving Identifier/LiteralValue/BinaryExpression/FunctionCall and Select/Insert/Update/Delete to the GC; the exported Put* functions keep fullreleaseAllsemantics and the exported API is byte-identical (542go docfunc lines). This is the issue's recommended option (b); option (a), having the parser draw hot node types from pools, remains a possible follow-up now that #555 made the pools safe. Independent validation ran the repository race gate (44 packages, 0 failures), vet/lint clean, and a 221-input corpus contamination test (176 parsed, 4,408 sequential + 3,200 concurrent parse -> ReleaseAST -> parse pairs compared against untouched-pool reference dumps, plus 10k+ drawn-pool Gets with no double Put), all passing with and without-race. A drawn-only policy test and the new repo policy tests pass on HEAD and fail on the parent commit 3facfca. DDL statements and the query inside CREATE VIEW / CREATE MATERIALIZED VIEW (and MERGE) are still fully recycled by their Put* functions, as the docs state.Commits (3), one per issue:
9 files changed, 1638 insertions(+), 343 deletions(-)Test Plan
CGO_ENABLED=1 go test -race -timeout 120s $(go list ./... | grep -v /cbinding)),go vet, andgolangci-linton this branch.benchstat -count=6before/after on the same machine; allocs/op and B/op are the deterministic signal, ns/op is directional on a loaded machine.#555 benchstat
#555 race gate: cd /Users/ajitpratapsingh/dev/GoSQLX-worktrees/perf-ast-pool-release-fixes && CGO_ENABLED=1 go test -count=1 -race -timeout 120s $(go list ./... | grep -v /cbinding) -> exit 0; 44 packages ok, 0 FAIL, no DATA RACE/panic lines (output saved to scratchpad/fix/555/validator_race2.txt). Targeted -race -v run of the new tests also green: TestReleaseAST_DerivedTableJoin_NoDoublePut, TestPutSelectStatement_HandBuilt_LeftAliasFrom, TestPutExpression_ResetsEveryField_Reflective, TestPutExpression_FunctionCall_ReleasesOverFilterOrderBy, TestPutExpression_UnpooledParents_ReleaseChildren, TestPutExpressi
#556 benchstat
#556 race gate: full race gate passed (see notes)
Breaking Changes
Notes
main.Fixes #555
Fixes #556
Part of #566
🤖 Generated with Claude Code
https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve