Service Level Objectives — Vanzik v2.0
vanzik v2.0updated view on github
Status: baseline established (§Baseline below).
Targets below are operational expectations for a single-replica
deployment running the in-process file-backed audit logger (the default);
revisit when deployments adopt the store-backed audit
(--audit-backend=store). Multi-node deployments (PostgreSQL
— see COMPATIBILITY.md) apply these targets
per replica; reconciliation is background-only and does not touch the
/v1/check path.
Service in scope#
POST /v1/check — the policy enforcement endpoint that AI agents call
before every sensitive action. Every other surface (approvals, audit
queries, dashboard, SSE) is best-effort and not part of this SLO.
Workload assumptions#
The targets below assume:
- Single-replica Vanzik process on commodity hardware (modern x86 laptop / small cloud VM, ≥4 cores, ≥8 GiB RAM, local SSD).
- In-process file-backed audit logger (
audit.FileLoggerwriting to local SSD). Network-attached storage will degrade the audit-write tail. - No upstream network calls in the hot path —
/v1/checkdoes not hit a webhook or Slack on the synchronous reply (notifications fan out on a non-blocking queue; seepkg/notify). - All rules fit in memory. The engine evaluates against a single
*Policysnapshot under a read lock; we have not modeled policies larger than a few hundred rules. require_priorconditions are map lookups. The server answers them from its in-memoryPriorActionIndex, not by scanning the audit log (seePOLICY_REFERENCE.md), so they stay inside these targets. Only an embedder that wires aHistoryQuerieralone gets a log-scan path.- Cost-scope traffic is a minority of total volume. Cost requests acquire a write lock; sustained heavy contention on the cost scope is out-of-scope.
Targets#
| Metric | Target | Why |
|---|---|---|
/v1/check p50 latency |
< 1 ms | Engine path is in-memory; audit write is a single json.Encode to a buffered file. |
/v1/check p95 latency |
< 3 ms | Allows one fsync per request to stay inside 3 ms. |
/v1/check p99 latency |
< 5 ms at 1k RPS | GC pause + sporadic file-system sync. |
| Throughput (sustained) | ≥ 1 000 RPS | Per replica; scale horizontally with the PostgreSQL backend (per-replica targets unchanged — reconciliation is background-only). |
| Audit-write durability lag | < 100 ms | Time between Log() returning and the entry being readable by Query. |
| Approval-required emit lag | < 250 ms | notify.Dispatcher queue depth + a single webhook hop. |
These targets are backed two ways: the micro-benchmarks in
§Baseline below, and a CI latency gate (TestEngineCheck_P99LatencyGate,
pkg/policy) that fails the build if Engine.Check p99 crosses the 3 ms
budget with the persistence syncer active (measured ≪0.1 ms for the bare
engine; ~0.53 ms for the full HTTP /v1/check path). Production
validation against a real workload (realistic policy sizes and
concurrency) remains open — see §Validation plan.
Out of scope for this SLO#
- Cost-scope under heavy contention.
Engine.Check(scope="cost")takes a write lock so cost reservation and decision are atomic. Many concurrent cost checks against the sameEngineinstance will serialize. We do not bound p99 in this regime. - Startup replay.
NewServerre-reads the audit log to seed in-memory decision counters. A multi-GiB log delays the first accurate/metricsscrape; this is documented inOPERATIONS.mdand not part of the steady-state SLO. - CSRF / session-store contention on the dashboard auth path. The dashboard is a developer tool, not a load-bearing surface.
Baseline (captured on v0.5.0 — still the recorded floor)#
Numbers below were captured on 2026-05-05 with make bench on an AMD
Ryzen 7 5800X (16-thread, Windows 11, Go 1.26.1) using a local SSD.
They are micro-benchmarks of the hot-path primitives, not full
end-to-end RPS measurements — but they bound the per-request floor.
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
BenchmarkEngineCheck_AllowFastPath |
284.9 | 104 | 6 |
BenchmarkEngineCheck_DenyDeepMatch (100 allows + 1 deny match) |
215.6 | 72 | 4 |
BenchmarkGlobMatch_DoubleStar/Match |
200.0 | 144 | 2 |
BenchmarkGlobMatch_DoubleStar/NoMatch |
196.9 | 144 | 2 |
BenchmarkFileLogger_Log (single-threaded append) |
4 546 | 336 | 2 |
BenchmarkDispatcher_Send_QueueFull (drop path under saturation) |
3 010 | 483 | 30 |
Reading the numbers:
- Engine
Checkfor the common allow path is ≈ 285 ns / 6 allocs. With a per-request audit write of ≈ 4.5 µs, the synchronous floor for/v1/checklands well under the p50 < 1 ms target with three orders of magnitude of headroom for HTTP framing and OS scheduling. - The deny-deep-match path (walk past 100 allows to a deny) is actually
cheaper per-op than the allow fast path — the deny match short-
circuits before any allow allocation, and
formatRulefor the matching deny does fewer string ops than the allow message format. This bounds the worst-caseCheck()shape we ship by default. globMatchwith**against a 5-segment path is ≈ 200 ns regardless of match/no-match. The two-allocation cost is the segment-split slices.FileLogger.Logat 4.5 µs / 2 allocs / 336 B is within an order of magnitude of the syscall floor for anO_APPENDwrite of a ≈ 200 B JSON line. Replacing the per-recordjson.Encoderwith batched writes is the obvious next optimisation.Dispatcher.Sendon a fully wedged queue cost ≈ 3 µs at this capture. Current builds drop the event without allocating (0 B/op, 0 allocs/op); the 483 B / 30 allocs in the table are from the baseline build.
Re-running the baseline#
make bench
The target runs go test -bench=. -benchmem -run=^$ ./... so only
benchmarks fire (-run=^$ skips regular tests) and allocations are
reported. Capture the numbers and replace the table above when the
benchmarks shift materially (≥ 10 % regression).
Validation plan#
The benchmarks above are the recorded floor. CI enforces the
engine-path budget on every push via TestEngineCheck_P99LatencyGate
plus the HTTP-path persistence-on check
(TestIntegration_HotPathLatencyWithPersistence). What remains for full
SLO validation:
- A
vegeta/heyharness againstvanzik serverrunning with--policy configs/default.yamland a tmpfs-backed audit log, driving 1 kRPS sustained for 5 minutes. Not automated yet (the in-suite concurrent soak —pkg/persist, 32 workers, ~11 kRPS locally — is the closest current proxy for it). - Latency histograms exported via
/metrics(vanzik_request_duration_ms,vanzik_policy_eval_duration_ms,vanzik_audit_write_duration_ms). - p99 reported by Prometheus'
histogram_quantile(0.99, …)over a 30-second window.
Until the harness is automated, the in-process micro-benchmarks above are the contract.