-
Notifications
You must be signed in to change notification settings - Fork 153
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
feat: take the concurrencyLimit from feature flags and keep in dependencies #4564
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
package execute | ||
|
||
import ( | ||
"context" | ||
"math" | ||
"testing" | ||
|
||
"github.com/influxdata/flux" | ||
"github.com/influxdata/flux/plan" | ||
planspec "github.com/influxdata/flux/plan/plantest/spec" | ||
"go.uber.org/zap/zaptest" | ||
) | ||
|
||
func TestExecuteOptions(t *testing.T) { | ||
type runWith struct { | ||
concurrencyQuota int | ||
memoryBytesQuota int64 | ||
} | ||
|
||
testcases := []struct { | ||
name string | ||
spec *planspec.PlanSpec | ||
concurrencyLimit int | ||
defaultMemoryLimit int64 | ||
want runWith | ||
}{ | ||
{ | ||
// If the concurrency quota and max bytes are set in the plan | ||
// resources, the execution state always uses those resources. | ||
// Historically, the values in the plan resources always took | ||
// precedence. | ||
name: "via-plan-no-options", | ||
spec: &planspec.PlanSpec{ | ||
Nodes: []plan.Node{ | ||
planspec.CreatePhysicalMockNode("0"), | ||
planspec.CreatePhysicalMockNode("1"), | ||
}, | ||
Resources: flux.ResourceManagement{ | ||
MemoryBytesQuota: 163484, | ||
ConcurrencyQuota: 4, | ||
}, | ||
Edges: [][2]int{ | ||
{0, 1}, | ||
}, | ||
}, | ||
want: runWith{ | ||
memoryBytesQuota: 163484, | ||
concurrencyQuota: 4, | ||
}, | ||
}, | ||
{ | ||
// Use the plan resources even if the execution options are set. | ||
name: "via-plan-with-exec-options", | ||
spec: &planspec.PlanSpec{ | ||
Nodes: []plan.Node{ | ||
planspec.CreatePhysicalMockNode("0"), | ||
planspec.CreatePhysicalMockNode("1"), | ||
}, | ||
Resources: flux.ResourceManagement{ | ||
MemoryBytesQuota: 163484, | ||
ConcurrencyQuota: 4, | ||
}, | ||
Edges: [][2]int{ | ||
{0, 1}, | ||
}, | ||
}, | ||
defaultMemoryLimit: 8, | ||
concurrencyLimit: 2, | ||
|
||
want: runWith{ | ||
memoryBytesQuota: 163484, | ||
concurrencyQuota: 4, | ||
}, | ||
}, | ||
{ | ||
// Choose resources based on the default execute options. We get | ||
// old behaviour of choosing concurrency quota based on the number | ||
// of roots in the plan. | ||
name: "defaults-one-root", | ||
spec: &planspec.PlanSpec{ | ||
Nodes: []plan.Node{ | ||
planspec.CreatePhysicalMockNode("0"), | ||
planspec.CreatePhysicalMockNode("1"), | ||
planspec.CreatePhysicalMockNode("2"), | ||
planspec.CreatePhysicalMockNode("3"), | ||
}, | ||
Edges: [][2]int{ | ||
{0, 1}, | ||
{1, 2}, | ||
{2, 3}, | ||
}, | ||
}, | ||
want: runWith{ | ||
memoryBytesQuota: math.MaxInt64, | ||
concurrencyQuota: 1, | ||
}, | ||
}, | ||
{ | ||
// Again use the default execute options. Two roots in the plan | ||
// means we get a concurrency quota of two. | ||
name: "defaults-two-roots", | ||
spec: &planspec.PlanSpec{ | ||
Nodes: []plan.Node{ | ||
planspec.CreatePhysicalMockNode("0"), | ||
planspec.CreatePhysicalMockNode("1"), | ||
planspec.CreatePhysicalMockNode("root-0"), | ||
planspec.CreatePhysicalMockNode("root-1"), | ||
}, | ||
Edges: [][2]int{ | ||
{0, 1}, | ||
{1, 2}, | ||
{1, 3}, | ||
}, | ||
}, | ||
want: runWith{ | ||
memoryBytesQuota: math.MaxInt64, | ||
concurrencyQuota: 2, | ||
}, | ||
}, | ||
{ | ||
// Set the execute options. The memory limit passes in verbatim. | ||
// The concurrency limit is 16 and the new behaviour of choosing | ||
// the concurreny quota based on the number of non-source nodes is | ||
// active. | ||
name: "via-options-new-behaviour-non-source", | ||
spec: &planspec.PlanSpec{ | ||
Nodes: []plan.Node{ | ||
planspec.CreatePhysicalMockNode("0"), | ||
planspec.CreatePhysicalMockNode("1"), | ||
planspec.CreatePhysicalMockNode("2"), | ||
planspec.CreatePhysicalMockNode("3"), | ||
planspec.CreatePhysicalMockNode("root-0"), | ||
planspec.CreatePhysicalMockNode("root-1"), | ||
}, | ||
Edges: [][2]int{ | ||
{0, 1}, | ||
{1, 2}, | ||
{2, 3}, | ||
{3, 4}, | ||
{3, 5}, | ||
}, | ||
}, | ||
defaultMemoryLimit: 32768, | ||
concurrencyLimit: 16, | ||
want: runWith{ | ||
memoryBytesQuota: 32768, | ||
concurrencyQuota: 5, | ||
}, | ||
}, | ||
{ | ||
// Set the execute options. We want the new behaviour of setting | ||
// concurrency quota based on the number of non-source nodes (5), | ||
// but limited by the concurrency limit (4). | ||
name: "via-options-new-behaviour-limited", | ||
spec: &planspec.PlanSpec{ | ||
Nodes: []plan.Node{ | ||
planspec.CreatePhysicalMockNode("0"), | ||
planspec.CreatePhysicalMockNode("1"), | ||
planspec.CreatePhysicalMockNode("2"), | ||
planspec.CreatePhysicalMockNode("3"), | ||
planspec.CreatePhysicalMockNode("root-0"), | ||
planspec.CreatePhysicalMockNode("root-1"), | ||
}, | ||
Edges: [][2]int{ | ||
{0, 1}, | ||
{1, 2}, | ||
{2, 3}, | ||
{3, 4}, | ||
{3, 5}, | ||
}, | ||
}, | ||
defaultMemoryLimit: 32768, | ||
concurrencyLimit: 4, | ||
want: runWith{ | ||
memoryBytesQuota: 32768, | ||
concurrencyQuota: 4, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range testcases { | ||
execDeps := NewExecutionDependencies(nil, nil, nil) | ||
ctx := execDeps.Inject(context.Background()) | ||
|
||
inputPlan := planspec.CreatePlanSpec(tc.spec) | ||
|
||
thePlanner := plan.NewPhysicalPlanner() | ||
outputPlan, err := thePlanner.Plan(context.Background(), inputPlan) | ||
if err != nil { | ||
t.Fatalf("Physical planning failed: %v", err) | ||
} | ||
|
||
// | ||
// Modify the execution options. In practice, we would do this from | ||
// planner rules | ||
// | ||
if tc.defaultMemoryLimit != 0 { | ||
execDeps.ExecutionOptions.DefaultMemoryLimit = tc.defaultMemoryLimit | ||
} | ||
if tc.concurrencyLimit != 0 { | ||
execDeps.ExecutionOptions.ConcurrencyLimit = tc.concurrencyLimit | ||
} | ||
|
||
// Construct a basic execution state and choose the default resources. | ||
es := &executionState{ | ||
p: outputPlan, | ||
ctx: ctx, | ||
resources: outputPlan.Resources, | ||
logger: zaptest.NewLogger(t), | ||
} | ||
es.chooseDefaultResources(ctx, outputPlan) | ||
|
||
if err := es.validate(); err != nil { | ||
t.Fatalf("execution state failed validation: %s", err.Error()) | ||
} | ||
|
||
if es.resources.MemoryBytesQuota != tc.want.memoryBytesQuota { | ||
t.Errorf("Expected memory quota of %v, but execution state has %v", | ||
tc.want.memoryBytesQuota, es.resources.MemoryBytesQuota) | ||
} | ||
|
||
if es.resources.ConcurrencyQuota != tc.want.concurrencyQuota { | ||
t.Errorf("Expected concurrency quota of %v, but execution state has %v", | ||
tc.want.concurrencyQuota, es.resources.ConcurrencyQuota) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Curious about this branch here. If we had a trivial query whose execution graph just had a
ReadWindowAggregate
node, I guessconcurrencyQuota
could be 0. Do we require it to be positive even if there are no non-sources?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 might be mistaken, but my guess is there needs to be at least one goroutine to work the consecutive transport belonging to the source nodes. The source nodes themselves I think just deposit messages to the outgoing dataset and that's as far as the source goroutines take it. There needs to be a dispatcher thread that reads those messages and writes to CSV writer.
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.
Anecdotally, during recent refactors when I failed to mark any nodes as roots (my mistake) the concurrency quota was set to zero, which produced an error. It didn't seem like there was any conditional around that check since it failed for from/range/filter which I think would have be rewritten as a single source.
flux/execute/executor.go
Lines 80 to 85 in dc08c57
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.
Right makes sense.
Given Go's convention of having a useful zero value, I wonder if we should change the meaning of concurrency quota to be the number of additional goroutines after the required one. Or maybe
0
should just mean the default of1
.Nothing that needs to change here for this PR, it just seems a little weird.