Sizing the GraphWarden proxy for your request volume
The GraphWarden proxy's CPU and memory needs scale with request rate and response-body transform complexity. This page gives conservative starting-point recommendations and a methodology for profiling your workload. Adjust based on your observed CPU and memory during load testing — these numbers are reference baselines, not guarantees.
Reference baselines — validate against your workloadPrerequisites
Size a proxy deployment with an AI assistant
Help me size a GraphWarden proxy for my expected workload.
Given my request profile (peak requests per minute, average response body size, and response filter complexity), recommend:
1. Starting CPU and memory allocation per replica.
2. Minimum and maximum replica counts.
3. A profiling plan to validate the recommendation against real traffic.
Reference documentation: https://graphwarden.com/llms.txt
Ask me for peak request rate, the kinds of Graph endpoints my clients hit most often, and whether my rulesets apply heavy response filters (many allow_properties or deep nested filtering).
Reference: llms.txt
Reference baseline (for initial provisioning)
The table below is a starting point for CPU and memory allocation per proxy replica. Rows target peak request rates, not averages. Provision for peak; auto-scaling handles the rest.
| Req/min | vCPU | RAM | Notes |
|---|---|---|---|
| ≤ 100 | 0.5 | 512 MB | Single replica. Typical dev/test workload. |
| 100-1000 | 1.0 | 1 GB | Single replica. Light production. |
| 1000-5000 | 2.0 | 2 GB | Single replica with headroom, or 2 small replicas. |
| 5000-20000 | 4.0 | 4 GB | Consider horizontal scale: 2-4 replicas of 2 vCPU / 2 GB each. |
| > 20000 | — | — | Horizontal scale mandatory: multiple replicas behind a load balancer. See "Horizontal scale considerations" below. |
Memory is driven primarily by response-body buffering. Graph responses can be large (a GET /users?$expand=... call against a large tenant returns megabytes per page), and the proxy holds the response in memory while applying filters. Doubling the RAM column is a safe first response to sustained memory pressure — doubling vCPU is the right response to sustained CPU pressure.
Dimensions that affect sizing
Four dimensions dominate proxy resource use:
- Response transform complexity. Rulesets with many
allow_propertiesentries, deep nested filtering, or hash/mask transforms over large objects consume more CPU per request. A rule that filters a single property is cheap; a rule with 20 transforms over a nested object adds measurable CPU. - Request body size. Write operations (POST, PATCH, DELETE) with large request bodies consume memory proportional to body size. The proxy's default
Kestrel:MaxRequestBodySizeMbis 10 MB — raising it raises the memory ceiling per request. - Concurrent connection count. Each concurrent request holds a connection, a request buffer, and (usually) a response buffer. Connection count is the best proxy for "current memory demand" and is what the ACA HTTP scale rule keys on.
- Logging verbosity. At
Logging:LogLevel:GraphProxy=Debugthe proxy logs every decision; atInformationit logs only denials and errors. Debug logging can add 10-20% CPU at high request rates and produces significant log volume.
Profiling methodology
Profiling against real traffic is the only way to size accurately. A four-step process:
Step 1 — Capture baseline at representative rate. Deploy the proxy at the table's baseline for your expected peak. Run representative traffic (either production mirror or synthetic load at production-like endpoint mix) for 30-60 minutes. Capture CPU, memory, and request latency percentiles.
Step 2 — Increase linearly. Double the request rate. Observe which metric saturates first — CPU will cap at 100%, memory will trigger GC pressure and eventually OOM. Note the request rate at which either saturates. This is your current capacity ceiling.
Step 3 — Identify headroom. Your production target is the capacity ceiling minus 30-40%. Running at 70% of capacity leaves room for Graph-side latency spikes (which fan out into more concurrent proxy requests at the same rate) and rule evaluations that periodically fan out Graph calls of their own (group_membership checks, for example).
Step 4 — Validate under fault conditions. Run the load test with Graph API artificially delayed (use an outbound proxy that injects 2-3 second latency). Confirm the proxy's resource use stays stable rather than spiking — if memory climbs unbounded, the concurrent-request count is too high and max-replicas needs to rise.
Repeat every major ruleset change and every quarterly review. Rulesets accrete transforms over time; a ruleset that fit on 1 vCPU at launch may need 2 vCPU six months later.
Horizontal scale considerations
The proxy is stateless. All state (ruleset, Graph tokens, audit buffer) is either read-only (ruleset, refreshed from control plane) or per-request (audit events are flushed on a timer and replayed from local retention if the cloud is unreachable).
Because the proxy is stateless, horizontal scale requires no session affinity. A simple round-robin or least-connections load balancer distributes traffic evenly. For Azure Container Apps, the built-in ingress handles this automatically. For on-premise deployments, any TCP or HTTPS load balancer (Azure Load Balancer, F5, HAProxy, nginx) works without special configuration.
Two caveats:
ProxyJwt:SigningKeymust be identical across replicas. A token issued by replica A must validate at replica B. Store the signing key in Key Vault once and inject it into every replica; do not generate per-replica keys.- Audit retention is per-replica. If a replica with undelivered audit events in its local retention path is destroyed (scale-down, pod eviction), those events are lost. For strict compliance deployments, pin the retention path to a persistent volume that survives replica churn, or set
Audit:FlushIntervalSecondslow enough that the in-memory buffer is small.
Troubleshooting
CPU sits at 100% with modest request rate
The ruleset is doing too much work per request. Inspect your response_filter blocks — deeply nested allow_properties lists, hash or noise transforms applied to large string fields, and regex_replace with complex expressions all accumulate CPU. Simplify the most expensive rule first (measure which rule dominates with Serilog's Decision events filtered by RuleId).
Memory climbs until OOM or GC pause spike
Response bodies from Graph are large enough to pressure the heap. Two fixes: increase memory allocation (doubling is the usual starting point), or reduce the client's $select footprint on hot endpoints so Graph returns fewer bytes per page. Server-side $select is almost always cheaper than proxy-side filtering of full objects.
Intermittent 503 under peak load
The Key Vault client is throttling. Azure Key Vault caps request rate per vault (2000 requests per 10 seconds at the standard tier). The proxy reads secrets on startup and on rotation, not per request — intermittent 503s from vault throttling usually indicate a rapid-scale event where many replicas started simultaneously. Increase minReplicas to reduce the churn, or stagger startup across replicas if your orchestrator supports it.
Request latency doubles at a specific request rate threshold
Kestrel's default thread pool saturates. The proxy's CPU is below 100% but the per-request latency climbs — work is queued waiting for a thread. Increase CPU allocation (Kestrel thread pool sizes based on Environment.ProcessorCount) or add a replica. This symptom is a clearer signal to scale horizontally than vertical CPU growth.
Last reviewed: