Skip to content
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

perf: optimize activation of bundles with no inter-bundle path overlap #7155

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

sqyang94
Copy link

@sqyang94 sqyang94 commented Nov 4, 2024

Why the changes in this PR are needed?

When multiple bundles are loaded into OPA using the bundle plugin, it takes a long time to active the bundles according to the logs. Profiling results has shown that most of the activation time is spent in checking path conflicts.

This PR avoids unnecessary path conflict checking when no root overlaps are guaranteed. This will reduce the number of expensive deep copies and hence improve bundle activation time and CPU utilization.

Resolves #7144.

What are the changes in this PR?

If a bundle has a manifest file with roots defined, then during path conflict checking, only check the sub-AST from the root path instead of the entire AST.

Notes to assist PR review:

#7144 includes a detailed explanation and context on this change.

Further comments:

I wasn't able to find a good way of directly testing the change (i.e. testing that only subtree was checked when roots are defined in the manifest). Instead, I added some bundle plugin tests with different scenarios to make sure conflicts are correctly checked still.

Ideas on how to directly test this approach are welcome :)

ast/compile.go Outdated
// WithPathConflictsCheckRoot enables checking path conflicts from the specified root instead
// of the top root node. This would enable optimizting path conflict checks during bundle
// activation if a bundle already defines its own root paths.
func (c *Compiler) WithPathConflictsCheckRoot(rootPaths []string) *Compiler {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to use singular "root" for the name while the args and the property says "roots"?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Will change to use the plural form.

@sqyang94 sqyang94 force-pushed the check-conflict-from-child-node branch from 179ecc8 to 5cd286b Compare November 4, 2024 18:54
@sqyang94
Copy link
Author

sqyang94 commented Nov 4, 2024

Force pushing to sign off my commits...

Copy link
Contributor

@johanfylling johanfylling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for contributing! 😃

Just a couple of comments.

ast/compile.go Outdated
Comment on lines 387 to 389
// WithPathConflictsCheckRoots enables checking path conflicts from the specified root instead
// of the top root node. This would enable optimizting path conflict checks during bundle
// activation if a bundle already defines its own root paths.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler is agnostic to concepts such as bundle activation. I suggest we drop the second sentence, or reformulate it to say something general about performance optimization. E.g.

Suggested change
// WithPathConflictsCheckRoots enables checking path conflicts from the specified root instead
// of the top root node. This would enable optimizting path conflict checks during bundle
// activation if a bundle already defines its own root paths.
// WithPathConflictsCheckRoots enables checking path conflicts from the specified root instead
// of the top root node. Limiting conflict checks to a known set of roots, such as bundle roots,
// improves performance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also mention the expected format of the paths: /-separated, data root document excluded.

@@ -1963,7 +1966,7 @@ p[r] := 2 if { r := "foo" }`,
return false, fmt.Errorf("unexpected error")
}
return false, nil
})
}).WithPathConflictsCheckRoots([]string{"badrules"})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is deserving of its own test, so that we assert the behavior of WithPathConflictsCheck() and WithPathConflictsCheckRoots() in isolation from each other.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. I'll add a new unit test for WithPathConflictsCheckRoots specifically.

ast/conflicts.go Outdated
node = child
}

if nodeNotFound {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we could just do node = node.Child(String(key)) in the above loop and check node == nil here instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

if nodeNotFound {
// could not find the node from the AST (e.g. `path` is from a data file)
// then no conflict is possible
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test case that covers this scenario? If not, to be exhaustive in our testing, we should add one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's implicitly tested with the WithPathConflictsCheckRoots test. Since I'm going to add a brand new test case, I'll make sure to comment on how this is tested.

if status.Code != errCode {
t.Fatalf("Expected status code to be %s, found %s", errCode, status.Code)
}
if !strings.Contains(status.Message, "detected overlapping") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: but we say "detected overlapping roots" in the error message below.

One way to make sure the check and error message always are in agreement across code changes is to extract the expected value into a var, e.g.:

if exp := "detected overlapping roots"; !strings.Contains(status.Message, exp) {
   t.Fatalf(`Expected status message to contain "%s", found %s`, exp, status.Message)
}

Copy link

netlify bot commented Nov 26, 2024

Deploy Preview for openpolicyagent ready!

Name Link
🔨 Latest commit c5c1dfa
🔍 Latest deploy log https://app.netlify.com/sites/openpolicyagent/deploys/67463304c5d7f80008f6169e
😎 Deploy Preview https://deploy-preview-7155--openpolicyagent.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize activation of bundles with no inter-bundle path overlap
5 participants