Most performance claims in the rules-engine market are unfalsifiable. "Sub-second response times." "Massive scale." "Enterprise-grade throughput." None of it tells you what to put in a capacity plan.
This article does the opposite. Below are the measured results of two k6 load tests run against DecisionRules on Kubernetes, including the test conditions, the percentile spreads, and the exact point at which latency starts to degrade. Every number is reproducible from the raw k6 reports, and every number is scoped to the configuration that produced it.
The short version: a single medium-complexity business rule solves in under 10 milliseconds on average — and stays under 10 ms all the way up to roughly 8,900 solves per second. Pushed to maximum throughput, the same platform sustains over 15,000 solves per second with average latency of only 13.1 milliseconds. Throughput and latency are not the trade-off most people expect, provided the deployment is allowed to scale horizontally.
Summary of verified figures
| Metric | Value | Conditions | |
|---|---|---|---|
| Average latency, single decision table | 8.1 ms | ~5,200 solves/s, 50 concurrent clients | |
| Average latency, single decision tree | 8.2 ms | ~5,200 solves/s, 50 concurrent clients | |
| Average latency, 3-table flow rule | 12.1 ms | ~5,200 solves/s, 50 concurrent clients | |
| Blended average latency, moderate load | 9.5 ms | ~5,200 solves/s, all rule types | |
| Blended average latency, max throughput | 13.1 ms | ~15,200 solves/s, 200 concurrent clients | |
| p95 latency, max throughput | 29.0 ms | ~15,200 solves/s, 200 concurrent clients | |
| Sustained throughput, elastic scaling | ~15,200 solves/s | 200 concurrent clients, HPA ceiling 11 replicas | |
| Sustained throughput, capped at 5 replicas | ~9,800 solves/s | 200 concurrent clients, HPA ceiling 5 replicas | |
| Total requests across both runs | 23,170,203 | ~42 minutes of combined test time | |
| Failed requests across both runs | 0 | error rate 0.000% |
All latency figures are client-observed round-trip time (http_req_duration in k6) — including network transit to the ingress and back, not just server-side execution.
What we tested and how
Both runs used k6 driving the DecisionRules Rule Solver API over HTTP against DecisionRules deployed on Kubernetes with a Horizontal Pod Autoscaler. Load generators ran inside the same region as the cluster. Response validation was enabled on every call: a request counts as successful only if it returns HTTP 200 and the decision output passes a content check. Across 23.1 million calls, 100% of checks passed.
Each k6 iteration executed one business transaction consisting of three separate Rule Solver calls:
| Rule | Type | Complexity | |
|---|---|---|---|
| Rule 1 | Decision Table, 20 rows | Medium | |
| Rule 2 | Decision Tree | Medium | |
| Rule 3 | Flow — executes three decision tables, then performs summary calculations over their combined results | Higher |
This mix is deliberate. Rules 1 and 2 represent the everyday case: a single, self-contained piece of decision logic. Rule 3 represents an orchestrated decision process, where one API call triggers multiple rules and post-processing. Testing both in the same run lets us separate the cost of solving a rule from the cost of orchestrating a process.
k6 reports iterations per second, and each iteration here is three Solver calls. All throughput figures in this article are recalculated to solves per second.
Payloads were realistic but not large: roughly 422 bytes of input JSON per call and 1.2 KB of decision output per response.
Virtual-user count was stepped through 50 → 100 → 150 → 200, holding each level for five minutes so the autoscaler could settle before measurements were taken. The two runs were identical in every respect except the autoscaler ceiling — 11 replicas in one, 5 in the other.
Normal load: single rules solve in under 10 milliseconds
At 50 concurrent clients — a sustained 5,243 solves per second, which is already substantial production traffic — the per-rule figures are:
| Rule | Average | Median | p90 | p95 | p99 | |
|---|---|---|---|---|---|---|
| Decision Table (20 rows) | 8.1 ms | 6.9 ms | 13.0 ms | 14.8 ms | 18.1 ms | |
| Decision Tree | 8.2 ms | 6.8 ms | 13.4 ms | 15.2 ms | 18.7 ms | |
| Flow (3 tables + calculations) | 12.1 ms | 10.2 ms | 19.4 ms | 21.3 ms | 25.1 ms | |
| Blended, all calls | 9.5 ms | 8.4 ms | 16.2 ms | 18.4 ms | 22.9 ms |
A medium-complexity decision table or decision tree resolves in roughly 8 milliseconds average, under 7 milliseconds at median, measured client-side with network round-trip included. Two-thirds of that budget is the rule solve; the remainder is HTTP and transit.
The sub-10-millisecond figure is not a low-load artifact. It holds as throughput climbs:
| Throughput | Decision Table (avg) | Decision Tree (avg) | Under 10 ms? | |
|---|---|---|---|---|
| 5,243 solves/s | 8.1 ms | 8.2 ms | Yes | |
| 8,908 solves/s | 9.6 ms | 9.7 ms | Yes | |
| 12,302 solves/s | 10.3 ms | 10.6 ms | Median still under 10 ms | |
| 15,215 solves/s | 11.1 ms | 11.5 ms | No — 11 ms |
Single-rule average latency stays below 10 milliseconds up to approximately 8,900 solves per second, and the median stays below 10 milliseconds up to approximately 12,300 solves per second. Only at the highest tested load does a single rule cost more than 11 milliseconds.
If your workload is dominated by individual decision tables and trees rather than multi-step orchestration, sub-10-millisecond average response is a reasonable expectation, not a best case.
What the flow rule costs
The flow rule — three decision tables plus summary calculations, all behind one API call — averaged 12.1 ms at moderate load and 16.6 ms at maximum throughput. Against a single table at the same load, that is a consistent 1.5× multiplier, stable across every concurrency level tested.
That ratio is the interesting part. The flow does at least three times the rule-solving work of a single decision table, plus aggregation over the results, for about half again the latency. Executing those three tables as three separate Solver API calls would cost roughly 33 milliseconds at maximum throughput — three round-trips, three HTTP handshakes, three sets of transit. Orchestrating them inside one flow costs 16.6 ms.
Consolidating multi-step decision logic into a flow is roughly twice as fast as calling the rules individually from your application.
Maximum throughput: 15,200 solves per second
Stepping concurrency up against a deployment allowed to scale to 11 replicas:
| Concurrent clients | Sustained throughput | Median | Average | p90 | p95 | p99 | |
|---|---|---|---|---|---|---|---|
| 50 | 5,243 solves/s | 8.4 ms | 9.5 ms | 16.2 ms | 18.4 ms | 22.9 ms | |
| 100 | 8,908 solves/s | 9.4 ms | 11.2 ms | 19.4 ms | 23.1 ms | 29.1 ms | |
| 150 | 12,302 solves/s | 10.5 ms | 12.1 ms | 22.3 ms | 27.2 ms | 35.2 ms | |
| 200 | 15,215 solves/s | 11.9 ms | 13.1 ms | 24.1 ms | 29.0 ms | 37.9 ms |
Read the first and last rows together. Throughput increased 2.9×. Average latency increased by 3.6 milliseconds. Median latency increased by 3.5 milliseconds.
This is close to linear scaling. Each concurrency step of 50 clients bought roughly 3,400 additional solves per second, and the marginal latency cost of each step stayed under 2 ms. The system was still scaling at the point the test ended — 15,215 solves/s is the highest rate we measured, not a ceiling we found.
Sustained, that rate represents 54.8 million decisions per hour, or 1.31 billion per day. Zero requests failed across all 12,756,996 calls in this run.
Full transaction latency
Because each iteration made all three calls in sequence, the run also measures the complete business transaction:
| Throughput | Transaction average | Median | p95 | p99 | |
|---|---|---|---|---|---|
| 5,243 solves/s | 28.6 ms | 24.3 ms | 47.9 ms | 53.5 ms | |
| 8,908 solves/s | 33.7 ms | 29.6 ms | 62.2 ms | 68.2 ms | |
| 12,302 solves/s | 36.6 ms | 35.1 ms | 76.2 ms | 82.8 ms | |
| 15,215 solves/s | 39.4 ms | 37.9 ms | 82.4 ms | 90.5 ms |
A complete decision process — a decision table, a decision tree, and a three-table flow, executed sequentially as three API calls — completes in 28.6 ms at moderate load and 39.4 ms at maximum throughput. Transaction latency tracks the sum of its parts almost exactly, which means the overhead of issuing multiple sequential Solver calls is negligible; the cost is the rules themselves.
The one that saturated — and why we're showing it
The second run was identical in every respect except one: the autoscaler ceiling was set to 5 replicas instead of 11. This is the most instructive result in the set.
| Concurrent clients | HPA max 5 | HPA max 11 | Throughput gap | |
|---|---|---|---|---|
| 50 | 5,529 solves/s | 5,243 solves/s | — | |
| 100 | 9,115 solves/s | 8,908 solves/s | — | |
| 150 | 9,540 solves/s | 12,302 solves/s | - 23% | |
| 200 | 9,794 solves/s | 15,215 solves/s | - 36% |
And the corresponding latency:
| Concurrent clients | Avg (max 5) | Avg (max 11) | p95 (max 5) | p95 (max 11) | |
|---|---|---|---|---|---|
| 50 | 9.0 ms | 9.5 ms | 15.1 ms | 18.4 ms | |
| 100 | 10.9 ms | 11.2 ms | 22.2 ms | 23.1 ms | |
| 150 | 15.7 ms | 12.1 ms | 27.9 ms | 27.2 ms | |
| 200 | 20.3 ms | 13.1 ms | 35.4 ms | 29.0 ms |
Up to 100 concurrent clients the two configurations are indistinguishable — the capped deployment is even marginally faster, because five replicas were sufficient and the request distribution was tighter. Past that point they diverge sharply. The capped deployment hits its ceiling at roughly 9,800 solves/s and stops gaining throughput; additional load converts into queueing, and average latency more than doubles from 9.0 ms to 20.3 ms. Single-rule latency degrades in step, from 7.6 ms to 17.4 ms.
It is worth stating plainly: the capped deployment did not fail. Zero errors across 10.4 million requests, p99 still under 47 ms, all configured latency thresholds still passed. It simply stopped getting faster.
A rules engine that degrades gracefully under saturation is arguably more valuable than one that is fast until it isn't. But the deployment lesson is direct: if your peak load exceeds what your replica ceiling can absorb, raise the ceiling. The engine scales; the constraint here was autoscaler configuration, not the software.
How to use these numbers
If you are sizing a deployment. The saturated run gives the cleanest per-replica figure: five replicas plateaued at approximately 9,800 solves/s, or roughly 1,950 solves/s per replica at saturation. For headroom, plan on ~1,400 solves/s per replica and set your HPA maximum above the resulting count, not at it. (Pod counts were not recorded in the k6 output, so these are derived from autoscaler ceilings rather than observed replica counts.)
If you have a latency SLA. For a single medium-complexity decision table or tree under normal load, budget 20 ms at p99, measured client-side in-region. For a flow that orchestrates several tables, budget 25–41 ms at p99 depending on load. For a three-call business transaction, budget 90 ms at p99 at maximum throughput.
If you are designing decision logic. Consolidate. Three rules called individually cost roughly twice as much as the same three rules orchestrated inside one flow, because you pay the network round-trip once instead of three times.
If you are comparing engines. Compare percentile distributions, not averages. An average is easy to win by being fast on the happy path. The p50-to-p99 spread tells you whether the system queues, and queueing is what breaks production systems.
Scope and limitations
These results describe the configurations tested and should not be generalized past them:
- Rule complexity drives latency more than anything else. These tests used a 20-row decision table, a decision tree, and a three-table flow, all of medium complexity. A 10,000-row decision table, deeply nested workflows, or heavy scripting rules will produce different figures.
- Payload size matters. These tests used ~422-byte requests and ~1.2 KB responses. Substantially larger payloads will shift both latency and throughput.
- Network topology is excluded. Load generators ran in-region. Cross-region or cross-continent callers add transit time that has nothing to do with the engine.
- These are not maximum achievable figures. The elastic run was still scaling linearly when it ended. We report what we measured, not what we extrapolate.
- Throughput figures are recalculated, not taken from the k6 dashboard display, for the reason described in the What we tested and how section above.
Testing performed with k6 against DecisionRules on Kubernetes with horizontal pod autoscaling. Latency measured as client-observed HTTP round-trip time (http_req_duration). Throughput recalculated from raw interval counts. Raw k6 reports available on request.
The takeaway
Every figure here comes from a k6 report you could reproduce, with the conditions stated, the percentiles published rather than averaged away, and the run that saturated shown alongside the one that did not. That last part is the point. A benchmark that only shows the configuration where everything went well tells you nothing about what happens when it does not.
If you are evaluating a rules engine, ask for the same. If percentile distributions and stated conditions are not available, what you are being shown is not a measurement.
To test DecisionRules against your own rules and payloads, book a demo or start a free trial.