Validate function graph operands once - #338
Conversation
Flatten Durofut graphs through one iterative pass shared by df.start() and df.explain(). Assign IDs as children are discovered so both callers consume the same FunctionNode representation without recursive walkers. Validate node types, depth, and node count while flattening. Include child paths in errors, preserve the existing df.nodes.query format, and document why pre-order insertion relies on deferred foreign keys.
Insert graph nodes in bounded multi-row statements to reduce SPI round trips while preserving legacy and current schema layouts. Cover both SQL shapes and exercise a graph that crosses the batch boundary without launching its activities.
Performance results and recommendationI ran two independent benchmark sessions on the same environment using PostgreSQL 17.10, pgrx 0.16.1, and release builds. The comparisons were incremental:
What the benchmarks didComposition microbenchmark: constructed a left-deep sequence by repeatedly passing the accumulated serialized graph and one newly arrived plain-SQL operand to Start/persistence microbenchmark: loaded an already constructed, shallow-wide graph and called End-to-end microbenchmark: constructed the left-deep sequence and passed it directly to What #337 changedCompared with #335, #337 reduced the number of node INSERT statements from one per node to one per 1,000-node chunk. The measured counts were exactly 1, 1, and 2 INSERTs for 999, 1,000, and 1,001 nodes, and 5 INSERTs for 5,000 nodes. That structural reduction produced a large end-to-end
This establishes #337 as the correct baseline for evaluating #338: after batching, persistence is much cheaper, so #338 must demonstrate an additional user-visible improvement rather than only a faster composition loop. What #338 changedCompared with #337, #338 produced a strong and highly repeatable composition-only improvement:
The optimization is therefore real in the narrow loop it targets. Composition also remains measurable after #337: at 250 operands, composition took about 10 ms of a 46-47 ms end-to-end start. However, the end-to-end comparison of #338 against #337 did not reproduce:
At the realistic 100- and 250-operand sizes, the second session reduced the apparent improvement to 2-3%. The 50-operand result changed direction entirely. That spread indicates the incremental end-to-end effect is not materially larger than run-to-run noise. The safety tests for #338 pass, including sibling-key injection handling, non-future-helper attribution, and path-aware corrupt-accumulator reporting. Its batching behavior also remains identical to #337. The concern is not correctness; it is whether the additional validation trust boundary buys enough user-visible performance to justify itself. Conclusion: #338 is not worth merging. It consistently speeds up the composition microbenchmark, but the improvement does not carry through as a repeatable, material reduction in end-to-end |
0ff1570 to
87ee524
Compare
Stacked PR
This PR is not intended to merge as-is. It is temporarily based on #337 (
pinodeca/batch-node-inserts) so Step 5 can be reviewed while Step 4 is still in review.After #337 merges, this branch will be rebased onto
mainand the PR's base branch will be changed tomain.Summary
df.seq(),df.join(), anddf.race()when it is the exact output of the previous binary composition in the same backendflatten_graphfor corrupt accumulatorsdocs/nested-graph-design.mdDecision Gate
This is the experimental Step 5 branch. It should be accepted only if Step 4-to-Step 5 measurements show a repeatable, material end-to-end improvement under realistic graph sizes. If the improvement is not material, Step 5 should not merge.
Validation
cargo fmt -p pg_durable -- --checkcargo clippy --features pg17./scripts/test-unit.sh(285 passed, 16 ignored)./scripts/test-e2e-local.sh 09_graph_and_validation(1 passed)