Outpost Performance Tuning
Outpost Performance Tuning
Section titled “Outpost Performance Tuning”The Arbitex Hybrid Outpost is designed for low-latency, high-throughput policy enforcement. This guide covers the four primary levers for tuning outpost performance: connection pool sizing, response caching, graceful drain configuration, and load testing methodology for establishing production performance baselines.
Hardware requirements
Section titled “Hardware requirements”The Outpost now delivers the same advanced PII detection as Arbitex Cloud — the Presidio + GLiNER NER backend with structured validators (passport, SSN, IBAN, Luhn) and zero-shot entity expansion — on-premises and air-gap-ready, on a GPU appliance you control.
A GPU is required for production and air-gap Outpost deployments. The NER backend (Presidio + GLiNER) and the DeBERTa Tier 3 contextual validator both run GPU inference at the throughput Outpost deployments target.
| Tier | GPU | Notes |
|---|---|---|
| Production (recommended) | NVIDIA L4 or A10 — or equivalent with ≥ 24 GB VRAM | Recommended for sustained throughput |
| Production minimum | NVIDIA T4 (16 GB VRAM) | Acceptable for low-throughput sites |
| Below T4 | Not supported | Provision at least a T4-class GPU |
A CPU-only profile remains available for development and evaluation only — it is not a supported production or air-gap target.
CPU and memory
Section titled “CPU and memory”CPU and RAM requirements are unchanged from previous releases. Size them per the sizing guidance for your expected request rate — the GPU handles model inference while CPU and memory continue to serve connection pooling, caching, audit buffering, and policy evaluation.
Connection Pool Configuration
Section titled “Connection Pool Configuration”The outpost maintains a persistent HTTP connection pool to the Arbitex platform for policy lookups and telemetry upload. Proper pool sizing prevents connection exhaustion under peak load while avoiding unnecessary resource consumption during quiet periods.
How the pool works
Section titled “How the pool works”Each time the outpost evaluates a policy or uploads audit telemetry, it draws a connection from the pool. If an idle connection is available it is reused immediately (a keepalive reuse). If no idle connection is available and the pool has not reached PLATFORM_MAX_CONNECTIONS, a new connection is created. If the pool is at capacity, the request waits in queue until a connection is released.
On startup the outpost pre-establishes PLATFORM_MAX_KEEPALIVE / 2 connections to the platform. This warmup avoids cold-start latency on the first burst of incoming traffic after a deployment.
Environment variables
Section titled “Environment variables”| Variable | Default | Range | Description |
|---|---|---|---|
PLATFORM_MAX_CONNECTIONS |
50 |
10–500 | Maximum number of concurrent connections to the platform. Requests exceeding this limit wait in queue. |
PLATFORM_MAX_KEEPALIVE |
20 |
5–200 | Maximum number of idle keepalive connections held open between requests. Must be ≤ PLATFORM_MAX_CONNECTIONS. |
PLATFORM_KEEPALIVE_EXPIRY |
60 |
10–300 | Idle keepalive connection timeout in seconds. Connections idle longer than this value are closed and removed from the pool. |
Pool metrics endpoint
Section titled “Pool metrics endpoint”Retrieve live connection pool statistics from the outpost admin API:
GET /admin/api/metrics/connectionsAuthorization: X-API-Key <admin-key>Response — ConnectionMetrics:
| Field | Type | Description |
|---|---|---|
active_connections |
int | Connections currently in use by an in-flight request. |
idle_connections |
int | Connections held open and available for immediate reuse. |
max_connections |
int | The effective value of PLATFORM_MAX_CONNECTIONS. |
total_created |
int | Total connections created since process start (cumulative). |
total_reused |
int | Total keepalive connection reuses since process start (cumulative). |
pool_wait_count |
int | Number of requests that had to wait for a free connection since process start. |
pool_wait_duration_ms |
float | Average wait time in milliseconds for requests that queued for a connection. |
Example response:
{ "active_connections": 12, "idle_connections": 8, "max_connections": 50, "total_created": 1847, "total_reused": 94302, "pool_wait_count": 3, "pool_wait_duration_ms": 2.4}A non-zero pool_wait_count that grows over time indicates that PLATFORM_MAX_CONNECTIONS is too low for the current traffic load. Increase the value and monitor until pool_wait_count stops accumulating. A high total_reused relative to total_created confirms keepalive connections are working effectively.
Response Caching
Section titled “Response Caching”The outpost can cache policy evaluation responses in memory to reduce platform round-trips for repeated identical requests. Caching is most effective for workloads where many requests share the same authentication context, path, and request shape — for example, API gateway traffic where a small set of routes is called repeatedly by many clients.
How the cache works
Section titled “How the cache works”Before forwarding a policy evaluation request to the platform, the outpost computes a cache key from the normalized request:
- HTTP method (uppercased)
- Request path (normalized)
- Headers included in policy evaluation (as configured in policy rules)
- SHA-256 hash of the request body (if present)
- Query parameters sorted alphabetically before hashing
The full set of inputs is hashed with SHA-256 to produce a fixed-length cache key. If a valid (non-expired) cache entry exists for that key, the cached policy response is returned immediately without contacting the platform.
Environment variables
Section titled “Environment variables”| Variable | Default | Range | Description |
|---|---|---|---|
POLICY_CACHE_ENABLED |
true |
— | Enable or disable the in-memory policy response cache. Set to false to disable caching entirely. |
POLICY_CACHE_TTL_SECONDS |
300 |
30–3600 | Time-to-live for cached policy responses in seconds. After this period, the cached entry is treated as expired and the next request for that key will contact the platform. |
Cache statistics endpoint
Section titled “Cache statistics endpoint”GET /admin/api/cache/statsAuthorization: X-API-Key <admin-key>Response — CacheStats:
| Field | Type | Description |
|---|---|---|
enabled |
bool | Whether the policy cache is currently enabled. |
entry_count |
int | Number of entries currently held in the cache. |
hit_count |
int | Total cache hits since process start (cumulative). |
miss_count |
int | Total cache misses since process start (cumulative). |
hit_rate_percent |
float | Rolling hit rate: hit_count / (hit_count + miss_count) × 100. |
eviction_count |
int | Total entries evicted due to TTL expiry since process start. |
memory_bytes |
int | Approximate memory used by the cache, in bytes. |
ttl_seconds |
int | The effective value of POLICY_CACHE_TTL_SECONDS. |
Example response:
{ "enabled": true, "entry_count": 412, "hit_count": 187430, "miss_count": 21804, "hit_rate_percent": 89.57, "eviction_count": 19312, "memory_bytes": 2097152, "ttl_seconds": 300}Cache clear endpoint
Section titled “Cache clear endpoint”Use the cache clear endpoint to force immediate re-evaluation of all cached policy decisions — for example, immediately after a policy update:
POST /admin/api/cache/clearAuthorization: X-API-Key <admin-key>Response:
{ "cleared": true, "entries_removed": 412}entries_removed reflects the number of entries that were in the cache at the time of the clear. After clearing, the next request for any previously cached key will contact the platform, which causes a brief spike in platform round-trips. In high-traffic environments, consider clearing during a low-traffic window or in conjunction with a rolling restart.
Graceful Drain
Section titled “Graceful Drain”When the outpost process receives SIGTERM — from a Kubernetes pod termination, systemctl stop, or a manual signal — it enters drain mode rather than terminating immediately. Drain mode ensures in-flight requests are completed before the process exits, preventing request failures during planned deployments and restarts.
Drain sequence
Section titled “Drain sequence”- Stop accepting new connections. The outpost immediately closes its listen socket. New connection attempts are refused. Upstream load balancers should detect this via health check failure (
GET /healthreturns non-200 or times out) and stop routing new traffic to the draining instance. - Finish in-flight requests. All requests currently being processed continue to completion.
- Wait for in-flight requests to drain. The outpost waits up to
DRAIN_TIMEOUT_SECONDSfor all in-flight requests to finish. - Forceful exit. If in-flight requests have not all completed by the timeout, the outpost forcefully closes remaining connections and exits with code 0. Any requests that did not complete are abandoned at the client level.
Environment variables
Section titled “Environment variables”| Variable | Default | Range | Description |
|---|---|---|---|
DRAIN_TIMEOUT_SECONDS |
30 |
5–120 | Maximum time in seconds to wait for in-flight requests to complete after receiving SIGTERM before forcefully terminating. |
Drain status endpoint
Section titled “Drain status endpoint”Poll this endpoint to monitor drain progress during a rolling restart:
GET /admin/api/drain/statusAuthorization: X-API-Key <admin-key>Response — DrainStatus:
| Field | Type | Description |
|---|---|---|
draining |
bool | Whether the outpost is currently in drain mode. false under normal operation. |
started_at |
ISO-8601 string | null | Timestamp when drain mode was entered, or null if not draining. |
in_flight_requests |
int | Number of requests currently being processed. Decreases toward 0 as drain completes. |
drain_timeout_seconds |
int | The effective value of DRAIN_TIMEOUT_SECONDS. |
Example — active drain:
{ "draining": true, "started_at": "2026-03-15T14:22:08Z", "in_flight_requests": 3, "drain_timeout_seconds": 30}Example — normal operation:
{ "draining": false, "started_at": null, "in_flight_requests": 0, "drain_timeout_seconds": 30}Zero-downtime rolling restart
Section titled “Zero-downtime rolling restart”Use the following procedure to restart or update outpost replicas without dropping requests. This requires at least two outpost replicas behind a load balancer.
- Send SIGTERM to one replica. The replica enters drain mode and stops accepting new connections. The load balancer routes all new traffic to the remaining healthy replicas.
- Wait for drain to complete. Poll
GET /admin/api/drain/statuson the draining replica and wait forin_flight_requeststo reach 0. Alternatively, waitDRAIN_TIMEOUT_SECONDSto guarantee the process has exited. - Start the replacement replica. Deploy the new replica and wait for its health check to return HTTP 200 on
GET /health. Do not proceed until the health check passes — the new replica is not yet in the load balancer rotation. - Confirm the new replica is healthy. Once
GET /healthreturns 200, the load balancer will begin routing traffic to it. Verify connections are being served by checkingGET /admin/api/metrics/connectionson the new replica. - Repeat for the next replica. Proceed to the next instance and repeat from step 1.
This procedure maintains service continuity throughout the update. The minimum number of healthy replicas available at any point is N − 1, where N is the total replica count. For environments that require stricter availability guarantees, complete the drain and replacement of one replica before starting the next.
Performance Benchmarking
Section titled “Performance Benchmarking”Establishing performance baselines before production deployment allows you to detect regressions early, size infrastructure correctly, and set realistic SLA expectations.
Baseline test approach
Section titled “Baseline test approach”Run load tests against a staging outpost configured to mirror production: same PLATFORM_MAX_CONNECTIONS, POLICY_CACHE_TTL_SECONDS, and network topology relative to the Arbitex platform region.
Recommended tooling: k6, wrk, or vegeta. All three can generate sustained HTTP load and report latency percentiles.
Test procedure:
- Start with a low baseline rate (10 rps) and confirm error rate is 0%.
- Ramp in steps (10 → 50 → 100 → 250 → 500 rps, or to your expected peak).
- Hold each step for at least 60 seconds to allow the cache and connection pool to reach steady state.
- Record p50, p95, and p99 latency plus error rate at each step.
- Note the load level at which any of the performance targets listed below are first exceeded — that is your current capacity ceiling.
Example k6 ramp script:
import http from 'k6/http';import { check } from 'k6';
export const options = { stages: [ { duration: '60s', target: 10 }, { duration: '60s', target: 50 }, { duration: '60s', target: 100 }, { duration: '60s', target: 250 }, { duration: '60s', target: 500 }, { duration: '30s', target: 0 }, ],};
export default function () { const res = http.post( 'https://outpost.example.internal/v1/proxy/completions', JSON.stringify({ model: 'gpt-4o', messages: [{ role: 'user', content: 'Hello' }] }), { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer <token>' } } ); check(res, { 'status 200': (r) => r.status === 200 });}Performance targets
Section titled “Performance targets”| Metric | Target | Notes |
|---|---|---|
| p99 latency (cache hit) | < 2 ms | Policy response served from in-memory cache; no platform round-trip. |
| p99 latency (cache miss) | < 50 ms | Policy response requires platform round-trip. Varies by region and platform load. |
| Error rate (normal load) | < 0.01% | Errors during normal load indicate connection pool exhaustion or platform availability issues. |
| Connection pool utilization | < 80% of PLATFORM_MAX_CONNECTIONS |
Sustained utilization above 80% risks pool exhaustion under traffic spikes. |
| Cache hit rate | > 80% | Below 50% indicates unusual request diversity or TTL misconfiguration; see Response Caching. |
| Drain completion time | < DRAIN_TIMEOUT_SECONDS |
In-flight requests should complete well within the configured drain window under normal load. |
Interpreting results
Section titled “Interpreting results”Pool wait count increasing: pool_wait_count in GET /admin/api/metrics/connections increasing monotonically under load means PLATFORM_MAX_CONNECTIONS is undersized. Increase it by 25–50% and retest.
Cache hit rate below target: If hit_rate_percent from GET /admin/api/cache/stats is below 80%, review whether request headers or body content vary significantly across requests. Requests with high cardinality body content (e.g., unique per-user prompts) will not benefit from caching at the policy evaluation layer.
Error rate spike at high load: A sudden error rate increase at a specific load level (rather than a gradual rise) usually indicates a hard resource limit: connection pool exhaustion, platform rate limiting, or file descriptor limits on the outpost host. Check active_connections against max_connections in the pool metrics and review outpost container resource limits.
p99 latency exceeds cache miss target: If uncached latency consistently exceeds 50 ms, investigate network path latency between the outpost and the platform using standard tools (traceroute, mtr). Consider deploying outpost replicas in the same cloud region as the platform or enabling a platform edge endpoint closer to the outpost deployment site.
See Also
Section titled “See Also”- Outpost Administration — budget enforcement, CredInt, health monitoring, JWT validation, PVC recovery, security hardening, SIEM direct integration