Kanak Singhal
Kanak Singhal

Reputation: 3312

OPA PrepareForEval time increases exponentially

I need my application to be able to load policies (rego) and then evaluate an input JSON based on defined policies.

I mistakenly used PrepareForEval in my evaluation API instead of load policy API. The result of this surprised me as the response time kept on increasing exponentially after each evaluation, while the policies were kept unchanged. Although, after this, I've realized and changed my logic to call PrepareForEval method at the time of loading policy and then store the prepared query instance in my struct instance. But I'm still concerned if prepare method gets executed multiple times while loading policies then it'd still become an expensive operation.

So, it'd be great to be pointed towards the correct way of using the prepare method.

Sample code:

// My provider
func init() {
  cachedRego := rego.New(rego.Query("data.report"))
}

// My load policy method
func loadPolicy(ctx context.Context, filename, regoPolicy string) {
  mod, err := ast.ParseModule(filename, regoPolicy)
  rego.ParsedModule(mod)(cachedRego)
}

// My evaluate method
func eval(ctx context.Context, input interface{}) {
  // after loading my policies, the following call took 1s, 2s, 5s, 10s,... respectively on eval calls
  preparedQuery, _ := cachedRego.PrepareForEval(ctx) // <- I've moved this to my load policy method and cached preparedQuery

  // this doesn’t take much time
  rs, _ := preparedQuery.Eval(ctx, rego.EvalInput(input))
}

// My use case
func main() {
  // load policies and evaluate inputs
  for _, p := range policySet1 {
    loadPolicy(context.Background(), p.filename, p.regoPolicy)
  }

  for _, inp := range inputSet1 {
    eval(context.Background(), inp)
  }


  // load more policies to the earlier set and evaluate another input set
  for _, p := range policySet2 {
    loadPolicy(context.Background(), p.filename, p.regoPolicy)
  }

  for _, inp := range inputSet2 {
    eval(context.Background(), inp)
  }
}

Upvotes: 1

Views: 403

Answers (1)

tsandall
tsandall

Reputation: 1609

TLDR; this is probably not the right place for this question. File an issue on GitHub if you are seeing behaviour you think is wrong.

To answer your question about how to prepare queries properly, your main function should (minimally) look like this:

func main() {
  pq1 := loadPolicies(policySet1)
  
  for _, inp := range inputSet1 {
    eval(pq1, inp)
  }

  pq2 := loadPolicies(policySet2)
 
  for _, inp := range inputSet2 {
    eval(pq2, inp)
  }
}

The easiest way to implement loadPolicies for the above example would be something like:

func loadPolicies(policySet) {

  opts := []func(*rego.Rego){}

  // setup prepared query. load all modules.
  for _, p := range policySet {
    opts = append(opts, rego.Module(p.filename, p.regoPolicy))
  }

  // create new prepared query
  return rego.New(rego.Query("data.report"), opts...).PrepareForEval()
}

Then your eval function becomes:

func eval(pq, inp) {
   rs, err := pq.Eval(rego.EvalInput(inp))
   // handle err
   // interpret rs
}

Upvotes: 3

Related Questions