> ## Documentation Index
> Fetch the complete documentation index at: https://autopilot.docs.xano.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pod Resizing

> Vertical optimization — right-sizing container CPU and memory requests from actual usage, in-place with no restarts.

The Pod Resizer monitors container CPU and memory usage via Prometheus, computes
right-sized resource requests, and applies them — in-place on Kubernetes 1.33+ (no
restarts), or as a recommendation you apply on restart.

**CRD:** `PodResizePolicy` (cluster-scoped)

## How it works

1. Every 5 minutes, the reconciler fetches 24h of usage data per workload.
2. It computes percentile-based recommendations (P99 for memory, Max or P95 for CPU).
3. It applies a configurable buffer (default 15%) for headroom.
4. If the change exceeds the sensitivity threshold, it applies an in-place resize (K8s 1.33+) or records a recommendation.

### The formula

```
CPU:    recommended = Max(cpu_samples) × 1.15
Memory: recommended = max(P99(mem) × 1.15, P99(mem_peaks) × 1.075)
```

In plain English:

* **CPU** uses the maximum observed usage over 24h, plus a 15% buffer.
* **Memory** takes the higher of: P99 of actual usage (+15% buffer) or P99 of 1-minute peaks (+7.5% buffer).

The 15% default buffer is configurable via `recommendationBuffer` on the PodResizePolicy CRD.

### Why these choices

**Why Max for CPU (not P99)?** CPU is compressible — if you're wrong, the pod gets
throttled, not killed. Using Max covers worst-case spikes. If the pod has an HPA, Autopilot
switches to P95 instead (let the HPA handle the top 5% via horizontal scaling).

**Why P99 for memory (not Max)?** Memory is **not** compressible — if you're wrong, the pod
gets OOM-killed. But absolute Max is unstable across Prometheus step-grid shifts (the same
spike appears and disappears as the query window moves). P99 filters step-aliasing noise
while still covering 99% of actual usage.

**Why a separate peak query for memory?** The main memory query uses instantaneous values
(good for percentiles). Short spikes (30–60 seconds) can be missed between Prometheus scrape
points. The `max_over_time[1m]` query captures these spikes. Autopilot takes P99 of the
peaks (not absolute max) for stability, with half the buffer (7.5%) since the peak query
already inflates values slightly.

**Why 15% buffer and not target utilization?** A `targetUtilization` divider double-inflated
recommendations when combined with the buffer. The old formula
`percentile × (1 + buffer) / targetUtil` — with a 15% buffer and 70% target — is 64%
overhead. The current formula `percentile × (1 + buffer)` is exactly 15% overhead. The
`targetUtilization` field still exists on the CRD but is display-only.

## What gets filtered out

* **Startup spikes** — the first 5 minutes after pod creation are ignored (JIT warmup, cache loading, connection pools).
* **Container restart spikes** — samples within 5 minutes of a container restart are filtered (same initialization noise).
* **Workload aggregation** — data from old pod incarnations (same Deployment, prior restarts) is merged so you get the full 24h history even after a restart.

## When recommendations change

A recommendation doesn't change on every evaluation. Five stability mechanisms prevent churn:

1. **Change sensitivity** — the change must exceed a dynamic threshold (\~25% for tiny pods like 64Mi, \~5% for large pods like 16Gi). Small fluctuations are ignored.
2. **Hysteresis** — if the rounded recommendation bounces between two buckets (e.g. 128Mi ↔ 192Mi), the previous value is held when the pre-rounding value is near the boundary.
3. **Decrease bias** — memory decreases require the value to move at least 50% of the way toward the new recommendation before being accepted. Memory is not compressible, so shrinks are conservative.
4. **P99 of peaks** — filters the Prometheus step-aliasing that would cause Max to oscillate.
5. **Split queries** — instantaneous values for percentiles, `max_over_time` only for Max, preventing peak inflation of the P50–P99 range.

### Rounding

* CPU: nearest 25m (below 500m) or 100m (500m+)
* Memory: nearest 32Mi (below 256Mi) or 64Mi (256Mi+)

## Key behaviors

* **HPA-aware** — switches to P95 for CPU on HPA-managed workloads (lets the HPA handle spikes).
* **OOM-responsive** — bumps memory on OOM kills, never shrinks below OOM-bumped values.
* **Change sensitivity** — dynamic threshold prevents churn.
* **Startup filtering** — ignores the first 5 min of data after pod creation or container restart.

## Configuration

```yaml theme={null}
apiVersion: ops-ai.io/v1alpha1
kind: PodResizePolicy
metadata:
  name: default
spec:
  recommendationBuffer: 15     # 15% headroom above measured usage
  resizeMode: InPlace          # InPlace or Restart
  includeNamespaces: ["xano-*"]
  excludeNamespaces: [kube-system, monitoring, ops-ai]
  minResources:
    cpu: 100m
    memory: 128Mi
  maxResources:
    cpu: "4"
    memory: 8Gi
  changeSensitivity:
    cpu: { mode: Dynamic }
    memory: { mode: Dynamic }
  singleReplicaProtection:
    enabled: true
  limitSafetyNet:
    enabled: true
    minMemoryLimit: 128Mi
```

### Key knobs

| What                  | Field                              | Default          | Effect of changing                                                              |
| --------------------- | ---------------------------------- | ---------------- | ------------------------------------------------------------------------------- |
| Recommendation buffer | `recommendationBuffer`             | 15               | Higher = more headroom, lower = tighter packing                                 |
| CPU percentile        | `resourceConfig.cpu.percentile`    | Max (P95 if HPA) | P95 = less conservative, Max = covers worst case                                |
| Memory percentile     | `resourceConfig.memory.percentile` | P99              | P95 = tighter but higher OOM risk                                               |
| Change sensitivity    | `changeSensitivity.*.mode`         | Dynamic          | `Percentage` for a fixed threshold, `Dynamic` scales with pod size              |
| Downscaling mode      | `downscalingMode`                  | Immediate        | `Deferred` waits for a natural restart; `AdaptiveDeferred` tries in-place first |
| Namespace filter      | `includeNamespaces`                | `["xano-*"]`     | Which namespaces get resized                                                    |

## Dashboard

The **Vertical Optimization** page shows all recommendations with savings estimates. Click
any recommendation for audit detail including usage charts, algorithm transparency, and
history.
