Skip to content

Commit

Permalink
Add a new inter-query value cache to cache data across queries
Browse files Browse the repository at this point in the history
This commit adds a new inter-query value cache that built-in
functions can use to cache information across queries.
For example, the `regex` and `glob` builtins can use this
to cache compiled regex and glob match patterns respectively.

The number of entries in the cache can be configured via the OPA
config. By default there is no limit.

Fixes: #6908

Signed-off-by: Ashutosh Narkar <[email protected]>
  • Loading branch information
ashutosh-narkar committed Sep 23, 2024
1 parent f492f96 commit 2c56293
Show file tree
Hide file tree
Showing 21 changed files with 1,021 additions and 381 deletions.
14 changes: 9 additions & 5 deletions docs/content/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -856,11 +856,15 @@ Caching represents the configuration of the inter-query cache that built-in func
functions provided by OPA, `http.send` is currently the only one to utilize the inter-query cache. See the documentation
on the [http.send built-in function](../policy-reference/#http) for information about the available caching options.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `caching.inter_query_builtin_cache.max_size_bytes` | `int64` | No | Inter-query cache size limit in bytes. OPA will drop old items from the cache if this limit is exceeded. By default, no limit is set. |
| `caching.inter_query_builtin_cache.forced_eviction_threshold_percentage` | `int64` | No | Threshold limit configured as percentage of `caching.inter_query_builtin_cache.max_size_bytes`, when exceeded OPA will start dropping old items permaturely. By default, set to `100`. |
| `caching.inter_query_builtin_cache.stale_entry_eviction_period_seconds` | `int64` | No | Stale entry eviction period in seconds. OPA will drop expired items from the cache every `stale_entry_eviction_period_seconds`. By default, set to `0` indicating stale entry eviction is disabled. |
It also represents the configuration of the inter-query value cache that built-in functions can utilize. Currently, this
cache is utilized by the `regex` and `glob` built-in functions for compiled regex and glob match patterns respectively.

| Field | Type | Required | Description |
|--------------------------------------------------------------------------| --- | --- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `caching.inter_query_builtin_cache.max_size_bytes` | `int64` | No | Inter-query cache size limit in bytes. OPA will drop old items from the cache if this limit is exceeded. By default, no limit is set. |
| `caching.inter_query_builtin_cache.forced_eviction_threshold_percentage` | `int64` | No | Threshold limit configured as percentage of `caching.inter_query_builtin_cache.max_size_bytes`, when exceeded OPA will start dropping old items permaturely. By default, set to `100`. |
| `caching.inter_query_builtin_cache.stale_entry_eviction_period_seconds` | `int64` | No | Stale entry eviction period in seconds. OPA will drop expired items from the cache every `stale_entry_eviction_period_seconds`. By default, set to `0` indicating stale entry eviction is disabled. |
| `caching.inter_query_builtin_value_cache.max_num_entries` | `int` | No | Maximum number of entries in the Inter-query value cache. OPA will drop random items from the cache if this limit is exceeded. By default, set to `0` indicating unlimited size. |

## Distributed tracing

Expand Down
19 changes: 10 additions & 9 deletions internal/rego/opa/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ type Result struct {

// EvalOpts define options for performing an evaluation.
type EvalOpts struct {
Input *interface{}
Metrics metrics.Metrics
Entrypoint int32
Time time.Time
Seed io.Reader
InterQueryBuiltinCache cache.InterQueryCache
NDBuiltinCache builtins.NDBCache
PrintHook print.Hook
Capabilities *ast.Capabilities
Input *interface{}
Metrics metrics.Metrics
Entrypoint int32
Time time.Time
Seed io.Reader
InterQueryBuiltinCache cache.InterQueryCache
InterQueryBuiltinValueCache cache.InterQueryValueCache
NDBuiltinCache builtins.NDBCache
PrintHook print.Hook
Capabilities *ast.Capabilities
}
6 changes: 5 additions & 1 deletion plugins/discovery/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1905,7 +1905,11 @@ func TestReconfigureWithLocalOverride(t *testing.T) {
*period = 10
threshold := new(int64)
*threshold = 90
expectedCacheConf := &cache.Config{InterQueryBuiltinCache: cache.InterQueryBuiltinCacheConfig{MaxSizeBytes: maxSize, StaleEntryEvictionPeriodSeconds: period, ForcedEvictionThresholdPercentage: threshold}}
maxNumEntriesInterQueryValueCache := new(int)
*maxNumEntriesInterQueryValueCache = 0

expectedCacheConf := &cache.Config{InterQueryBuiltinCache: cache.InterQueryBuiltinCacheConfig{MaxSizeBytes: maxSize, StaleEntryEvictionPeriodSeconds: period, ForcedEvictionThresholdPercentage: threshold},
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{MaxNumEntries: maxNumEntriesInterQueryValueCache}}

if !reflect.DeepEqual(cacheConf, expectedCacheConf) {
t.Fatalf("want %v got %v", expectedCacheConf, cacheConf)
Expand Down
2 changes: 1 addition & 1 deletion plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ func (m *Manager) Labels() map[string]string {
return m.Config.Labels
}

// InterQueryBuiltinCacheConfig returns the configuration for the inter-query cache.
// InterQueryBuiltinCacheConfig returns the configuration for the inter-query caches.
func (m *Manager) InterQueryBuiltinCacheConfig() *cache.Config {
m.mtx.Lock()
defer m.mtx.Unlock()
Expand Down
10 changes: 9 additions & 1 deletion plugins/plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,14 +396,16 @@ func TestPluginManagerInitIdempotence(t *testing.T) {
}

func TestManagerWithCachingConfig(t *testing.T) {
m, err := New([]byte(`{"caching": {"inter_query_builtin_cache": {"max_size_bytes": 100}}}`), "test", inmem.New())
m, err := New([]byte(`{"caching": {"inter_query_builtin_cache": {"max_size_bytes": 100}, "inter_query_builtin_value_cache": {"max_num_entries": 100}}}`), "test", inmem.New())
if err != nil {
t.Fatal(err)
}

expected, _ := cache.ParseCachingConfig(nil)
limit := int64(100)
expected.InterQueryBuiltinCache.MaxSizeBytes = &limit
maxNumEntriesInterQueryValueCache := int(100)
expected.InterQueryBuiltinValueCache.MaxNumEntries = &maxNumEntriesInterQueryValueCache

if !reflect.DeepEqual(m.InterQueryBuiltinCacheConfig(), expected) {
t.Fatalf("want %+v got %+v", expected, m.interQueryBuiltinCacheConfig)
Expand All @@ -414,6 +416,12 @@ func TestManagerWithCachingConfig(t *testing.T) {
if err == nil {
t.Fatal("expected error but got nil")
}

// config error
_, err = New([]byte(`{"caching": {"inter_query_builtin_value_cache": {"max_num_entries": "100"}}}`), "test", inmem.New())
if err == nil {
t.Fatal("expected error but got nil")
}
}

func TestManagerWithNDCachingConfig(t *testing.T) {
Expand Down
195 changes: 110 additions & 85 deletions rego/rego.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,32 +99,33 @@ type preparedQuery struct {
// EvalContext defines the set of options allowed to be set at evaluation
// time. Any other options will need to be set on a new Rego object.
type EvalContext struct {
hasInput bool
time time.Time
seed io.Reader
rawInput *interface{}
parsedInput ast.Value
metrics metrics.Metrics
txn storage.Transaction
instrument bool
instrumentation *topdown.Instrumentation
partialNamespace string
queryTracers []topdown.QueryTracer
compiledQuery compiledQuery
unknowns []string
disableInlining []ast.Ref
parsedUnknowns []*ast.Term
indexing bool
earlyExit bool
interQueryBuiltinCache cache.InterQueryCache
ndBuiltinCache builtins.NDBCache
resolvers []refResolver
sortSets bool
copyMaps bool
printHook print.Hook
capabilities *ast.Capabilities
strictBuiltinErrors bool
virtualCache topdown.VirtualCache
hasInput bool
time time.Time
seed io.Reader
rawInput *interface{}
parsedInput ast.Value
metrics metrics.Metrics
txn storage.Transaction
instrument bool
instrumentation *topdown.Instrumentation
partialNamespace string
queryTracers []topdown.QueryTracer
compiledQuery compiledQuery
unknowns []string
disableInlining []ast.Ref
parsedUnknowns []*ast.Term
indexing bool
earlyExit bool
interQueryBuiltinCache cache.InterQueryCache
interQueryBuiltinValueCache cache.InterQueryValueCache
ndBuiltinCache builtins.NDBCache
resolvers []refResolver
sortSets bool
copyMaps bool
printHook print.Hook
capabilities *ast.Capabilities
strictBuiltinErrors bool
virtualCache topdown.VirtualCache
}

func (e *EvalContext) RawInput() *interface{} {
Expand All @@ -147,6 +148,10 @@ func (e *EvalContext) InterQueryBuiltinCache() cache.InterQueryCache {
return e.interQueryBuiltinCache
}

func (e *EvalContext) InterQueryBuiltinValueCache() cache.InterQueryValueCache {
return e.interQueryBuiltinValueCache
}

func (e *EvalContext) PrintHook() print.Hook {
return e.printHook
}
Expand Down Expand Up @@ -307,6 +312,14 @@ func EvalInterQueryBuiltinCache(c cache.InterQueryCache) EvalOption {
}
}

// EvalInterQueryBuiltinValueCache sets the inter-query value cache that built-in functions can utilize
// during evaluation.
func EvalInterQueryBuiltinValueCache(c cache.InterQueryValueCache) EvalOption {
return func(e *EvalContext) {
e.interQueryBuiltinValueCache = c
}
}

// EvalNDBuiltinCache sets the non-deterministic builtin cache that built-in functions can
// use during evaluation.
func EvalNDBuiltinCache(c builtins.NDBCache) EvalOption {
Expand Down Expand Up @@ -546,64 +559,65 @@ type loadPaths struct {

// Rego constructs a query and can be evaluated to obtain results.
type Rego struct {
query string
parsedQuery ast.Body
compiledQueries map[queryType]compiledQuery
pkg string
parsedPackage *ast.Package
imports []string
parsedImports []*ast.Import
rawInput *interface{}
parsedInput ast.Value
unknowns []string
parsedUnknowns []*ast.Term
disableInlining []string
shallowInlining bool
skipPartialNamespace bool
partialNamespace string
modules []rawModule
parsedModules map[string]*ast.Module
compiler *ast.Compiler
store storage.Store
ownStore bool
txn storage.Transaction
metrics metrics.Metrics
queryTracers []topdown.QueryTracer
tracebuf *topdown.BufferTracer
trace bool
instrumentation *topdown.Instrumentation
instrument bool
capture map[*ast.Expr]ast.Var // map exprs to generated capture vars
termVarID int
dump io.Writer
runtime *ast.Term
time time.Time
seed io.Reader
capabilities *ast.Capabilities
builtinDecls map[string]*ast.Builtin
builtinFuncs map[string]*topdown.Builtin
unsafeBuiltins map[string]struct{}
loadPaths loadPaths
bundlePaths []string
bundles map[string]*bundle.Bundle
skipBundleVerification bool
interQueryBuiltinCache cache.InterQueryCache
ndBuiltinCache builtins.NDBCache
strictBuiltinErrors bool
builtinErrorList *[]topdown.Error
resolvers []refResolver
schemaSet *ast.SchemaSet
target string // target type (wasm, rego, etc.)
opa opa.EvalEngine
generateJSON func(*ast.Term, *EvalContext) (interface{}, error)
printHook print.Hook
enablePrintStatements bool
distributedTacingOpts tracing.Options
strict bool
pluginMgr *plugins.Manager
plugins []TargetPlugin
targetPrepState TargetPluginEval
regoVersion ast.RegoVersion
query string
parsedQuery ast.Body
compiledQueries map[queryType]compiledQuery
pkg string
parsedPackage *ast.Package
imports []string
parsedImports []*ast.Import
rawInput *interface{}
parsedInput ast.Value
unknowns []string
parsedUnknowns []*ast.Term
disableInlining []string
shallowInlining bool
skipPartialNamespace bool
partialNamespace string
modules []rawModule
parsedModules map[string]*ast.Module
compiler *ast.Compiler
store storage.Store
ownStore bool
txn storage.Transaction
metrics metrics.Metrics
queryTracers []topdown.QueryTracer
tracebuf *topdown.BufferTracer
trace bool
instrumentation *topdown.Instrumentation
instrument bool
capture map[*ast.Expr]ast.Var // map exprs to generated capture vars
termVarID int
dump io.Writer
runtime *ast.Term
time time.Time
seed io.Reader
capabilities *ast.Capabilities
builtinDecls map[string]*ast.Builtin
builtinFuncs map[string]*topdown.Builtin
unsafeBuiltins map[string]struct{}
loadPaths loadPaths
bundlePaths []string
bundles map[string]*bundle.Bundle
skipBundleVerification bool
interQueryBuiltinCache cache.InterQueryCache
interQueryBuiltinValueCache cache.InterQueryValueCache
ndBuiltinCache builtins.NDBCache
strictBuiltinErrors bool
builtinErrorList *[]topdown.Error
resolvers []refResolver
schemaSet *ast.SchemaSet
target string // target type (wasm, rego, etc.)
opa opa.EvalEngine
generateJSON func(*ast.Term, *EvalContext) (interface{}, error)
printHook print.Hook
enablePrintStatements bool
distributedTacingOpts tracing.Options
strict bool
pluginMgr *plugins.Manager
plugins []TargetPlugin
targetPrepState TargetPluginEval
regoVersion ast.RegoVersion
}

// Function represents a built-in function that is callable in Rego.
Expand Down Expand Up @@ -1114,6 +1128,14 @@ func InterQueryBuiltinCache(c cache.InterQueryCache) func(r *Rego) {
}
}

// InterQueryBuiltinValueCache sets the inter-query value cache that built-in functions can utilize
// during evaluation.
func InterQueryBuiltinValueCache(c cache.InterQueryValueCache) func(r *Rego) {
return func(r *Rego) {
r.interQueryBuiltinValueCache = c
}
}

// NDBuiltinCache sets the non-deterministic builtins cache.
func NDBuiltinCache(c builtins.NDBCache) func(r *Rego) {
return func(r *Rego) {
Expand Down Expand Up @@ -1309,6 +1331,7 @@ func (r *Rego) Eval(ctx context.Context) (ResultSet, error) {
EvalInstrument(r.instrument),
EvalTime(r.time),
EvalInterQueryBuiltinCache(r.interQueryBuiltinCache),
EvalInterQueryBuiltinValueCache(r.interQueryBuiltinValueCache),
EvalSeed(r.seed),
}

Expand Down Expand Up @@ -1386,6 +1409,7 @@ func (r *Rego) Partial(ctx context.Context) (*PartialQueries, error) {
EvalMetrics(r.metrics),
EvalInstrument(r.instrument),
EvalInterQueryBuiltinCache(r.interQueryBuiltinCache),
EvalInterQueryBuiltinValueCache(r.interQueryBuiltinValueCache),
}

if r.ndBuiltinCache != nil {
Expand Down Expand Up @@ -2106,6 +2130,7 @@ func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
WithIndexing(ectx.indexing).
WithEarlyExit(ectx.earlyExit).
WithInterQueryBuiltinCache(ectx.interQueryBuiltinCache).
WithInterQueryBuiltinValueCache(ectx.interQueryBuiltinValueCache).
WithStrictBuiltinErrors(r.strictBuiltinErrors).
WithBuiltinErrorList(r.builtinErrorList).
WithSeed(ectx.seed).
Expand Down Expand Up @@ -2164,7 +2189,6 @@ func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
}

func (r *Rego) evalWasm(ctx context.Context, ectx *EvalContext) (ResultSet, error) {

input := ectx.rawInput
if ectx.parsedInput != nil {
i := interface{}(ectx.parsedInput)
Expand Down Expand Up @@ -2393,6 +2417,7 @@ func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries,
WithSkipPartialNamespace(r.skipPartialNamespace).
WithShallowInlining(r.shallowInlining).
WithInterQueryBuiltinCache(ectx.interQueryBuiltinCache).
WithInterQueryBuiltinValueCache(ectx.interQueryBuiltinValueCache).
WithStrictBuiltinErrors(ectx.strictBuiltinErrors).
WithSeed(ectx.seed).
WithPrintHook(ectx.printHook)
Expand Down
Loading

0 comments on commit 2c56293

Please sign in to comment.