-
Notifications
You must be signed in to change notification settings - Fork 51.1k
fix(core): Optimize connection type lookups #17585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(core): Optimize connection type lookups #17585
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cubic analysis
2 issues found across 1 file • Review in cubic
React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.
packages/workflow/src/workflow.ts
Outdated
| // Collect connection types from source connections | ||
| for (const nodeConnections of Object.values(this.connectionsBySourceNode)) { | ||
| for (const connectionType of Object.keys(nodeConnections)) { | ||
| connectionTypes.add(connectionType as NodeConnectionType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rule violated: Prefer Typeguards over Type casting
Directly casting `connectionType` to `NodeConnectionType` violates the "Prefer Typeguards over Type casting" rule. Replace the `as` assertion with a type guard or collect keys into a `Set<string>` and validate before use.
Prompt for AI agents
Address the following comment on packages/workflow/src/workflow.ts at line 1041:
<comment>Directly casting `connectionType` to `NodeConnectionType` violates the "Prefer Typeguards over Type casting" rule. Replace the `as` assertion with a type guard or collect keys into a `Set<string>` and validate before use.</comment>
<file context>
@@ -1032,16 +1032,24 @@ export class Workflow {
visited.add(nodeName);
- // Check all connection types for this node
- const allConnectionTypes = [
- NodeConnectionTypes.Main,
- NodeConnectionTypes.AiTool,
- NodeConnectionTypes.AiMemory,
- NodeConnectionTypes.AiDocument,
</file context>
packages/workflow/src/workflow.ts
Outdated
|
|
||
| for (const connectionType of allConnectionTypes) { | ||
| // Get connection types that actually exist in this workflow | ||
| const connectionTypes = new Set<NodeConnectionType>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
connectionTypes is recomputed on every BFS iteration, iterating over the entire connection map each time and causing avoidable O(N²) overhead; compute it once outside the while-loop instead.
Prompt for AI agents
Address the following comment on packages/workflow/src/workflow.ts at line 1036:
<comment>`connectionTypes` is recomputed on every BFS iteration, iterating over the entire connection map each time and causing avoidable O(N²) overhead; compute it once outside the while-loop instead.</comment>
<file context>
@@ -1032,16 +1032,24 @@ export class Workflow {
visited.add(nodeName);
- // Check all connection types for this node
- const allConnectionTypes = [
- NodeConnectionTypes.Main,
- NodeConnectionTypes.AiTool,
- NodeConnectionTypes.AiMemory,
- NodeConnectionTypes.AiDocument,
</file context>
…trieval for more performance
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…rieval efficiency
…main output handling
…thod to cover various node connection scenarios
…nodes and streamline connection retrieval
| for (const connectionType of nonMainConnectionTypes) { | ||
| const connections = nodeConnections[connectionType] || []; | ||
| for (const connectionGroup of connections) { | ||
| for (const connection of connectionGroup || []) { | ||
| if (connection?.node) { | ||
| const returnNode = this.getNode(connection.node); | ||
| if (!returnNode) { | ||
| throw new ApplicationError(`Node "${connection.node}" not found`); | ||
| } | ||
| return this.getParentMainInputNode(returnNode); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the more declarative (functional programming) to the iterative way and if it doesn't decrease performance that much I'd go with this
| for (const connectionType of nonMainConnectionTypes) { | |
| const connections = nodeConnections[connectionType] || []; | |
| for (const connectionGroup of connections) { | |
| for (const connection of connectionGroup || []) { | |
| if (connection?.node) { | |
| const returnNode = this.getNode(connection.node); | |
| if (!returnNode) { | |
| throw new ApplicationError(`Node "${connection.node}" not found`); | |
| } | |
| return this.getParentMainInputNode(returnNode); | |
| } | |
| } | |
| } | |
| } | |
| const parentNode = nonMainConnectionTypes | |
| .flatMap((connectionType) => nodeConnections[connectionType] || []) | |
| .flatMap((connectionGroup) => connectionGroup || []) | |
| .map((connection) => { | |
| if (connection?.node) { | |
| const returnNode = this.getNode(connection.node); | |
| if (!returnNode) { | |
| throw new ApplicationError(`Node "${connection.node}" not found`); | |
| } | |
| return this.getParentMainInputNode(returnNode); | |
| } | |
| }) | |
| .find(Boolean); | |
| return parentNode || node; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a quick benchmark, and the iterative will perform better on complex graphs
- Simple Chain (5 nodes): Declarative was 63.6% faster - this is likely due to JIT optimization on simple cases
- Complex Graph (20 nodes): Declarative was 11.6% slower
- Deep Nesting (10 levels): Declarative was 18.6% slower
- Single Node (baseline): Declarative was 38.3% slower
Average performance ratio: 1.013 (declarative slightly slower overall)
| if (node) { | ||
| const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); | ||
| const outputs = NodeHelpers.getNodeOutputs(this, node, nodeType.description); | ||
| if (!node) return node; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either the argument type should reflect this is nullish, or this check is unnecessary?
| const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); | ||
| const outputs = NodeHelpers.getNodeOutputs(this, node, nodeType.description); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe not to check outputs anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I'm not an expert on this part, I discussed it with claude code and it said yes.
I can share the reasoning with you if you're interested
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nobody is an expert on this ancient stuff :)
Since this is the core of the PR, can you please verify? I trust your judgment, not Claude's.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we still don't have enough tests to catch if something is wrong with this?
packages/workflow/src/workflow.ts
Outdated
| ...Object.values(this.connectionsBySourceNode), | ||
| ...Object.values(this.connectionsByDestinationNode), | ||
| ].flatMap((nodeConnections) => | ||
| Object.keys(nodeConnections).filter((key): key is NodeConnectionType => !!key), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this filter a no-op? Should it be like this?
].flatMap((nodeConnections) => Object.keys(nodeConnections) as NodeConnectionType[]),There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was to avoid type casting
I added them back
I just remember Mutasem setting up some rule to avoid using it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are many cases where asserting (not casting) is reasonable if we know the keys. Nice article about it here.
| expect(result).toBe(set1Node); | ||
| }); | ||
|
|
||
| describe('nodes with only main outputs', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hats off, nice coverage!
…ndefined nodes and improve connection type retrieval
…al null parentMainInputNode
…otential null results
…m getParentMainInputNode
BundleMonUnchanged files (2)
No change in files bundle size Groups updated (2)
Final result: ✅ View report in BundleMon website ➡️ |
ivov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ivov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't tested, change looks clean.
|
E2E Tests: n8n tests passed after 5m 27.4s Run Details
Groups
This message was posted automatically by
currents.dev | Integration Settings
|
|
Got released with |
Summary
Fixes performance bottleneck in workflow runners by optimizing the hasPath method to avoid unnecessary node type loading during expression evaluation.
Related Linear tickets, Github issues, and Community forum posts
PAY-3158
Review / Merge checklist
release/backport(if the PR is an urgent fix that needs to be backported)