Skip to content

perf(decision): add evaluator p99 gate and free the connection during evaluation - #187

Merged
tiana-code merged 1 commit into
mainfrom
feat/E-05-decision-perf-gate
Jun 17, 2026
Merged

perf(decision): add evaluator p99 gate and free the connection during evaluation#187
tiana-code merged 1 commit into
mainfrom
feat/E-05-decision-perf-gate

Conversation

@tiana-code

Copy link
Copy Markdown
Owner

What

Two performance concerns for the decision engine (#176):

(A) Evaluator p99 latency gate. A pure, in-process JUnit gate (EvaluatorLatencyGateTest) parses one 100-condition all rule via the public RuleParser, warms up the JIT, then times RuleEvaluator.evaluate over 10k iterations (nanoTime around the call only) and asserts nearest-rank p99 < 10ms. Deterministic, no I/O; a regression ceiling on the hot path. Stable across repeated local runs with ~100x headroom.

(B) Free the JDBC connection during evaluation. EvaluationServiceImpl.evaluate was @Transactional, so it held a pooled connection across the up-to-200ms bounded evaluation (open-in-view is already false). The orchestrator is now non-transactional and DecisionLogWriter.write owns its own short transaction (a separate bean, so the proxy boundary is honored - no self-invocation). The bounded evaluation now runs with no connection bound to the thread; a connection is taken only for the brief audit-log write.

Correctness

  • Fail-closed audit preserved: the log write is the last call, reached only after a successful evaluation. Timeout / rule-not-found / rule-not-active all throw before any write -> no decision_logs row. Covered by the existing unit test (verify(exactly = 0)) and end-to-end by DecisionEvaluationApiIT (one row on success, zero on timeout).
  • No LazyInitializationException: the only fields read after load are eager scalar columns (RuleVersionEntity.dsl/id, DecisionRuleEntity.activeVersionId); no lazy association is traversed outside a transaction.
  • A reflection test (EvaluationTransactionBoundaryTest) pins the moved boundary so the connection-hold cannot silently regress.

Tests

4 new tests (2 perf + 2 structural); existing EvaluationServiceImplTest (4) unchanged and green. DecisionEvaluationApiIT is the CI verifier for (B).

Gates

critic GO (no must-fixes; verified eager columns, all no short-circuit, separate-bean proxy) -> code-reviewer APPROVE-WITH-NITS (member-reference reflection, trimmed KDoc, shouldBeLessThan - all folded) -> security-auditor PASS (fail-closed impossible-to-write-on-failure, no regression, connection released, boundary/secrets clean) -> evaluator 0.93 PASS.

Note: the same read-only connection-hold exists in ReplayServiceImpl (@Transactional(readOnly), writes nothing); deferred as read-only and out of scope for #176.

Closes #176

… evaluation

Add an in-process latency gate asserting p99 under 10ms for evaluating a
100-condition rule (warm-up then nearest-rank p99 over the timed evaluate
calls), so a future evaluator regression fails on CI.

Stop holding a JDBC connection across the bounded evaluation: evaluate is
no longer transactional and the audit-log write owns its own short
transaction, so the up-to-timeout evaluation no longer pins a pooled
connection. Reads stay simple finds on immutable data and fail-closed is
preserved (the log is written only after a successful evaluation). A
reflection test pins the moved boundary.

Closes #176
@tiana-code tiana-code added this to the v0.2.0 milestone Jun 17, 2026
@tiana-code
tiana-code merged commit f37d140 into main Jun 17, 2026
8 checks passed
@tiana-code
tiana-code deleted the feat/E-05-decision-perf-gate branch June 17, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

F-05.9 Performance gate: p99 < 10ms for <=100 rules

1 participant