The Rule Engine is designed to evaluate thousands of rules per second on small input payloads. The cost model is simple: every rule walks its condition tree once, helper functions iterate their lists, and resolved actions are merged into the response. Most performance issues come from rule shape, not engine internals.
What's cached
- Compiled rule sets. Once a YAML/JSON rule set is loaded, the parsed rule tree is cached in process memory. Updating a rule set via
PUT /rules/sets/:idinvalidates the cache for that ID. - Helper function bodies. Built-in helpers are pre-bound. Custom helpers registered via
POST /rules/helpersare compiled once at registration. - Validation results for unchanged YAML/JSON content (when the same string hashes to the same key).
Per-execution context is not cached — the engine evaluates rules deterministically against the input every time.
Indexed evaluation
Rules don't have a database index — evaluation is in-memory. To "index" a rule set, structure it so the cheapest condition runs first:
- Put cheap field-equality checks before iterations and aggregates.
- Use
varsto bind a filtered subset once, then reference it in subsequent conditions instead of re-filtering. - Short-circuit with
AND: place the most selective predicate first.
# Slow: filters twice
- id: BAD
when: COUNT(FILTER(order.lines, _.qty > _.stock)) > 0 AND order.total > 100
# Faster: filter once, reuse
- id: GOOD
vars:
- bad = FILTER(order.lines, _.qty > _.stock)
when: order.total > 100 AND COUNT(bad) > 0
Batch execution
/rule-engine/batch-executeJWTRun the same rules against many inputs in one request. The engine reuses the parsed rule tree and parallelizes evaluation up to options.maxConcurrency.
{
"rules": [ /* parsed once */ ],
"dataArray": [ { "order": {...} }, { "order": {...} } ],
"options": { "batchSize": 100, "maxConcurrency": 8 }
}
Use this for nightly recomputation of pricing, scoring, or eligibility across a catalog.
Retries and timeouts
/rule-engine/execute-with-retryJWTWraps execution with retry logic — useful when a rule calls into a remote helper. Config: maxRetries, retryDelay, backoffMultiplier.
options.timeout (milliseconds) on any execute call aborts the run when exceeded. Default is no timeout. Set one in production: 5,000 ms is a reasonable ceiling for transactional pricing and eligibility paths.
Performance tips
- Keep helpers cheap. A custom helper that hits the database on every invocation will dominate execution time. Cache lookups outside the helper or denormalize the data into the input.
- Pre-filter the input. If a rule only inspects orders over $1000, filter at the call site rather than running the rule on every order.
- Use
varsto memoize. Compute a list filter once, reference it in multiple conditions. - Watch
forloops. A loop over 10,000 line items with a complexdoblock multiplies cost by 10,000. Prefer aggregates (SUM,COUNT,FILTER) where possible. - Avoid regex in hot paths.
MATCHESis convenient but ~10x slower than equality. Pre-compute the bucket if you can. - Profile via
performance-metrics.GET /rules/performance-metrics?ruleSetId=...gives you per-rule-set p50/p95 latency. Slow rules show up there first.
When the engine isn't the bottleneck
If a rule-set evaluates fast in isolation but slow in production, the cause is usually upstream — input fetching, helper RPC calls, or sequential request patterns. The engine itself is bounded by the depth of the rule tree and the size of any list inside a for or FILTER.
Run GET /rule-engine/helpers to confirm what's registered. A rule that references an unregistered helper fails with Helper not found rather than degrading silently.