Skip to content

Commit 8866251

Browse files
authored
[Fusion] Improve Cost Estimator for improved schema resolution. (#9119)
1 parent 90af6b1 commit 8866251

19 files changed

+1233
-1249
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/OperationPlan.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ private OperationPlan(
2121
Operation operation,
2222
ImmutableArray<ExecutionNode> rootNodes,
2323
ImmutableArray<ExecutionNode> allNodes,
24-
uint searchSpace,
24+
int searchSpace,
2525
int expandedNodes)
2626
{
2727
Id = id;
@@ -67,7 +67,7 @@ public IReadOnlyList<VariableDefinitionNode> VariableDefinitions
6767
/// <summary>
6868
/// Gets a number specifying how many possible plans were considered during planning.
6969
/// </summary>
70-
public uint SearchSpace { get; }
70+
public int SearchSpace { get; }
7171

7272
/// <summary>
7373
/// Gets the number of nodes expanded (dequeued) during the A* search.
@@ -101,8 +101,8 @@ public static OperationPlan Create(
101101
Operation operation,
102102
ImmutableArray<ExecutionNode> rootNodes,
103103
ImmutableArray<ExecutionNode> allNodes,
104-
uint searchSpace,
105-
int expandedNodes = 0)
104+
int searchSpace,
105+
int expandedNodes)
106106
{
107107
ArgumentException.ThrowIfNullOrEmpty(id);
108108
ArgumentNullException.ThrowIfNull(operation);
@@ -128,8 +128,8 @@ public static OperationPlan Create(
128128
Operation operation,
129129
ImmutableArray<ExecutionNode> rootNodes,
130130
ImmutableArray<ExecutionNode> allNodes,
131-
uint searchSpace,
132-
int expandedNodes = 0)
131+
int searchSpace,
132+
int expandedNodes)
133133
{
134134
ArgumentNullException.ThrowIfNull(operation);
135135
ArgumentOutOfRangeException.ThrowIfLessThan(rootNodes.Length, 0);

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ public override OperationPlan Parse(ReadOnlyMemory<byte> planSourceText)
2020
{
2121
using var document = JsonDocument.Parse(planSourceText);
2222
var rootElement = document.RootElement;
23-
uint searchSpace = 0;
24-
int expandedNodes = 0;
23+
var searchSpace = 0;
24+
var expandedNodes = 0;
2525

2626
var id = rootElement.GetProperty("id").GetString()!;
2727
var operation = ParseOperation(rootElement.GetProperty("operation"));
2828

2929
if (rootElement.TryGetProperty("searchSpace", out var searchSpaceElement))
3030
{
31-
searchSpace = searchSpaceElement.GetUInt32();
31+
searchSpace = searchSpaceElement.GetInt32();
3232
}
3333

3434
if (rootElement.TryGetProperty("expandedNodes", out var expandedNodesElement))
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System.Collections.Immutable;
2+
3+
namespace HotChocolate.Fusion.Planning;
4+
5+
/// <summary>
6+
/// An immutable backlog of work items paired with cost tracking.
7+
/// Every push and pop keeps the cost in sync with the stack,
8+
/// so branch scoring stays O(1) per transition.
9+
/// </summary>
10+
internal readonly struct Backlog(ImmutableStack<WorkItem> items, BacklogCost cost)
11+
{
12+
private readonly ImmutableStack<WorkItem> _items = items;
13+
14+
public static Backlog Empty { get; } = new([], BacklogCost.Empty);
15+
16+
/// <summary>
17+
/// The cost tracking state for all items in this backlog.
18+
/// </summary>
19+
public BacklogCost Cost { get; } = cost;
20+
21+
/// <summary>
22+
/// Whether this backlog has no work items left.
23+
/// </summary>
24+
public bool IsEmpty => _items.IsEmpty;
25+
26+
/// <summary>
27+
/// Returns the next work item without removing it.
28+
/// </summary>
29+
public WorkItem Peek() => _items.Peek();
30+
31+
/// <summary>
32+
/// Pushes a work item and updates cost tracking.
33+
/// </summary>
34+
public Backlog Push(WorkItem workItem)
35+
{
36+
var cost = PlannerCostEstimator.AddWorkItemCost(Cost, workItem);
37+
return new Backlog(_items.Push(workItem), cost);
38+
}
39+
40+
/// <summary>
41+
/// Pops the next work item and updates cost tracking.
42+
/// </summary>
43+
public Backlog Pop(out WorkItem workItem)
44+
{
45+
var items = _items.Pop(out workItem);
46+
var cost = PlannerCostEstimator.RemoveWorkItemCost(Cost, workItem);
47+
return new Backlog(items, cost);
48+
}
49+
50+
/// <summary>
51+
/// Pushes work items for unresolvable selection sets that need
52+
/// their own operation steps on other schemas.
53+
/// </summary>
54+
public Backlog PushUnresolvable(
55+
ImmutableStack<SelectionSet> unresolvable,
56+
string fromSchema,
57+
int parentDepth)
58+
{
59+
if (unresolvable.IsEmpty)
60+
{
61+
return this;
62+
}
63+
64+
var backlog = this;
65+
66+
foreach (var selectionSet in unresolvable.Reverse())
67+
{
68+
var workItem = new OperationWorkItem(
69+
selectionSet.Path.IsRoot
70+
? OperationWorkItemKind.Root
71+
: OperationWorkItemKind.Lookup,
72+
selectionSet,
73+
FromSchema: fromSchema)
74+
{
75+
ParentDepth = parentDepth
76+
};
77+
backlog = backlog.Push(workItem);
78+
}
79+
80+
return backlog;
81+
}
82+
83+
/// <summary>
84+
/// Pushes work items for fields that have requirements
85+
/// which may need lookup steps to satisfy.
86+
/// </summary>
87+
public Backlog PushRequirements(
88+
ImmutableStack<FieldSelection> fieldsWithRequirements,
89+
int stepId,
90+
int parentDepth)
91+
{
92+
if (fieldsWithRequirements.IsEmpty)
93+
{
94+
return this;
95+
}
96+
97+
var backlog = this;
98+
99+
foreach (var selection in fieldsWithRequirements.Reverse())
100+
{
101+
var workItem = new FieldRequirementWorkItem(selection, stepId)
102+
{
103+
ParentDepth = parentDepth
104+
};
105+
backlog = backlog.Push(workItem);
106+
}
107+
108+
return backlog;
109+
}
110+
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/BacklogCostState.cs renamed to src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/BacklogCost.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,25 @@
33
namespace HotChocolate.Fusion.Planning;
44

55
/// <summary>
6-
/// Incremental backlog lower-bound state used to project remaining work shape.
6+
/// Tracks the minimum guaranteed cost of all work items still in the backlog.
77
/// </summary>
8-
/// <param name="OperationLowerBound">
9-
/// Guaranteed remaining operation floor from all backlog items.
8+
/// <param name="MinimumCost">
9+
/// Sum of the cheapest possible cost for each backlog item.
1010
/// </param>
1111
/// <param name="MaxProjectedDepth">
12-
/// Deepest projected operation depth across remaining guaranteed operations.
12+
/// The deepest level at which backlog items are expected to produce operations.
1313
/// </param>
1414
/// <param name="ProjectedOpsPerLevel">
15-
/// Projected guaranteed operation count per depth level.
15+
/// How many operations each depth level is expected to add.
1616
/// </param>
17-
internal readonly record struct BacklogCostState(
18-
double OperationLowerBound,
17+
internal readonly record struct BacklogCost(
18+
double MinimumCost,
1919
int MaxProjectedDepth,
2020
ImmutableDictionary<int, int> ProjectedOpsPerLevel)
2121
{
2222
/// <summary>
2323
/// Gets an empty backlog cost state with no projected operations.
2424
/// </summary>
25-
public static BacklogCostState Empty { get; } =
25+
public static BacklogCost Empty { get; } =
2626
new(0.0, 0, ImmutableDictionary<int, int>.Empty);
2727
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/ISelectionSetIndex.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ public interface ISelectionSetIndex
1515
/// <returns></returns>
1616
uint GetId(SelectionSetNode selectionSet);
1717

18-
bool TryGetId(SelectionSetNode selectionSet, out uint id);
19-
2018
bool TryGetOriginalIdFromCloned(uint clonedId, out uint originalId);
2119

2220
bool IsRegistered(SelectionSetNode selectionSet);

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/OperationDefinitionBuilder.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ internal sealed class OperationDefinitionBuilder
1212
{
1313
private OperationType _type = OperationType.Query;
1414
private string? _name;
15-
private string? _description;
1615
private Lookup? _lookup;
1716
private List<ArgumentNode>? _lookupArguments;
1817
private ITypeDefinition? _typeToLookup;
@@ -38,12 +37,6 @@ public OperationDefinitionBuilder SetName(string? name)
3837
return this;
3938
}
4039

41-
public OperationDefinitionBuilder SetDescription(string? description)
42-
{
43-
_description = description;
44-
return this;
45-
}
46-
4740
public OperationDefinitionBuilder SetLookup(
4841
Lookup lookup,
4942
List<ArgumentNode> arguments,
@@ -139,7 +132,7 @@ public OperationDefinitionBuilder SetSelectionSet(SelectionSetNode selectionSet)
139132
var definition = new OperationDefinitionNode(
140133
null,
141134
string.IsNullOrEmpty(_name) ? null : new NameNode(_name),
142-
string.IsNullOrEmpty(_description) ? null : new StringValueNode(_description),
135+
null,
143136
_type,
144137
[],
145138
[],

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/OperationPlanner.BuildExecutionTree.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ private OperationPlan BuildExecutionPlan(
1919
Operation operation,
2020
OperationDefinitionNode operationDefinition,
2121
ImmutableList<PlanStep> planSteps,
22-
uint searchSpace,
22+
int searchSpace,
2323
int expandedNodes)
2424
{
2525
if (operation.IsIntrospectionOnly())

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/OperationPlanner.MergeSelectionSetRewriter.cs

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)