Hello,
I am coding a parsing of rule engine with CEL-GO. Saddly, it costs a lot of performance in my server than cel.Compile(expression).
So, the solution I came up with is to cache the ast result generated by cel.Compile(expression) into memory in a separate goroutine; other business goroutines access the cache concurrently, use expression to directly check the corresponding ast from the cache, and pass it to Different env, then env can directly call the Program method and Eval method to complete the expression calculation. This avoids repeatedly compiling expressions, which improves performance.
code example:
// in cache produce goroutine
func productAst() (astCache map[string]*expr.CheckedExpr) {
astCache = make(map[string]*expr.CheckedExpr)
env, _ := NewEnv(nil)
for _, v := range rules {
ast, iss := env.Compile(v)
fmt.Printf("rule=%v, iss=%v\n", v, iss)
checkedExpr, err := cel.AstToCheckedExpr(ast)
fmt.Printf("rule=%v, err=%v\n", v, err)
astCache[v] = checkedExpr
}
return astCache
}
// in business goroutine, read astCache
for i, req := range requests {
wg.Add(1)
go func(i int, req *Req) {
defer wg.Done()
ctx := map[string]interface{}{
"req": req,
}
env, _ := NewEnv(ctx)
for _, exp := range expressions {
// ignore the question for concurrently read and write map, just demo
ast := cel.CheckedExprToAst(AstCache[exp])
prg, err := env.Program(ast)
if err != nil {
fmt.Printf("exp=%v: %v\n", exp, err)
}
out, detail, err := prg.Eval(map[string]interface{}{})
fmt.Printf("go[%v], exp=%v: out=%v, detail=%v, err=%v\n\n\n", i, exp, out, detail, err)
}
}(i, req)
My questions are:
In a concurrent scenario, if multiple goroutines jointly access the same ast object, will there be concurrency issues?
Will the variables in this ast object be written?
Is the ast a read-only object after it is generated?
Or what is the best way to cache the AST?
Thank you